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:
- No string copy into the arena. Element names, attribute
values, and text content slices point directly at bytes inside
buf.parse_bytescopies them into a fresh arena (or, when borrowing succeeds, holds the source separately). - In-place entity decode. Builtin entities (
&,<,>,',"), 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.