Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.xml

std.xml reads and writes bounded XML 1.0 events over std.io byte streams. It is implemented in Plenty so buffer, string, attribute-list, and nesting-stack allocation failures remain explicit. It keeps a current token and a stack of open names, without building a document tree.

Events and format

Attribute(name: str, value: str) has public fields. Events own their strings and attribute lists; they remain valid after another reader call.

EventMeaning
Declaration(Option[bool])XML 1.0 declaration; optional standalone yes/no
Start(str, list[Attribute])Open an element with ordered attributes
Empty(str, list[Attribute])Empty element, without a separate end event
End(str)Close the matching element
Text(str)Decoded character data, including whitespace
CData(str)CDATA content without delimiters
Comment(str)Comment content without delimiters
ProcessingInstruction(str, str)Target and data; separator whitespace is omitted
EofComplete, well-formed document

Input must be UTF-8, optionally with a leading UTF-8 BOM. A declaration is optional; when present it must be first, specify version 1.0, and use UTF-8 if it names an encoding. The standalone flag is retained. There must be exactly one root element; comments, processing instructions, and XML whitespace may surround it. Names use XML 1.0 Fifth Edition character ranges. Duplicate attribute names and mismatched closing names are errors.

Literal CRLF and CR normalize to LF. Literal whitespace in attributes becomes spaces. The five predefined entities (amp, lt, gt, apos, quot) and decimal/hexadecimal character references are decoded in text and attributes; references to tab, LF, or CR in attributes retain those characters. Comments, CDATA, and processing instructions do not expand references. XML-forbidden characters, malformed UTF-8, and malformed references are rejected.

DTD declarations, including internal subsets and external entities, are rejected. The module never loads an external resource. Names with colons and xmlns attributes are preserved literally: it does not resolve namespaces or check namespace bindings. It does not validate against a schema or DTD, build a DOM, query XPath, canonicalize XML, or support encodings other than UTF-8. Events preserve content rather than original spelling, quote choices, entity spellings, or declaration formatting.

Limits

limits() returns a Limits with public fields. Constructors copy the limits; changing the original afterwards has no effect.

FieldTypeDefaultMeaning
max_depthNonNegative[i64]64Open nesting including an empty element; at most 1024
max_inputNonNegative[i64]67108864Total input bytes consumed, including BOM
max_outputNonNegative[i64]67108864Total encoded bytes written
max_tokenPositive[i64]1048576Raw bytes per reader event, or encoded bytes per writer event, including markup
max_attributesNonNegative[i64]4096Attributes per element

A literal converts where a field is expected; a computed value needs NonNegative.try_from(value)? or Positive.try_from(value)?. reader and writer trap when max_depth is above 1024. A zero depth permits no root and therefore no complete document. The input limit applies to readers and the output limit to writers. The XML declaration has its own fixed grammar of at most three fields. The BOM is checked against the token limit separately from the first event.

A reader uses a fixed 4096-byte read buffer and may read ahead that far. Size limits constrain consumption, rather than the inner stream’s position. Text between markup tokens is one event and must fit max_token; it is not split into arbitrarily sized chunks. Memory is bounded by the buffer, current event, and at most max_depth retained names, plus events retained by the caller. These limits bound input-controlled work and storage, not elapsed time or a stream that repeatedly reports interruption.

Input/output position sums are bounded by their remaining byte budgets before advancing, even when a configured budget is i64.MAX. Internal cursor and suffix arithmetic uses explicit opt-outs justified by held-buffer bounds; character-reference arithmetic is guarded by the Unicode scalar ceiling. Oversized numeric references remain InvalidReference, and byte/token exhaustion remains InputTooLarge, OutputTooLarge, or TokenTooLarge rather than exposing arithmetic errors. Partial writer progress retains the original transport cause and exact accepted-byte offset.

Reading and writing

reader[R](inner: R, bounds: &Limits) -> Result[Reader[R], XmlError] owns a stream with a public read method matching std.io.Reader. Reader.next(&mut self) -> Result[Event, XmlError] retries interrupted reads, checks reported progress, and returns Eof repeatedly after successful EOF. Reader.offset() counts consumed input bytes, before line normalization.

writer[W](inner: W, bounds: &Limits) -> Result[Writer[W], XmlError] owns a stream with public write and flush methods matching std.io.Writer. Writer.write(&mut self, event: &Event) -> Result[(), XmlError] validates and encodes one event before writing it. It checks nesting, root count, attribute uniqueness, names, characters, and event ordering. Text and attribute values are escaped; attribute tab/LF/CR and text CR inside elements are written as character references. Comments containing -- or ending in -, CDATA containing ]]>, and processing instructions containing ?> are rejected. Raw CR in comments, CDATA, processing instructions, or document whitespace still follows XML line normalization when read back.

Writer.finish(&mut self) -> Result[(), XmlError] verifies that the root was completed and flushes the stream. It is idempotent after success. Writing Eof calls finish; further writes are rejected. Writer.offset() counts bytes accepted by the inner writer. Both wrappers expose their owned inner stream; avoid reading or writing it directly while using the wrapper, since that bypasses the wrapper’s position and document state.

A writer can stream a reader’s events straight through, but this is not an atomic file replacement: earlier events and a partially written event remain visible on failure. Use a separately managed temporary destination when that matters. See the XML lesson.

Errors

XmlError has public fields fault: Fault and offset: i64. The offset is zero-based consumed input or accepted output bytes at detection. Validation may detect a malformed token after consuming it; this is not necessarily the byte containing the original mistake.

Fault distinguishes syntax, unexpected EOF, invalid names/characters/UTF-8, invalid references, unknown entities, duplicate attributes, mismatched ends, forbidden doctypes, unsupported declarations, each resource bound, invalid writer state, and Stopped. Allocation(AllocError) and Stream(StreamError) retain their causes, including stream allocation errors. All allocation in the module is fallible. A malformed or oversized reader event, failed writer event, or failed finish permanently stops that wrapper; subsequent operations return Stopped. Successfully finished writers instead reject additional writes with InvalidState.

Acknowledgements

  • W3C XML 1.0 Fifth Edition supplies the character, name, document, reference, and normalization rules implemented independently.
  • quick-xml informs the streaming event API; the module does not wrap or ship its code.