Files
craft-libs/crates/xml/src/read.rs
T
Dylan M. Taylor 34aa8c1cfd craft-xlsx: read parts with canonical prefixes so kept trees can be written back (#1031)
The part layer read every part by local name, so extLst children (x14:, xr:, xm:),
mc:AlternateContent and r:id lost their namespaces and could not be written back.
Parts are now read with one fixed prefix per namespace (the main namespace unprefixed),
mc:Ignorable and mc:Choice/@Requires are rewritten to match, and ns::declare prepares a
kept tree for writing. craft-xml: a namespace listed with the empty prefix reads unprefixed.
2026-11-11 20:46:37 -05:00

825 lines
34 KiB
Rust

//! Reading: the pull reader over `quick-xml`, the tree builder on top of it, and streaming.
use std::borrow::Cow;
use std::ops::Range;
use quick_xml::Reader;
use quick_xml::events::{BytesStart, Event as Q};
use crate::Error;
use crate::tree::{El, Node, Pat, local_of};
use crate::write::is_xml_char;
/// The namespace the `xml` prefix is always bound to.
pub const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
/// What the reader refuses to exceed.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
/// Most bytes of input. Default 256 MiB.
pub max_input: usize,
/// Deepest element nesting: the root is at depth 1. Default 256.
pub max_depth: usize,
/// Most nodes in a tree: elements, text stretches and whatever else is kept. Elements
/// handed to a streaming callback leave the count with everything inside them. Default
/// 2,000,000: a tree node costs about 72 bytes before its strings, and text lies between
/// elements at most, so this is about a million elements of an indented document.
pub max_nodes: usize,
/// Most attributes on one start tag, namespace declarations and malformed ones included.
/// With [`Names::Canonical`] it is also the most namespace declarations in scope at once.
/// Default 1024.
pub max_attrs: usize,
/// Most attributes kept in a tree at once, over all its elements. An attribute costs 48
/// bytes before its strings and five bytes of input (` a=""`), so without this a document
/// inside every other limit could take ten times its size in attribute headers. Elements
/// handed to a streaming callback take their attributes out of the count. Default
/// 4,000,000: two for every node at the node limit.
pub max_attrs_total: usize,
}
impl Limits {
pub const DEFAULT: Limits =
Limits { max_input: 256 * 1024 * 1024, max_depth: 256, max_nodes: 2_000_000, max_attrs: 1024, max_attrs_total: 4_000_000 };
}
impl Default for Limits {
fn default() -> Limits {
Limits::DEFAULT
}
}
/// How element and attribute names are read.
#[derive(Clone, Copy, Default)]
pub enum Names<'a> {
/// The qualified name as written: `p:sp`, `r:id`. Match with a `&str` when the prefixes
/// are known, or with [`crate::Local`] and [`El::attr_local`] when they are not.
#[default]
AsWritten,
/// The local name only: `sp`, `id`.
Local,
/// The caller's prefix for the name's namespace and the local name: `w:p`, whatever
/// prefix the document used. See [`Canonical`].
Canonical(&'a Canonical<'a>),
}
/// An element name being given its canonical prefix.
#[derive(Clone, Copy, Debug)]
pub struct NsName<'s> {
/// The prefix [`Canonical::namespaces`] has for `uri`, or [`Canonical::unknown`].
pub prefix: &'s str,
pub uri: &'s str,
pub local: &'s str,
/// The canonical name of the enclosing element; `None` at the root.
pub parent: Option<&'s str>,
}
/// See [`Canonical::element`].
pub type ElementHook<'a> = dyn Fn(&NsName<'_>) -> Option<&'static str> + 'a;
/// See [`Canonical::attr_value`].
pub type AttrValueHook<'a> = dyn Fn(&str, &str, &str, &Scope<'_>) -> Option<String> + 'a;
/// Canonical prefixes by namespace URI.
///
/// - An element in a listed namespace is named `prefix:local`; in an unlisted one,
/// `unknown:local`; in no namespace (or with a prefix nothing declares), `local`. A namespace
/// listed with the empty prefix reads unprefixed too: a vocabulary's own namespace.
/// - A prefixed attribute is named the same way. An unprefixed attribute is in no namespace
/// and keeps its name.
/// - The `xml` prefix needs no entry: `xml:space` stays `xml:space`.
#[derive(Clone, Copy)]
pub struct Canonical<'a> {
/// (prefix, namespace URI). The first entry with a URI wins, so several URIs may share a
/// prefix (the Strict and Transitional forms of a vocabulary).
pub namespaces: &'a [(&'a str, &'a str)],
/// The prefix for a namespace that is not listed. `"?"` by default: it cannot be part of an
/// XML name, so such names never collide with real ones and [`El::write_xml`] leaves them
/// out.
pub unknown: &'a str,
/// Overrides the prefix of an element, for vocabularies where one namespace is read as
/// several depending on the element and its parent. `None` keeps [`NsName::prefix`]. The
/// prefixes it returns are constants of the caller's.
pub element: Option<&'a ElementHook<'a>>,
/// Rewrites an attribute value while the namespace declarations in scope are still known:
/// for values that hold prefixes (`mc:Choice/@Requires`, `xsi:type`). Called with the
/// element's name, the attribute's name, the value and the scope; `None` keeps the value.
pub attr_value: Option<&'a AttrValueHook<'a>>,
}
impl<'a> Canonical<'a> {
pub const fn new(namespaces: &'a [(&'a str, &'a str)]) -> Canonical<'a> {
Canonical { namespaces, unknown: "?", element: None, attr_value: None }
}
/// The prefix for `uri`: the listed one, `xml` for [`XML_NS`], else [`Canonical::unknown`].
pub fn prefix(&self, uri: &str) -> &'a str {
match self.namespaces.iter().find(|(_, u)| *u == uri) {
Some((p, _)) => p,
None if uri == XML_NS => "xml",
None => self.unknown,
}
}
}
/// The namespace declarations in scope at an element.
pub struct Scope<'s> {
bindings: &'s [(String, String)],
}
impl<'s> Scope<'s> {
/// The namespace URI `prefix` is bound to; `""` is the default namespace.
pub fn uri(&self, prefix: &str) -> Option<&'s str> {
if prefix == "xml" {
return Some(XML_NS);
}
self.bindings.iter().rev().find(|(p, _)| p == prefix).map(|(_, u)| u.as_str()).filter(|u| !u.is_empty())
}
}
/// What to do with a reference to an entity that is neither predefined nor numeric. Entities a
/// document declares are never expanded, so `&name;` for a declared entity is one of these too.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum UnknownEntity {
/// Leave it out.
#[default]
Drop,
/// Keep it as the text `&name;`.
Literal,
/// Refuse the document ([`Error::UnknownEntity`]).
Error,
}
/// What to do with a document type declaration. Whatever is chosen, the declaration is never
/// processed.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Doctype {
/// Skip it.
#[default]
Ignore,
/// Refuse a declaration with an internal subset (`<!DOCTYPE x [ … ]>`), skip any other.
RejectInternalSubset,
/// Refuse any declaration.
Reject,
}
/// How to read a document. `Options::default()` keeps names as written and is lenient.
#[derive(Clone, Copy)]
pub struct Options<'a> {
pub limits: Limits,
pub names: Names<'a>,
/// Keep namespace declarations (`xmlns`, `xmlns:p`) as attributes, as written. On by
/// default; [`Options::local`] and [`Options::canonical`] turn it off.
pub ns_decls: bool,
/// Refuse what a lenient read repairs. Lenient: an end tag closes the innermost open
/// element whatever its name, an end tag with nothing open is skipped, elements still open
/// at the end of the input are closed, reading stops when the root element ends, a
/// malformed attribute is skipped, and bytes that are not UTF-8 become U+FFFD. Strict: each
/// of those is an error, as are a second root element and text outside the root.
pub strict: bool,
pub unknown_entity: UnknownEntity,
pub doctype: Doctype,
/// Keep CDATA sections as [`Node::CData`] instead of joining them to the text around them.
pub cdata: bool,
/// Keep processing instructions inside the root element as [`Node::Pi`].
pub pis: bool,
/// Keep comments inside the root element as [`Node::Comment`].
pub comments: bool,
/// Drop the text of an element that has child elements when all of it is whitespace: the
/// indentation of an indented document.
pub drop_blank_text: bool,
/// For [`Options::parse_streaming`]: how far below the root an element may be and still be
/// handed out. 2 by default: children and grandchildren of the root.
pub split_depth: usize,
}
impl Default for Options<'_> {
fn default() -> Self {
Options {
limits: Limits::DEFAULT,
names: Names::AsWritten,
ns_decls: true,
strict: false,
unknown_entity: UnknownEntity::Drop,
doctype: Doctype::Ignore,
cdata: false,
pis: false,
comments: false,
drop_blank_text: false,
split_depth: 2,
}
}
}
/// Reads `bytes` with [`Options::default`].
pub fn parse(bytes: &[u8]) -> Result<El, Error> {
Options::default().parse(bytes)
}
impl<'a> Options<'a> {
/// Names by local part, namespace declarations dropped.
pub fn local() -> Options<'static> {
Options { names: Names::Local, ns_decls: false, ..Options::default() }
}
/// Names with canonical prefixes, namespace declarations dropped.
pub fn canonical(canonical: &'a Canonical<'a>) -> Options<'a> {
Options { names: Names::Canonical(canonical), ns_decls: false, ..Options::default() }
}
/// [`Options::strict`] on, and an undefined entity an error.
#[must_use]
pub fn strict(self) -> Self {
Options { strict: true, unknown_entity: UnknownEntity::Error, ..self }
}
#[must_use]
pub fn with_limits(self, limits: Limits) -> Self {
Options { limits, ..self }
}
/// Reads a document into its root element.
pub fn parse(&self, bytes: &[u8]) -> Result<El, Error> {
self.parse_streaming(bytes, &[], |_| Ok::<(), Error>(()))
}
/// Reads a document, handing each complete element whose name is in `split` to `each`
/// instead of keeping it in the tree: worksheet rows, shared strings. Only elements at
/// most [`Options::split_depth`] levels below the root are handed out; a deeper element of
/// the same name stays in the tree. `split` names are matched against names as this reader
/// produces them. Elements handed out do not count toward [`Limits::max_nodes`] once they
/// are gone, so a part of any length can be read; each one is itself held to the limit.
///
/// An error from `each` stops the read and is returned.
pub fn parse_streaming<E: From<Error>>(&self, bytes: &[u8], split: &[&str], each: impl FnMut(El) -> Result<(), E>) -> Result<El, E> {
match self.parse_streaming_partial(bytes, split, each) {
(_, Some(e)) => Err(e),
(root, None) => root.ok_or_else(|| Error::NoRoot.into()),
}
}
/// [`Options::parse_streaming`] that also returns what it had read when it failed: the
/// root with the children that were complete (and not handed out) before the error, and
/// the elements still open at that point with what they held. A worksheet whose rows
/// cannot all be read still gives its properties and merged cells. `(None, Some(_))` when
/// nothing was read; `(Some(_), None)` is a whole document.
///
/// The partial tree is not a document: it stops where the error was, and an open element
/// named in `split` is attached to it instead of being handed to `each`.
pub fn parse_streaming_partial<E: From<Error>>(
&self,
bytes: &[u8],
split: &[&str],
mut each: impl FnMut(El) -> Result<(), E>,
) -> (Option<El>, Option<E>) {
if bytes.len() > self.limits.max_input {
return (None, Some(Error::TooLarge(self.limits.max_input).into()));
}
let text = to_utf8(bytes);
let mut events = Events::new(*self, &text, false);
let mut tree = Builder { opts: self, split, stack: Vec::new(), marks: Vec::new(), kept: 0, attrs: 0, root: None, at: 0 };
let mut err = tree.run(&mut events, &mut each).err();
// What was open when the read stopped goes into its parent as it is.
while let Some(el) = tree.stack.pop() {
match tree.stack.last_mut() {
Some(parent) => parent.children.push(Node::El(el)),
None => tree.root = Some(el),
}
}
if tree.root.is_none() && err.is_none() {
err = Some(Error::NoRoot.into());
}
(tree.root, err)
}
/// The first element that matches, from its start tag alone: its name and attributes, no
/// children. Nothing after the tag is read, so unbalanced markup around it neither hides
/// nor moves it. `None` when there is none or the markup before it cannot be read.
pub fn first_start(&self, bytes: &[u8], pat: impl Pat) -> Option<El> {
if bytes.len() > self.limits.max_input {
return None;
}
let text = to_utf8(bytes);
// No tree is built, and markup that never closes its tags would otherwise look deep.
let opts = Options { limits: Limits { max_depth: usize::MAX, ..self.limits }, ..*self };
let mut events = Events::new(opts, &text, false);
loop {
match events.next().ok()?? {
Event::Start { name, attrs } if pat.matches(&name) => return Some(El { name, attrs, children: Vec::new() }),
_ => {}
}
}
}
/// A pull reader over `utf8`, which is UTF-8 text: pass other input through [`to_utf8`]
/// first. No tree is built, so [`Limits::max_nodes`] and [`Limits::max_input`] do not
/// apply; depth and attribute limits do.
pub fn events<'i>(&self, utf8: &'i [u8]) -> Events<'a, 'i> {
Events::new(*self, utf8, true)
}
}
/// One step of a document. See [`Options::events`].
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
/// A start tag. An empty element (`<a/>`) is a `Start` followed by its `End`.
Start {
name: String,
attrs: Vec<(String, String)>,
},
/// The end of the element named `name` (the name of its start tag).
End {
name: String,
},
/// Character data, references resolved. One stretch of text may arrive in several events.
Text(String),
/// The content of a CDATA section.
CData(String),
Pi {
target: String,
data: String,
},
Comment(String),
}
struct Open {
/// Where this element's namespace declarations start in the scope.
scope: usize,
/// The name as written, kept for a strict read to check the end tag against.
raw: Vec<u8>,
/// The name as produced, kept when an end event or a canonical parent needs it.
name: String,
}
/// A pull reader. See [`Options::events`].
pub struct Events<'o, 'i> {
reader: Reader<&'i [u8]>,
opts: Options<'o>,
/// Whether [`Event::End`] carries the name.
end_names: bool,
open: Vec<Open>,
scope: Vec<(String, String)>,
/// The end of an empty element, due after its start.
pending: bool,
/// Whether a root element has started.
rooted: bool,
span: Range<usize>,
}
fn at(pos: u64) -> usize {
usize::try_from(pos).unwrap_or(usize::MAX)
}
fn line_ends(s: Cow<'_, str>) -> String {
// XML 1.0 §2.11: a literal CR LF or CR is read as LF.
if s.contains('\r') { s.replace("\r\n", "\n").replace('\r', "\n") } else { s.into_owned() }
}
fn is_ns_decl(key: &[u8]) -> bool {
key == b"xmlns" || key.starts_with(b"xmlns:")
}
impl<'o, 'i> Events<'o, 'i> {
fn new(opts: Options<'o>, utf8: &'i [u8], end_names: bool) -> Self {
let mut reader = Reader::from_reader(utf8);
let cfg = reader.config_mut();
// The checks a strict read wants are made here, where the answer can be lenient.
cfg.check_end_names = false;
cfg.allow_unmatched_ends = true;
cfg.allow_dangling_amp = true;
cfg.trim_text_start = false;
cfg.trim_text_end = false;
cfg.expand_empty_elements = false;
Events { reader, opts, end_names, open: Vec::new(), scope: Vec::new(), pending: false, rooted: false, span: 0..0 }
}
/// The bytes of the input the last event was read from. Both events of an empty element
/// have the whole element.
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
fn syntax<T>(&self, msg: impl Into<String>) -> Result<T, Error> {
Err(Error::Syntax { at: self.span.start, msg: msg.into() })
}
fn decode<'b>(&self, bytes: &'b [u8]) -> Result<Cow<'b, str>, Error> {
match std::str::from_utf8(bytes) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(e) if self.opts.strict => Err(Error::Encoding(self.span.start.saturating_add(e.valid_up_to()))),
Err(_) => Ok(String::from_utf8_lossy(bytes)),
}
}
fn unknown(&self, name: &str) -> Result<Option<String>, Error> {
match self.opts.unknown_entity {
UnknownEntity::Drop => Ok(None),
UnknownEntity::Literal => Ok(Some(format!("&{name};"))),
UnknownEntity::Error => Err(Error::UnknownEntity { at: self.span.start, name: name.to_string() }),
}
}
/// The next event, or `None` at the end of the input.
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<Event>, Error> {
if self.pending {
self.pending = false;
return Ok(Some(self.end()));
}
loop {
let from = at(self.reader.buffer_position());
let ev = match self.reader.read_event() {
Ok(ev) => ev,
Err(e) => return Err(Error::Syntax { at: at(self.reader.error_position()), msg: e.to_string() }),
};
self.span = from..at(self.reader.buffer_position());
if let Some(out) = self.step(ev)? {
return Ok(out);
}
}
}
/// `None` to read on, `Some(None)` at the end of the input.
fn step(&mut self, ev: Q<'i>) -> Result<Option<Option<Event>>, Error> {
let event = match ev {
Q::Start(e) => self.start(&e)?,
Q::Empty(e) => {
self.pending = true;
self.start(&e)?
}
Q::End(e) => {
let Some(top) = self.open.last() else {
return if self.opts.strict { self.syntax("an end tag has no start tag") } else { Ok(None) };
};
if self.opts.strict && top.raw != e.name().as_ref() {
return self.syntax(format!("the end tag `{}` does not match its start tag", String::from_utf8_lossy(e.name().as_ref())));
}
self.end()
}
Q::Text(t) => {
if t.is_empty() {
return Ok(None);
}
// Outside the root element only white space may stand (XML 1.0 [1], [27]).
if self.outside_root() && t.iter().any(|b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n')) {
return self.syntax("text outside the root element");
}
Event::Text(line_ends(self.decode(&t)?))
}
Q::CData(_) | Q::GeneralRef(_) if self.outside_root() => return self.syntax("text outside the root element"),
Q::CData(t) => Event::CData(line_ends(self.decode(&t)?)),
Q::GeneralRef(r) => {
let name = self.decode(&r)?;
match entity(&name) {
Some(c) => Event::Text(c.to_string()),
None => match self.unknown(&name)? {
Some(literal) => Event::Text(literal),
None => return Ok(None),
},
}
}
Q::PI(p) => Event::Pi { target: self.decode(p.target())?.into_owned(), data: self.decode(p.content())?.trim_start().to_string() },
Q::Comment(c) => Event::Comment(self.decode(&c)?.into_owned()),
Q::DocType(d) => {
let refuse = match self.opts.doctype {
Doctype::Ignore => false,
// A `[` in a quoted identifier is refused too, which fails safe.
Doctype::RejectInternalSubset => d.contains(&b'['),
Doctype::Reject => true,
};
return if refuse { Err(Error::Doctype) } else { Ok(None) };
}
Q::Decl(_) => return Ok(None),
Q::Eof => {
return if self.opts.strict && !self.open.is_empty() { self.syntax("an element is not closed") } else { Ok(Some(None)) };
}
};
Ok(Some(Some(event)))
}
fn end(&mut self) -> Event {
let (scope, name) = self.open.pop().map_or((0, String::new()), |o| (o.scope, o.name));
self.scope.truncate(scope);
Event::End { name }
}
/// The URI `prefix` is bound to here; `""` is the default namespace.
fn uri(&self, prefix: &str) -> Option<&str> {
Scope { bindings: &self.scope }.uri(prefix)
}
fn element_name(&self, raw: &str) -> String {
match self.opts.names {
Names::AsWritten => raw.to_string(),
Names::Local => local_of(raw).to_string(),
Names::Canonical(canon) => {
let (prefix, local) = raw.split_once(':').unwrap_or(("", raw));
let Some(uri) = self.uri(prefix) else { return local.to_string() };
let listed = canon.prefix(uri);
let parent = self.open.last().map(|o| o.name.as_str());
let chosen = canon.element.and_then(|hook| hook(&NsName { prefix: listed, uri, local, parent })).unwrap_or(listed);
if chosen.is_empty() { local.to_string() } else { format!("{chosen}:{local}") }
}
}
}
fn attr_name(&self, raw: &str) -> String {
match self.opts.names {
Names::AsWritten => raw.to_string(),
Names::Local => local_of(raw).to_string(),
Names::Canonical(canon) => match raw.split_once(':') {
Some((prefix, local)) => match self.uri(prefix).map(|uri| canon.prefix(uri)) {
Some(p) if !p.is_empty() => format!("{p}:{local}"),
_ => local.to_string(),
},
None => raw.to_string(),
},
}
}
fn attr_value(&self, raw: &[u8]) -> Result<String, Error> {
match decode_refs(&self.decode(raw)?, true, self.opts.unknown_entity) {
Ok(v) => Ok(v),
Err(name) => Err(Error::UnknownEntity { at: self.span.start, name }),
}
}
/// Reads the tag's namespace declarations into the scope, so that the tag's own name and
/// attributes resolve against them.
fn declare(&mut self, e: &BytesStart<'_>) -> Result<(), Error> {
let mut attrs = e.attributes();
attrs.with_checks(false);
// The attribute pass that follows counts every attribute; this one only has to stop.
for a in attrs.take(self.opts.limits.max_attrs).flatten() {
let key = a.key.as_ref();
if is_ns_decl(key) {
let prefix = self.decode(key.get(6..).unwrap_or_default())?.into_owned();
let uri = self.attr_value(&a.value)?;
self.scope.push((prefix, uri));
}
}
// Every name is resolved against the scope, so the scope as a whole is bounded too:
// declarations piled up over many levels would make each lookup long.
if self.scope.len() > self.opts.limits.max_attrs {
return Err(Error::TooManyAttrs(self.opts.limits.max_attrs));
}
Ok(())
}
/// In a strict read: is the reader before or after the root element?
fn outside_root(&self) -> bool {
self.opts.strict && self.open.is_empty()
}
fn start(&mut self, e: &BytesStart<'_>) -> Result<Event, Error> {
let limits = self.opts.limits;
if self.outside_root() && self.rooted {
return self.syntax("more than one root element");
}
self.rooted = true;
if self.open.len() >= limits.max_depth {
return Err(Error::TooDeep(limits.max_depth));
}
let canonical = match self.opts.names {
Names::Canonical(c) => Some(c),
_ => None,
};
let scope = self.scope.len();
if canonical.is_some() {
self.declare(e)?;
}
let raw = e.name();
let name = self.element_name(&self.decode(raw.as_ref())?);
let mut out = Vec::new();
let mut attrs = e.attributes();
attrs.with_checks(false);
for (seen, a) in attrs.enumerate() {
if seen >= limits.max_attrs {
return Err(Error::TooManyAttrs(limits.max_attrs));
}
let a = match a {
Ok(a) => a,
Err(err) if self.opts.strict => return self.syntax(err.to_string()),
Err(_) => continue,
};
let key = a.key.as_ref();
let decl = is_ns_decl(key);
if decl && !self.opts.ns_decls {
continue;
}
let key = self.decode(key)?;
let key = if decl { key.into_owned() } else { self.attr_name(&key) };
let mut value = self.attr_value(&a.value)?;
if let Some(hook) = canonical.and_then(|c| c.attr_value)
&& let Some(rewritten) = hook(&name, &key, &value, &Scope { bindings: &self.scope })
{
value = rewritten;
}
out.push((key, value));
}
let keep_name = self.end_names || canonical.is_some_and(|c| c.element.is_some());
self.open.push(Open {
scope,
raw: if self.opts.strict { raw.as_ref().to_vec() } else { Vec::new() },
name: if keep_name { name.clone() } else { String::new() },
});
Ok(Event::Start { name, attrs: out })
}
}
struct Builder<'b, 'o> {
opts: &'b Options<'o>,
split: &'b [&'b str],
stack: Vec<El>,
/// The node and attribute counts when each open element started.
marks: Vec<(usize, usize)>,
kept: usize,
/// Attributes in the elements built so far and not handed out.
attrs: usize,
root: Option<El>,
/// Where the event being handled starts, for errors.
at: usize,
}
impl Builder<'_, '_> {
fn run<E: From<Error>>(&mut self, events: &mut Events<'_, '_>, each: &mut impl FnMut(El) -> Result<(), E>) -> Result<(), E> {
while let Some(ev) = events.next()? {
self.at = events.span().start;
self.event(ev, each)?;
if !self.opts.strict && self.root.is_some() {
break;
}
}
// A lenient read keeps what a truncated document had; a strict one failed above.
while let Some(el) = self.stack.pop() {
self.finish(el, each)?;
}
Ok(())
}
fn count(&mut self) -> Result<(), Error> {
self.kept = self.kept.saturating_add(1);
if self.kept > self.opts.limits.max_nodes { Err(Error::TooManyNodes(self.opts.limits.max_nodes)) } else { Ok(()) }
}
fn push(&mut self, node: Node) -> Result<(), Error> {
if !self.stack.is_empty() {
self.count()?;
}
if let Some(top) = self.stack.last_mut() {
top.children.push(node);
}
Ok(())
}
fn text(&mut self, text: String) -> Result<(), Error> {
match self.stack.last_mut() {
Some(top) => {
if let Some(Node::Text(prev)) = top.children.last_mut() {
prev.push_str(&text);
return Ok(());
}
self.push(Node::Text(text))
}
None if self.opts.strict && !text.trim().is_empty() => Err(Error::Syntax { at: self.at, msg: "text outside the root element".into() }),
None => Ok(()),
}
}
fn event<E: From<Error>>(&mut self, ev: Event, each: &mut impl FnMut(El) -> Result<(), E>) -> Result<(), E> {
match ev {
Event::Start { name, attrs } => {
if self.root.is_some() {
return Err(Error::Syntax { at: self.at, msg: "more than one root element".into() }.into());
}
self.marks.push((self.kept, self.attrs));
self.attrs = self.attrs.saturating_add(attrs.len());
if self.attrs > self.opts.limits.max_attrs_total {
return Err(Error::TooManyAttrsTotal(self.opts.limits.max_attrs_total).into());
}
self.stack.push(El { name, attrs, children: Vec::new() });
}
Event::End { .. } => {
if let Some(el) = self.stack.pop() {
self.finish(el, each)?;
}
}
Event::Text(t) => self.text(t)?,
Event::CData(t) if self.opts.cdata => self.push(Node::CData(t))?,
Event::CData(t) => self.text(t)?,
Event::Pi { target, data } if self.opts.pis => self.push(Node::Pi { target, data })?,
Event::Comment(c) if self.opts.comments => self.push(Node::Comment(c))?,
Event::Pi { .. } | Event::Comment(_) => {}
}
Ok(())
}
/// Attaches a complete element to its parent, hands it out, or makes it the root.
fn finish<E: From<Error>>(&mut self, mut el: El, each: &mut impl FnMut(El) -> Result<(), E>) -> Result<(), E> {
let (mark, attrs_mark) = self.marks.pop().unwrap_or((0, 0));
if self.opts.drop_blank_text && el.children.iter().any(|n| matches!(n, Node::El(_))) {
let is_text = |n: &Node| matches!(n, Node::Text(_));
if el.children.iter().all(|n| !is_text(n) || matches!(n, Node::Text(t) if t.trim().is_empty())) {
let before = el.children.len();
el.children.retain(|n| !is_text(n));
self.kept = self.kept.saturating_sub(before - el.children.len());
}
}
let depth = self.stack.len();
if depth == 0 {
self.count()?;
self.root = Some(el);
} else if depth <= self.opts.split_depth && self.split.contains(&el.name.as_str()) {
each(el)?;
// The element and everything inside it have left the tree.
self.kept = mark;
self.attrs = attrs_mark;
} else {
self.push(Node::El(el))?;
}
Ok(())
}
}
/// The input as UTF-8 text for the reader: a UTF-8 byte-order mark is removed, and UTF-16 (with
/// a byte-order mark, or recognisable without one by the zero bytes of its first two
/// characters) is transcoded. An unpaired surrogate becomes U+FFFD and a trailing odd byte is
/// dropped. The result is at most 1.5 times the input. Any other input is returned as it is;
/// the encoding an XML declaration names is not consulted.
pub fn to_utf8(bytes: &[u8]) -> Cow<'_, [u8]> {
let (little_endian, body) = match bytes {
[0xEF, 0xBB, 0xBF, rest @ ..] => return Cow::Borrowed(rest),
[0xFF, 0xFE, rest @ ..] => (true, rest),
[0xFE, 0xFF, rest @ ..] => (false, rest),
[a, 0, b, 0, ..] if *a != 0 && *b != 0 => (true, bytes),
[0, a, 0, b, ..] if *a != 0 && *b != 0 => (false, bytes),
_ => return Cow::Borrowed(bytes),
};
let units = body.as_chunks::<2>().0.iter().map(|pair| if little_endian { u16::from_le_bytes(*pair) } else { u16::from_be_bytes(*pair) });
Cow::Owned(char::decode_utf16(units).map(|c| c.unwrap_or(char::REPLACEMENT_CHARACTER)).collect::<String>().into_bytes())
}
/// The character a reference names, without its `&` and `;`: one of the five predefined
/// entities (`amp`, `lt`, `gt`, `quot`, `apos`) or a numeric character reference (`#65`,
/// `#x41`, zero-padded or not). `None` for anything else, for a number that is not a character
/// XML can carry, and for a number with a sign.
pub fn entity(name: &str) -> Option<char> {
let number = match name {
"amp" => return Some('&'),
"lt" => return Some('<'),
"gt" => return Some('>'),
"quot" => return Some('"'),
"apos" => return Some('\''),
_ => name.strip_prefix('#')?,
};
let (digits, radix) = match number.strip_prefix(['x', 'X']) {
Some(hex) => (hex, 16),
None => (number, 10),
};
if digits.is_empty() {
return None;
}
let code = digits.chars().try_fold(0u32, |n, c| n.checked_mul(radix)?.checked_add(c.to_digit(radix)?))?;
char::from_u32(code).filter(|c| is_xml_char(*c))
}
/// Resolves the references in `s`, and with `attr` turns literal whitespace into spaces as
/// attribute-value normalisation does. An `&` that starts no reference stays as it is. `Err` is
/// the name of an unknown entity under [`UnknownEntity::Error`].
fn decode_refs(s: &str, attr: bool, unknown: UnknownEntity) -> Result<String, String> {
let special = |c: char| c == '&' || (attr && matches!(c, '\t' | '\n' | '\r'));
let mut out = String::with_capacity(s.len());
let mut rest = s;
while let Some(i) = rest.find(special) {
out.push_str(rest.get(..i).unwrap_or_default());
let tail = rest.get(i..).unwrap_or_default();
let Some(after) = tail.strip_prefix('&') else {
out.push(' ');
rest = tail.strip_prefix("\r\n").or_else(|| tail.get(1..)).unwrap_or_default();
continue;
};
// Only the bytes a name or a number can have are scanned, so a run of `&` costs one
// look each however far away the next `;` is.
let len = after.bytes().take_while(|b| b.is_ascii_alphanumeric() || matches!(b, b'#' | b'_' | b'-' | b'.' | b':')).count();
let name = after.get(..len).unwrap_or_default();
if len == 0 || after.as_bytes().get(len) != Some(&b';') {
out.push('&');
rest = after;
continue;
}
match (entity(name), unknown) {
(Some(c), _) => out.push(c),
(None, UnknownEntity::Drop) => {}
(None, UnknownEntity::Literal) => {
out.push('&');
out.push_str(name);
out.push(';');
}
(None, UnknownEntity::Error) => return Err(name.to_string()),
}
rest = after.get(len + 1..).unwrap_or_default();
}
out.push_str(rest);
Ok(out)
}
/// `s` with its predefined and numeric references resolved ([`entity`]). Anything else that
/// starts with `&` stays as it is.
pub fn unescape(s: &str) -> String {
decode_refs(s, false, UnknownEntity::Literal).unwrap_or_else(|_| s.to_string())
}