Expand description
Streaming XML reader over io::Read.
XmlByteStreamReader is a thin wrapper around the existing
slurped XmlBytesReader that owns a rolling Vec<u8> buffer
and pulls more bytes from the inner reader on demand. Used by
the CLI’s lint subcommand to validate multi-GB XML files in
bounded memory without slurping them.
§Design
The wrapper owns:
inner: R— the source of bytes.buf: Vec<u8>— a rolling buffer holding the not-yet-consumed slice of input. Grows up tobuffer_size, then refuses to grow further (a single XML token bigger thanbuffer_sizebecomes a hard error).reader: XmlBytesReader<'static>— the slurped reader, with its scanner re-bound to point intobufafter every refill / compaction / growth. The'staticlifetime is a deliberate lie maintained internally: the actual borrow is bounded byself.buf, and we re-point the scanner viaXmlBytesReader::rebind_scannerwhenever the buffer might have moved. This is sound because (a) both fields are owned bySelfand outlive each other, (b) the scanner only stores a raw pointer into the buffer (no Rust borrow), and (c) we never let any&[u8]derived frombufescape across a buffer mutation.
§Pre-fill model
XmlBytesReader::next mutates internal state (depth,
element_stack, …) partway through the call — i.e., it’s not
transactional. We can’t retry it after a mid-token refill
without corrupting state. So the wrapper refills between
events, before calling next:
1. Before calling reader.next():
if cur_len - cur_pos < buffer_size, refill.
2. Call reader.next() — guaranteed to have at least
buffer_size bytes ahead. Completes within them or hits
true EOF.
3. Repeat.This guarantees the inner reader never sees a transient
“ran-off-the-end” condition — its bytes are always there. The
trade-off is that buffer_size is also the maximum size of a
single XML token (text node, attribute value, CDATA section);
anything larger errors out. Matches libxml2’s
XML_MAX_TEXT_LENGTH semantics.
§Reading
XmlByteStreamReader::next_event pulls one event at a time,
streaming more bytes from the source as needed. Each event
borrows the rolling buffer and is valid only until the next pull;
the borrow checker enforces this by tying the event’s lifetime to
&mut self, so a caller must consume each event before requesting
the next. This is the same zero-copy contract as
XmlBytesReader::next and quick-xml’s Reader::read_event.
XmlByteStreamReader::validate is the drive-to-EOF convenience
used by the CLI’s lint when only the well-formedness verdict
matters — it pulls events to completion and discards them.
Structs§
- XmlByte
Stream Reader - Streaming XML reader that pulls bytes from an
io::Readsource on demand.
Constants§
- DEFAULT_
BUFFER_ SIZE - Default working-buffer size when none is provided. Matches
libxml2’s
XML_MAX_TEXT_LENGTH(10 MB) — bigger than any single text node in the vast majority of real-world XML, small enough that streaming meaningfully saves memory vs slurping. - HUGE_
BUFFER_ SIZE - “Huge” mode buffer size — matches libxml2’s
XML_PARSE_HUGE(1 GB). Use for inputs that contain unusually large tokens (embedded base64 blobs in SVG, OOXML packages, etc.).