Skip to main content

parse_bytes_in_place

Function parse_bytes_in_place 

Source
pub fn parse_bytes_in_place(
    buf: Vec<u8>,
    opts: &ParseOptions,
) -> Result<Document>
Expand description

Destructive-parse fast path. Takes ownership of buf, mutates it in place during parsing, and returns a Document whose strings point directly into the (now-mutated) buffer. The Document keeps the buffer alive for its lifetime.

The speedup vs parse_bytes is workload-dependent and depends on what flags you pass in opts. On entity-heavy documents the structural in-place mechanism (in-place entity decode, zero string copy) wins ~20-30% even with full XML 1.0 validation enabled. On documents that contain few or no entities (most “data” XML — swiss_prot, OSM, sitemaps, RSS) the structural win is small — often within run-to-run noise — because the validation cost dominates and both paths pay it equally. In that regime, the bigger lever is the four skip_* validation flags: passing all four true reaches roughly half of pugixml’s throughput. See “When to use this” below for which combination matches your needs.

§Why it’s faster

Two structural advantages over parse_bytes, independent of any validation flags:

  1. No string copy into the arena. Element names, attribute values, and text content slices point directly at bytes inside buf. parse_bytes copies them into a fresh arena (or, when borrowing succeeds, holds the source separately).
  2. In-place entity decode. Builtin entities (&amp;, &lt;, &gt;, &apos;, &quot;), numeric character references, and newline normalization are decoded by mutating the source bytes in place — no scratch buffer per text chunk. User-defined entities with replacement text smaller than the &name; reference also fit in place; larger ones are rejected (see Errors below).

The skip-all validation flags are NOT applied automatically. If you want the maximum speed shown in the benchmarks (about ~30% on top of the structural wins above), build ParseOptions with skip_xml_char_validation, skip_name_validation, skip_attr_validation, and skip_end_tag_check set to true. Otherwise the parser still performs full XML 1.0 validation while it parses destructively.

use sup_xml_core::{parse_bytes_in_place, ParseOptions};

let buf: Vec<u8> = b"<root><child id=\"1\"/></root>".to_vec();

// Full XML 1.0 validation + destructive parse:
let _doc = parse_bytes_in_place(buf.clone(), &ParseOptions::default())?;

// Trust-the-input maximum-speed:
let fast_opts = ParseOptions {
    skip_xml_char_validation: true,
    skip_name_validation:     true,
    skip_attr_validation:     true,
    skip_end_tag_check:       true,
    ..ParseOptions::default()
};
let _doc = parse_bytes_in_place(buf, &fast_opts)?;

§When to use this vs parse_bytes

Pick parse_bytes_in_place when:

  • You own the input buffer and don’t need to preserve its original bytes (round-trip-byte-identical serialization isn’t a goal).
  • Your inputs use only the 5 XML 1.0 builtin entities, OR any user-defined <!ENTITY> declarations have replacement text whose byte length is ≤ the corresponding &name; reference.
  • You do NOT need ParseOptions::recovery_mode.

Pick parse_bytes when:

  • You need lossless round-trip (preserve the input bytes verbatim).
  • You need ParseOptions::recovery_mode.
  • You don’t own the buffer or can’t have it consumed.

§Errors

Returns Err immediately (before any mutation) for:

  • opts.recovery_mode == true — recovery is incompatible with destructive parsing (we can’t unmutate after the fact).

Returns Err during parsing for:

  • User <!ENTITY> whose expansion exceeds its reference length — the bytes don’t fit in place.
  • Cyclic entity references — well-formedness error, same as parse_bytes.
  • Any other XML 1.0 well-formedness violation.

On any error, buf is consumed and dropped (it has been partially mutated by the time most errors fire; handing it back would be misleading).

§Buffer ownership

buf is consumed unconditionally — successful parse returns a Document that owns the buffer; failed parse drops it. If you might need to fall back to parse_bytes, use that entry point from the start; speculative pre-cloning defeats the performance benefit this function exists for.