From fd168c202cb4a3333e576208e1bc53a3d7ecae4c Mon Sep 17 00:00:00 2001 From: Felix Bernhardt Date: Thu, 19 Feb 2026 09:35:03 +0100 Subject: [PATCH 1/3] parse name_len directly when parsing start elements --- src/parser/fast_element.rs | 243 ++++++++++++++++++++++++++++++++++ src/parser/mod.rs | 2 + src/reader/buffered_reader.rs | 41 +++++- src/reader/mod.rs | 22 ++- src/reader/slice_reader.rs | 18 ++- src/reader/state.rs | 6 +- 6 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 src/parser/fast_element.rs diff --git a/src/parser/fast_element.rs b/src/parser/fast_element.rs new file mode 100644 index 00000000..58833504 --- /dev/null +++ b/src/parser/fast_element.rs @@ -0,0 +1,243 @@ +//! Contains a parser for an XML element. + +use crate::errors::SyntaxError; + +/// A parser that search a `>` symbol in the slice outside of quoted regions. +/// +/// The parser considers two quoted regions: a double-quoted (`"..."`) and +/// a single-quoted (`'...'`) region. Matches found inside those regions are not +/// considered as results. Each region starts and ends by its quote symbol, +/// which cannot be escaped (but can be encoded as XML character entity or named +/// entity. Anyway, that encoding does not contain literal quotes). +/// +/// To use a parser create an instance of parser and [`feed`] data into it. +/// After successful search the parser will return [`Some`] with position of +/// found symbol. If search is unsuccessful, a [`None`] will be returned. You +/// typically would expect positive result of search, so that you should feed +/// new data until you get it. +/// +/// NOTE: after successful match the parser does not returned to the initial +/// state and should not be used anymore. Create a new parser if you want to perform +/// new search. +/// +/// # Example +/// +/// ``` +/// # use pretty_assertions::assert_eq; +/// use quick_xml::parser::{ElementParser, Parser}; +/// +/// let mut parser = ElementParser::default(); +/// +/// // Parse `and the text follow...` +/// // splitted into three chunks +/// assert_eq!(parser.feed(b"and the text follow..."), Some(8)); +/// // ^ ^ +/// // 0 8 +/// ``` +/// +/// [`feed`]: Self::feed() +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FastElementParser { + /// The initial state, inside the Tag name. + /// Contains the current length of the tag name. + Tag(usize), + /// The name fast completely parsed. Now look for the '>'. + Attributes(usize, AttributeParser), +} + +/// The internal state of the attribute parser. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AttributeParser { + /// The initial state, not within ' or ". + Outside, + /// Inside a single-quoted region (`'...'`). + /// Contains the length of the tag name. + SingleQ, + /// Inside a double-quoted region (`"..."`). + /// Contains the length of the tag name. + DoubleQ, +} + +impl FastElementParser { + /// Returns the length of the name and the number of consumed bytes of the current call or `None` if `>` was not found in `bytes`. + /// A return-value of None implies, that the full butes array was consumed. + /// Assumes, that the initial '<' or ' Option<(usize, usize)> { + // The number of bytes consumed in the current feed iteration. + let mut consumed: usize = 0; + + let (name_len, mut attr_parser) = 'name_len: { + match *self { + Self::Tag(name_len) => { + for i in 0..bytes.len() { + let byte = bytes[i]; + + if matches!(byte, b' ' | b'\r' | b'\n' | b'\t' | b'/') { + // TODO(flxbe): Somehow make sure, that the only expect a '>' after the '/'. + let name_len = name_len + i; + let attr_parser = AttributeParser::Outside; + *self = Self::Attributes(name_len, attr_parser); + + consumed += i; + break 'name_len (name_len, attr_parser); + } else if byte == b'>' { + return Some((name_len + i, consumed + i)); + } + } + + *self = Self::Tag(name_len + bytes.len()); + return None; + } + Self::Attributes(name_len, attr_parser) => (name_len, attr_parser), + } + }; + + let new_data = &bytes[consumed..]; + for i in memchr::memchr3_iter(b'>', b'\'', b'"', new_data) { + attr_parser = match (attr_parser, new_data[i]) { + // only allowed to match `>` while we are in state `Outside` + (AttributeParser::Outside, b'>') => return Some((name_len, consumed + i)), + (AttributeParser::Outside, b'\'') => AttributeParser::SingleQ, + (AttributeParser::Outside, b'"') => AttributeParser::DoubleQ, + + // the only end_byte that gets us out if the same character + (AttributeParser::SingleQ, b'\'') | (AttributeParser::DoubleQ, b'"') => { + AttributeParser::Outside + } + + // all other bytes: no state change + _ => continue, + }; + } + + *self = Self::Attributes(name_len, attr_parser); + None + } + + /// Return the correct EOF SyntaxError based on the current internal state. + #[inline] + pub fn eof_error(self, _content: &[u8]) -> SyntaxError { + match self { + Self::Tag(_) => SyntaxError::UnclosedTag, + Self::Attributes(_, attr) => match attr { + AttributeParser::Outside => SyntaxError::UnclosedTag, + AttributeParser::SingleQ => SyntaxError::UnclosedSingleQuotedAttributeValue, + AttributeParser::DoubleQ => SyntaxError::UnclosedDoubleQuotedAttributeValue, + }, + } + } +} + +impl Default for FastElementParser { + #[inline] + fn default() -> Self { + Self::Tag(0) + } +} + +#[test] +fn parse_all() { + use pretty_assertions::assert_eq; + + fn parse_input(input: &[u8], name_len: usize) { + let mut parser = FastElementParser::default(); + + assert_eq!(parser.feed(input), Some((name_len, input.len() - 1))); + } + + parse_input(b"tag key='value' key=\"value\">", 3); + parse_input(b"tag>", 3); + parse_input(b"tag />", 3); + parse_input(b"tag/>", 3); +} + +#[test] +fn parse_internal_state() { + use pretty_assertions::assert_eq; + + let mut parser = FastElementParser::default(); + assert_eq!(parser.feed(b""), None); + assert_eq!(parser, FastElementParser::Tag(0)); + + // start feeding the tag + assert_eq!(parser.feed(b"tag"), None); + assert_eq!(parser, FastElementParser::Tag(3)); + + // Finish the tag parsing after seeing some whitespace + assert_eq!(parser.feed(b" "), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::Outside) + ); + + // Remain in state when no progress is made + assert_eq!(parser.feed(b""), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::Outside) + ); + assert_eq!(parser.feed(b"some random content"), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::Outside) + ); + + // Handle single qoute + assert_eq!(parser.feed(b"\'"), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::SingleQ) + ); + + // Remain in state when no progress is made + assert_eq!(parser.feed(b""), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::SingleQ) + ); + assert_eq!(parser.feed(b"some random content \">"), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::SingleQ) + ); + + // Close single quote + assert_eq!(parser.feed(b"'"), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::Outside) + ); + + // Handle double qoute + assert_eq!(parser.feed(b"\""), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::DoubleQ) + ); + + // Remain in state when no progress is made + assert_eq!(parser.feed(b""), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::DoubleQ) + ); + assert_eq!(parser.feed(b"some random content '>"), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::DoubleQ) + ); + + // Close double quote + assert_eq!(parser.feed(b"\""), None); + assert_eq!( + parser, + FastElementParser::Attributes(3, AttributeParser::Outside) + ); + + assert_eq!(parser.feed(b">"), Some((3, 0))); +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 382ac855..9a3a4106 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5,11 +5,13 @@ use crate::errors::SyntaxError; mod comment; mod dtd; mod element; +mod fast_element; mod pi; pub use comment::CommentParser; pub(crate) use dtd::DtdParser; pub use element::ElementParser; +pub use fast_element::FastElementParser; pub use pi::PiParser; /// Used to decouple reading of data from data source and parsing XML structure from it. diff --git a/src/reader/buffered_reader.rs b/src/reader/buffered_reader.rs index 27aa74c0..5f05a530 100644 --- a/src/reader/buffered_reader.rs +++ b/src/reader/buffered_reader.rs @@ -8,7 +8,7 @@ use std::path::Path; use crate::errors::{Error, Result}; use crate::events::Event; use crate::name::QName; -use crate::parser::Parser; +use crate::parser::{FastElementParser, Parser}; use crate::reader::{BangType, ReadRefResult, ReadTextResult, Reader, Span, XmlSource}; use crate::utils::is_whitespace; @@ -318,6 +318,45 @@ macro_rules! impl_buffered_source { }; } } + + #[inline] + $($async)? fn read_start_element<'i>(&mut self, buf: &'i mut Vec, position: &mut u64) -> Result<(usize, &'i [u8])> { + let mut parser = FastElementParser::default(); + let mut read = 0; + let start = buf.len(); + loop { + let available = match self $(.$reader)? .fill_buf() $(.$await)? { + Ok(n) if n.is_empty() => break, + Ok(n) => n, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => { + *position += read; + return Err(Error::Io(e.into())); + } + }; + + if let Some((name_len, consumed)) = parser.feed(available) { + buf.extend_from_slice(&available[..consumed]); + + // +1 for `>` which we do not include + self $(.$reader)? .consume(consumed + 1); + read += consumed as u64 + 1; + + *position += read; + return Ok((name_len, &buf[start..])); + } + + // The `>` symbol not yet found, continue reading + buf.extend_from_slice(available); + + let used = available.len(); + self $(.$reader)? .consume(used); + read += used as u64; + } + + *position += read; + Err(Error::Syntax(parser.eof_error(&buf[start..]))) + } }; } diff --git a/src/reader/mod.rs b/src/reader/mod.rs index e227e189..0ec5ed12 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -459,10 +459,10 @@ macro_rules! read_until_close { }, // `<...` - opening or self-closed tag Ok(Some(_)) => match $reader - .read_with(ElementParser::Outside, $buf, &mut $self.state.offset) + .read_start_element($buf, &mut $self.state.offset) $(.$await)? { - Ok(bytes) => Ok($self.state.emit_start(bytes)), + Ok((name_len, bytes)) => Ok($self.state.emit_start(name_len, bytes)), Err(e) => { // We want to report error at `<`, but offset was increased, // so return it back (-1 for `<`) @@ -1147,6 +1147,24 @@ trait XmlSource<'r, B> { /// Return one character without consuming it, so that future `read_*` calls /// will still include it. On EOF, return `None`. fn peek_one(&mut self) -> io::Result>; + + /// Read input until start element is finished. + /// + /// This method expect that start sequence of a parser already was read. + /// + /// Returns a tuple of the length of the tag name and a slice of data read up to the end of the thing being parsed. + /// The end of thing and the returned content is determined by the used parser. + /// + /// If input (`Self`) is exhausted and no bytes was read, or if the specified + /// parser could not find the ending sequence of the thing, returns `SyntaxError`. + /// + /// # Parameters + /// - `buf`: Buffer that could be filled from an input (`Self`) and + /// from which [events] could borrow their data + /// - `position`: Will be increased by amount of bytes consumed + /// + /// [events]: crate::events::Event + fn read_start_element(&mut self, buf: B, position: &mut u64) -> Result<(usize, &'r [u8]), Error>; } /// Possible elements started with ` XmlSource<'a, ()> for &'a [u8] { fn peek_one(&mut self) -> io::Result> { Ok(self.first().copied()) } + + #[inline] + fn read_start_element(&mut self, _buf: (), position: &mut u64) -> Result<(usize, &'a [u8])> { + let mut parser = FastElementParser::default(); + + if let Some((name_len, consumed)) = parser.feed(self) { + // +1 for `>` which we do not include + *position += consumed as u64 + 1; + let bytes = &self[..consumed]; + *self = &self[consumed + 1..]; + return Ok((name_len, bytes)); + } + + *position += self.len() as u64; + Err(Error::Syntax(parser.eof_error(self))) + } } #[cfg(test)] diff --git a/src/reader/state.rs b/src/reader/state.rs index ff08272d..555a8231 100644 --- a/src/reader/state.rs +++ b/src/reader/state.rs @@ -283,10 +283,10 @@ impl ReaderState { /// /// # Parameters /// - `content`: Content of a tag between `<` and `>` - pub fn emit_start<'b>(&mut self, content: &'b [u8]) -> Event<'b> { + pub fn emit_start<'b>(&mut self, name_len: usize, content: &'b [u8]) -> Event<'b> { if let Some(content) = content.strip_suffix(b"/") { // This is self-closed tag `` - let event = BytesStart::wrap(content, name_len(content), self.decoder()); + let event = BytesStart::wrap(content, name_len, self.decoder()); if self.config.expand_empty_elements { self.state = ParseState::InsideEmpty; @@ -297,7 +297,7 @@ impl ReaderState { Event::Empty(event) } } else { - let event = BytesStart::wrap(content, name_len(content), self.decoder()); + let event = BytesStart::wrap(content, name_len, self.decoder()); // #514: Always store names event when .check_end_names == false, // because checks can be temporary disabled and when they would be From 2fa8b7f852c8efcf4f4ed0cc3a698d1fc511f658 Mon Sep 17 00:00:00 2001 From: Felix Bernhardt Date: Thu, 19 Feb 2026 11:00:31 +0100 Subject: [PATCH 2/3] rename new parser --- src/parser/mod.rs | 4 +- .../{fast_element.rs => start_element.rs} | 45 +++++++++---------- src/reader/buffered_reader.rs | 4 +- src/reader/slice_reader.rs | 4 +- 4 files changed, 28 insertions(+), 29 deletions(-) rename src/parser/{fast_element.rs => start_element.rs} (84%) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9a3a4106..aa101408 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5,13 +5,13 @@ use crate::errors::SyntaxError; mod comment; mod dtd; mod element; -mod fast_element; +mod start_element; mod pi; pub use comment::CommentParser; pub(crate) use dtd::DtdParser; pub use element::ElementParser; -pub use fast_element::FastElementParser; +pub use start_element::StartElementParser; pub use pi::PiParser; /// Used to decouple reading of data from data source and parsing XML structure from it. diff --git a/src/parser/fast_element.rs b/src/parser/start_element.rs similarity index 84% rename from src/parser/fast_element.rs rename to src/parser/start_element.rs index d61af2fa..4164c61e 100644 --- a/src/parser/fast_element.rs +++ b/src/parser/start_element.rs @@ -11,7 +11,8 @@ use crate::errors::SyntaxError; /// entity. Anyway, that encoding does not contain literal quotes). /// /// To use a parser create an instance of parser and [`feed`] data into it. -/// After successful search the parser will return [`Some`] with position of +/// After successful search the parser will return [`Some`] with the length +/// of the element name and the position of /// found symbol. If search is unsuccessful, a [`None`] will be returned. You /// typically would expect positive result of search, so that you should feed /// new data until you get it. @@ -41,7 +42,7 @@ use crate::errors::SyntaxError; /// /// [`feed`]: Self::feed() #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum FastElementParser { +pub enum StartElementParser { /// The initial state, inside the Tag name. /// Contains the current length of the tag name. Tag(usize), @@ -55,17 +56,15 @@ pub enum AttributeParser { /// The initial state, not within ' or ". Outside, /// Inside a single-quoted region (`'...'`). - /// Contains the length of the tag name. SingleQ, /// Inside a double-quoted region (`"..."`). - /// Contains the length of the tag name. DoubleQ, } -impl FastElementParser { +impl StartElementParser { /// Returns the length of the name and the number of consumed bytes of the current call or `None` if `>` was not found in `bytes`. - /// A return-value of None implies, that the full butes array was consumed. - /// Assumes, that the initial '<' or are already consumed. + /// A return-value of None implies, that the full bytes array was consumed. + /// Assumes, that the initial '<' is already consumed. #[inline] pub fn feed(&mut self, bytes: &[u8]) -> Option<(usize, usize)> { // The number of bytes consumed in the current feed iteration. @@ -133,7 +132,7 @@ impl FastElementParser { } } -impl Default for FastElementParser { +impl Default for StartElementParser { #[inline] fn default() -> Self { Self::Tag(0) @@ -145,7 +144,7 @@ fn parse_all() { use pretty_assertions::assert_eq; fn parse_input(input: &[u8], name_len: usize) { - let mut parser = FastElementParser::default(); + let mut parser = StartElementParser::default(); assert_eq!(parser.feed(input), Some((name_len, input.len() - 1))); } @@ -160,83 +159,83 @@ fn parse_all() { fn parse_internal_state() { use pretty_assertions::assert_eq; - let mut parser = FastElementParser::default(); + let mut parser = StartElementParser::default(); assert_eq!(parser.feed(b""), None); - assert_eq!(parser, FastElementParser::Tag(0)); + assert_eq!(parser, StartElementParser::Tag(0)); // start feeding the tag assert_eq!(parser.feed(b"tag"), None); - assert_eq!(parser, FastElementParser::Tag(3)); + assert_eq!(parser, StartElementParser::Tag(3)); // Finish the tag parsing after seeing some whitespace assert_eq!(parser.feed(b" "), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::Outside) + StartElementParser::Attributes(3, AttributeParser::Outside) ); // Remain in state when no progress is made assert_eq!(parser.feed(b""), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::Outside) + StartElementParser::Attributes(3, AttributeParser::Outside) ); assert_eq!(parser.feed(b"some random content"), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::Outside) + StartElementParser::Attributes(3, AttributeParser::Outside) ); // Handle single qoute assert_eq!(parser.feed(b"\'"), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::SingleQ) + StartElementParser::Attributes(3, AttributeParser::SingleQ) ); // Remain in state when no progress is made assert_eq!(parser.feed(b""), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::SingleQ) + StartElementParser::Attributes(3, AttributeParser::SingleQ) ); assert_eq!(parser.feed(b"some random content \">"), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::SingleQ) + StartElementParser::Attributes(3, AttributeParser::SingleQ) ); // Close single quote assert_eq!(parser.feed(b"'"), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::Outside) + StartElementParser::Attributes(3, AttributeParser::Outside) ); // Handle double qoute assert_eq!(parser.feed(b"\""), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::DoubleQ) + StartElementParser::Attributes(3, AttributeParser::DoubleQ) ); // Remain in state when no progress is made assert_eq!(parser.feed(b""), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::DoubleQ) + StartElementParser::Attributes(3, AttributeParser::DoubleQ) ); assert_eq!(parser.feed(b"some random content '>"), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::DoubleQ) + StartElementParser::Attributes(3, AttributeParser::DoubleQ) ); // Close double quote assert_eq!(parser.feed(b"\""), None); assert_eq!( parser, - FastElementParser::Attributes(3, AttributeParser::Outside) + StartElementParser::Attributes(3, AttributeParser::Outside) ); assert_eq!(parser.feed(b">"), Some((3, 0))); diff --git a/src/reader/buffered_reader.rs b/src/reader/buffered_reader.rs index ce2fc515..b4bb58ea 100644 --- a/src/reader/buffered_reader.rs +++ b/src/reader/buffered_reader.rs @@ -8,7 +8,7 @@ use std::path::Path; use crate::errors::{Error, Result}; use crate::events::Event; use crate::name::QName; -use crate::parser::{FastElementParser, Parser}; +use crate::parser::{StartElementParser, Parser}; use crate::reader::{BangType, ReadRefResult, ReadTextResult, Reader, Span, XmlSource}; use crate::utils::is_whitespace; @@ -329,7 +329,7 @@ macro_rules! impl_buffered_source { #[inline] $($async)? fn read_start_element<'i>(&mut self, buf: &'i mut Vec, position: &mut u64) -> Result<(usize, &'i [u8])> { - let mut parser = FastElementParser::default(); + let mut parser = StartElementParser::default(); let mut read = 1; // '<' was consumed in peek_one(), but not placed in buf buf.push(b'<'); diff --git a/src/reader/slice_reader.rs b/src/reader/slice_reader.rs index 78e5f6f8..8a126b05 100644 --- a/src/reader/slice_reader.rs +++ b/src/reader/slice_reader.rs @@ -13,7 +13,7 @@ use encoding_rs::{Encoding, UTF_8}; use crate::errors::{Error, Result}; use crate::events::Event; use crate::name::QName; -use crate::parser::{FastElementParser, Parser}; +use crate::parser::{Parser, StartElementParser}; use crate::reader::{BangType, ReadRefResult, ReadTextResult, Reader, Span, XmlSource}; use crate::utils::is_whitespace; @@ -398,7 +398,7 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] { *position += 1; *self = &self[1..]; - let mut parser = FastElementParser::default(); + let mut parser = StartElementParser::default(); if let Some((name_len, consumed)) = parser.feed(self) { // +1 for `>` which we do not include From b2fa9567552dd9f4690095547b1156a36394ecc7 Mon Sep 17 00:00:00 2001 From: Felix Bernhardt Date: Thu, 26 Feb 2026 10:22:51 +0100 Subject: [PATCH 3/3] format --- src/parser/mod.rs | 4 ++-- src/reader/buffered_reader.rs | 2 +- src/reader/mod.rs | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index aa101408..70b3d233 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5,14 +5,14 @@ use crate::errors::SyntaxError; mod comment; mod dtd; mod element; -mod start_element; mod pi; +mod start_element; pub use comment::CommentParser; pub(crate) use dtd::DtdParser; pub use element::ElementParser; -pub use start_element::StartElementParser; pub use pi::PiParser; +pub use start_element::StartElementParser; /// Used to decouple reading of data from data source and parsing XML structure from it. /// This is a state preserved between getting chunks of bytes from the reader. diff --git a/src/reader/buffered_reader.rs b/src/reader/buffered_reader.rs index b4bb58ea..5c960adc 100644 --- a/src/reader/buffered_reader.rs +++ b/src/reader/buffered_reader.rs @@ -8,7 +8,7 @@ use std::path::Path; use crate::errors::{Error, Result}; use crate::events::Event; use crate::name::QName; -use crate::parser::{StartElementParser, Parser}; +use crate::parser::{Parser, StartElementParser}; use crate::reader::{BangType, ReadRefResult, ReadTextResult, Reader, Span, XmlSource}; use crate::utils::is_whitespace; diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 16a2a675..0a2e6290 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -1153,7 +1153,11 @@ trait XmlSource<'r, B> { /// - `position`: Will be increased by amount of bytes consumed /// /// [events]: crate::events::Event - fn read_start_element(&mut self, buf: B, position: &mut u64) -> Result<(usize, &'r [u8]), Error>; + fn read_start_element( + &mut self, + buf: B, + position: &mut u64, + ) -> Result<(usize, &'r [u8]), Error>; } /// Possible elements started with `