Skip to main content

Module streaming_reader

Module streaming_reader 

Source
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 to buffer_size, then refuses to grow further (a single XML token bigger than buffer_size becomes a hard error).
  • reader: XmlBytesReader<'static> — the slurped reader, with its scanner re-bound to point into buf after every refill / compaction / growth. The 'static lifetime is a deliberate lie maintained internally: the actual borrow is bounded by self.buf, and we re-point the scanner via XmlBytesReader::rebind_scanner whenever the buffer might have moved. This is sound because (a) both fields are owned by Self and 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 from buf escape 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§

XmlByteStreamReader
Streaming XML reader that pulls bytes from an io::Read source 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.).