use alloc::borrow::{Cow, ToOwned};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use super::event::BorrowedEvent;
use crate::error::{Error, ErrorKind, Result};
use crate::parser::{Parser, Run, Version};
use crate::tree::{Chars, ExpandedName};
use crate::{Document, Limits};
#[derive(Debug)]
pub(super) struct Carried {
document: Option<Box<Document>>,
namespaces: crate::parser::Namespaces,
names: alloc::collections::BTreeMap<String, Vec<u32>>,
version: Version,
pub(super) limits: Limits,
depth: usize,
dtd: Option<crate::dtd::Dtd>,
standalone: bool,
entity_budget: usize,
entity_cache: alloc::collections::BTreeSet<(String, u64)>,
}
impl Carried {
pub(super) fn new(limits: Limits) -> Self {
Self {
document: Some(Box::new(Document::placeholder())),
namespaces: crate::parser::Namespaces::default(),
names: alloc::collections::BTreeMap::new(),
version: Version::V10,
limits,
depth: 0,
dtd: None,
standalone: false,
entity_budget: limits.max_entity_expansion,
entity_cache: alloc::collections::BTreeSet::new(),
}
}
pub(super) fn declare(&mut self, version: Version, standalone: bool) {
self.version = version;
self.standalone = standalone;
}
fn lend<'a>(
&mut self,
text: &'a str,
pos: usize,
external: &'a dyn crate::external::ExternalSource,
) -> Parser<'a> {
Parser {
input: text,
bytes: text.as_bytes(),
pos,
doc: self
.document
.take()
.unwrap_or_else(|| Box::new(Document::placeholder())),
name_index: core::mem::take(&mut self.names),
external,
ns: core::mem::take(&mut self.namespaces),
depth: self.depth,
limits: self.limits,
dtd: self.dtd.take(),
version: self.version,
entity_budget: self.entity_budget,
entity_depth: 0,
standalone: self.standalone,
entity_cache: core::mem::take(&mut self.entity_cache),
}
}
fn reclaim(&mut self, parser: Parser<'_>) {
self.depth = parser.depth;
self.document = Some(parser.doc);
self.names = parser.name_index;
self.namespaces = parser.ns;
self.dtd = parser.dtd;
self.entity_budget = parser.entity_budget;
self.entity_cache = parser.entity_cache;
}
}
#[derive(Debug)]
pub(super) struct Cursor {
pub(super) pos: usize,
pub(super) open: Vec<(String, ExpandedName)>,
started: bool,
seen_root: bool,
prolog_doctypes: bool,
nodes: NodeTally,
}
impl Cursor {
pub(super) const fn new() -> Self {
Self {
pos: 0,
open: Vec::new(),
started: false,
seen_root: false,
prolog_doctypes: true,
nodes: NodeTally::new(),
}
}
}
#[derive(Debug)]
struct NodeTally {
count: usize,
}
impl NodeTally {
const fn new() -> Self {
Self { count: 1 }
}
fn add(&mut self, nodes: usize, limits: Limits, at: usize) -> Result<()> {
limits.check_nodes(self.count, nodes, at)?;
self.count += nodes;
Ok(())
}
}
#[derive(Debug)]
pub(super) struct Pending {
name: ExpandedName,
prefix: Option<String>,
}
impl Pending {
pub(super) fn into_event<'a>(self) -> BorrowedEvent<'a> {
BorrowedEvent::EndElement {
name: self.name,
prefix: self.prefix.map(Cow::Owned),
}
}
}
pub(super) enum Scanned<'a> {
Event(BorrowedEvent<'a>),
SelfClosed(BorrowedEvent<'a>, Pending),
Eof,
}
fn with_parser<'a, T>(
text: &'a str,
carried: &mut Carried,
cursor: &mut Cursor,
external: &'a dyn crate::external::ExternalSource,
f: impl FnOnce(&mut Parser<'a>, &mut Cursor) -> Result<T>,
) -> Result<T> {
let mut parser = carried.lend(text, cursor.pos, external);
let result = f(&mut parser, cursor);
cursor.pos = parser.pos;
carried.reclaim(parser);
result
}
pub(super) fn scan<'a>(
text: &'a str,
carried: &mut Carried,
cursor: &mut Cursor,
external: &'a dyn crate::external::ExternalSource,
) -> Result<Scanned<'a>> {
with_parser(text, carried, cursor, external, one_event)
}
pub(super) fn skip_eventless<'a>(
text: &'a str,
carried: &mut Carried,
cursor: &mut Cursor,
external: &'a dyn crate::external::ExternalSource,
) -> Result<bool> {
if cursor.started && !cursor.open.is_empty() {
return Ok(false);
}
with_parser(text, carried, cursor, external, eventless_step)
}
fn eventless_step(
parser: &mut Parser<'_>,
cursor: &mut Cursor,
) -> Result<bool> {
if !cursor.started {
parser.limits.check_nodes(0, 1, 0)?;
declaration(parser)?;
cursor.started = true;
return Ok(true);
}
if !cursor.open.is_empty() {
return Ok(false);
}
parser.skip_whitespace();
if parser.starts_with("<!DOCTYPE") {
doctype(parser, cursor)?;
return Ok(true);
}
if parser.pos < parser.bytes.len() {
cursor.prolog_doctypes = false;
}
Ok(false)
}
fn one_event<'a>(
parser: &mut Parser<'a>,
cursor: &mut Cursor,
) -> Result<Scanned<'a>> {
if cursor.open.is_empty() {
return outside_root(parser, cursor);
}
if parser.pos >= parser.bytes.len() {
return Err(Error::new(ErrorKind::UnexpectedEof, parser.pos));
}
if !parser.peek_is(b'<') || parser.starts_with("<![CDATA[") {
let text = char_data(parser)?;
tally_text(&text, &mut cursor.nodes, parser)?;
return Ok(Scanned::Event(text));
}
if parser.starts_with("</") {
return end_tag(parser, &mut cursor.open);
}
if let Some(event) = misc(parser, &mut cursor.nodes)? {
return Ok(Scanned::Event(event));
}
start_tag(parser, cursor)
}
fn tally_text(
event: &BorrowedEvent<'_>,
nodes: &mut NodeTally,
parser: &Parser<'_>,
) -> Result<()> {
if matches!(event, BorrowedEvent::Text(t) if !t.is_empty()) {
nodes.add(1, parser.limits, parser.pos)?;
}
Ok(())
}
fn declaration(parser: &mut Parser<'_>) -> Result<()> {
let (input, bytes) = (parser.input, parser.bytes);
let end = parser.pos + declaration_end(&input[parser.pos..]);
parser.input = &input[..end];
parser.bytes = &bytes[..end];
let outcome = parser.skip_prolog();
parser.input = input;
parser.bytes = bytes;
outcome
}
fn declaration_end(input: &str) -> usize {
let lead =
input.len() - input.trim_start_matches([' ', '\t', '\r', '\n']).len();
let Some(rest) = input[lead..].strip_prefix("<?xml") else {
return 0;
};
if rest
.bytes()
.next()
.is_some_and(|b| crate::parser::is_name_char(char::from(b)))
{
return 0;
}
rest.find("?>")
.map_or(input.len(), |i| lead + "<?xml".len() + i + "?>".len())
}
fn outside_root<'a>(
parser: &mut Parser<'a>,
cursor: &mut Cursor,
) -> Result<Scanned<'a>> {
parser.skip_whitespace();
if parser.pos >= parser.bytes.len() {
return at_end(cursor.seen_root, parser.pos);
}
if !parser.peek_is(b'<') {
return Err(Error::new(ErrorKind::TrailingContent, parser.pos));
}
if let Some(event) = misc(parser, &mut cursor.nodes)? {
return Ok(Scanned::Event(event));
}
if parser.starts_with("</") {
let name = parser.peek_end_tag_name();
return Err(Error::new(ErrorKind::UnexpectedEndTag(name), parser.pos));
}
if cursor.seen_root {
return Err(Error::new(ErrorKind::TrailingContent, parser.pos));
}
cursor.seen_root = true;
start_tag(parser, cursor)
}
fn at_end(seen_root: bool, pos: usize) -> Result<Scanned<'static>> {
if seen_root {
Ok(Scanned::Eof)
} else {
Err(Error::new(ErrorKind::NoRootElement, pos))
}
}
fn doctype(parser: &mut Parser<'_>, cursor: &Cursor) -> Result<()> {
if !cursor.prolog_doctypes && (cursor.seen_root || parser.dtd.is_some()) {
return Err(Error::new(ErrorKind::TrailingContent, parser.pos));
}
parser.skip_doctype()
}
fn misc<'a>(
parser: &mut Parser<'a>,
nodes: &mut NodeTally,
) -> Result<Option<BorrowedEvent<'a>>> {
if parser.starts_with("<!--") || parser.starts_with("<?") {
nodes.add(1, parser.limits, parser.pos)?;
}
if parser.starts_with("<!--") {
let c = parser.parse_comment()?;
return Ok(Some(BorrowedEvent::Comment(chars_to_cow(parser, c))));
}
if parser.starts_with("<?") {
let (target, data) = parser.parse_pi()?;
return Ok(Some(BorrowedEvent::ProcessingInstruction {
target: chars_to_cow(parser, target),
data: chars_to_cow(parser, data),
}));
}
Ok(None)
}
fn char_data<'a>(parser: &mut Parser<'a>) -> Result<BorrowedEvent<'a>> {
let mut run = Run::default();
loop {
if parser.starts_with("<![CDATA[") {
parser.parse_cdata(&mut run)?;
} else if parser.pos < parser.bytes.len() && !parser.peek_is(b'<') {
parser.parse_text_run(&mut run)?;
} else {
break;
}
}
if parser.limits.max_text_length.is_some_and(|m| run.len() > m) {
return Err(Error::new(ErrorKind::TextTooLong, parser.pos));
}
Ok(BorrowedEvent::Text(run_to_cow(parser.input, &run)))
}
fn end_tag<'a>(
parser: &mut Parser<'a>,
open: &mut Vec<(String, ExpandedName)>,
) -> Result<Scanned<'a>> {
let start = parser.pos;
parser.pos += 2;
let close = parser.parse_name()?;
parser.skip_whitespace();
if !parser.peek_is(b'>') {
return Err(Error::new(ErrorKind::Unterminated("end tag"), start));
}
parser.pos += 1;
let (expected, name) = open.pop().expect("inside an element");
if close != expected {
return Err(Error::new(
ErrorKind::MismatchedEndTag {
expected,
found: close.to_owned(),
},
start,
));
}
parser.ns.pop_scope();
parser.depth -= 1;
Ok(Scanned::Event(BorrowedEvent::EndElement {
name,
prefix: prefix_from(expected),
}))
}
fn prefix_from(mut qname: String) -> Option<Cow<'static, str>> {
let colon = qname.find(':')?;
qname.truncate(colon);
Some(Cow::Owned(qname))
}
fn prefix_of(qname: &str) -> Option<&str> {
qname.split_once(':').map(|(prefix, _)| prefix)
}
fn start_tag<'a>(
parser: &mut Parser<'a>,
cursor: &mut Cursor,
) -> Result<Scanned<'a>> {
if parser.depth >= parser.limits.max_depth {
return Err(Error::new(ErrorKind::DepthLimitExceeded, parser.pos));
}
let tag = parser.scan_start_tag()?;
let at = tag.tag_start;
let attributes = attributes(parser, tag.raw_attrs, tag.qname, at)?;
let is_root = parser.depth == 0;
cursor.nodes.add(
1 + attributes.len() + tag.declared.len() + usize::from(is_root),
parser.limits,
parser.pos,
)?;
let name = resolve(parser, tag.qname, true, at)?;
let start = BorrowedEvent::StartElement {
name: name.clone(),
prefix: prefix_of(tag.qname).map(Cow::Borrowed),
attributes,
namespaces: namespaces(tag.declared),
};
if tag.self_closing {
parser.ns.pop_scope();
let pending = Pending {
name,
prefix: prefix_of(tag.qname).map(ToOwned::to_owned),
};
return Ok(Scanned::SelfClosed(start, pending));
}
parser.depth += 1;
cursor.open.push((tag.qname.to_owned(), name));
Ok(Scanned::Event(start))
}
fn namespaces(
declared: Vec<(String, String)>,
) -> Vec<(Option<String>, String)> {
declared
.into_iter()
.map(|(prefix, uri)| ((!prefix.is_empty()).then_some(prefix), uri))
.collect()
}
fn resolve(
parser: &mut Parser<'_>,
qname: &str,
is_element: bool,
at: usize,
) -> Result<ExpandedName> {
let id = parser.intern_qname(qname, is_element, at)?;
Ok(parser
.doc
.name(id)
.cloned()
.unwrap_or_else(|| ExpandedName::local(qname)))
}
fn attributes<'a>(
parser: &mut Parser<'a>,
raw_attrs: Vec<(&'a str, Run)>,
element: &str,
at: usize,
) -> Result<Vec<(ExpandedName, Cow<'a, str>)>> {
let mut attributes = Vec::with_capacity(raw_attrs.len());
for (raw, value) in raw_attrs {
if raw == "xmlns" || raw.starts_with("xmlns:") {
continue;
}
let name = resolve(parser, raw, false, at)?;
if attributes
.iter()
.any(|(seen, _): &(ExpandedName, _)| *seen == name)
{
return Err(Error::new(
ErrorKind::DuplicateAttribute(raw.to_owned()),
at,
));
}
let tokenized = parser
.dtd
.as_ref()
.is_some_and(|d| d.is_tokenized_attribute(element, raw));
attributes
.push((name, attribute_value(parser.input, &value, tokenized)));
}
Ok(attributes)
}
fn attribute_value<'a>(
input: &'a str,
value: &Run,
tokenized: bool,
) -> Cow<'a, str> {
let text = value.as_str(input);
if tokenized {
let collapsed = collapse(text);
if collapsed != text {
return Cow::Owned(collapsed);
}
}
match value.as_span() {
Some((start, len)) => Cow::Borrowed(&input[start..start + len]),
None => Cow::Owned(text.to_owned()),
}
}
fn collapse(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for part in text.split(' ').filter(|p| !p.is_empty()) {
if !out.is_empty() {
out.push(' ');
}
out.push_str(part);
}
out
}
fn run_to_cow<'a>(input: &'a str, run: &Run) -> Cow<'a, str> {
match run.as_span() {
Some((start, len)) => Cow::Borrowed(&input[start..start + len]),
None => Cow::Owned(run.as_str(input).to_owned()),
}
}
fn chars_to_cow<'a>(parser: &Parser<'a>, c: Chars) -> Cow<'a, str> {
match c {
Chars::Span(s, l) => {
let start = s as usize;
let end = (s + l) as usize;
Cow::Borrowed(parser.input.get(start..end).unwrap_or_default())
}
Chars::Expanded(_) => Cow::Owned(parser.owned(c).to_owned()),
}
}