use std::io;
use std::ops::Range;
use crate::encoding::Decoder;
#[cfg(feature = "encoding")]
use crate::encoding::DetectedEncoding;
use crate::errors::{Error, IllFormedError, SyntaxError};
use crate::events::{BytesRef, Event};
use crate::parser::{DtdParser, ElementParser, Parser, PiParser};
use crate::reader::state::ReaderState;
#[cfg(feature = "encoding")]
use oxixml_encoding::Encoding;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde-types", derive(serde::Deserialize, serde::Serialize))]
#[non_exhaustive]
pub struct Config {
pub allow_dangling_amp: bool,
pub allow_unmatched_ends: bool,
pub check_comments: bool,
pub check_end_names: bool,
pub check_well_formedness: bool,
pub expand_empty_elements: bool,
pub trim_markup_names_in_closing_tags: bool,
pub trim_text_start: bool,
pub trim_text_end: bool,
pub max_element_depth: usize,
}
pub const DEFAULT_MAX_ELEMENT_DEPTH: usize = 1024;
impl Config {
#[inline]
pub fn trim_text(&mut self, trim: bool) {
self.trim_text_start = trim;
self.trim_text_end = trim;
}
#[inline]
pub fn enable_all_checks(&mut self, enable: bool) {
self.check_comments = enable;
self.check_end_names = enable;
self.check_well_formedness = enable;
}
}
impl Default for Config {
fn default() -> Self {
Self {
allow_dangling_amp: false,
allow_unmatched_ends: false,
check_comments: false,
check_end_names: true,
check_well_formedness: false,
expand_empty_elements: false,
trim_markup_names_in_closing_tags: true,
trim_text_start: false,
trim_text_end: false,
max_element_depth: DEFAULT_MAX_ELEMENT_DEPTH,
}
}
}
macro_rules! read_event_impl {
(
$self:ident, $buf:ident,
$reader:expr,
$read_until_close:ident
$(, $await:ident)?
) => {{
let event = loop {
break match $self.state.state {
ParseState::Init => {
#[cfg(feature = "encoding")]
if let Some(encoding) = $reader.detect_encoding() $(.$await)? ? {
if $self.state.encoding.can_be_refined() {
$self.state.encoding =
crate::reader::EncodingRef::BomDetected(encoding.encoding());
}
}
#[cfg(not(feature = "encoding"))]
$reader.remove_utf8_bom() $(.$await)? ?;
$self.state.state = ParseState::InsideText;
continue;
}
ParseState::InsideRef => {
let start = $self.state.offset;
match $reader.read_ref($buf, &mut $self.state.offset) $(.$await)? {
ReadRefResult::Ref(bytes) => {
$self.state.state = ParseState::InsideText;
Ok(Event::GeneralRef(BytesRef::wrap(
&bytes[1..bytes.len() - 1],
$self.decoder(),
)))
}
ReadRefResult::UpToEof(bytes) if $self.state.config.allow_dangling_amp => {
$self.state.state = ParseState::Done;
Ok(Event::Text($self.state.emit_text(bytes)))
}
ReadRefResult::UpToEof(_) => {
$self.state.state = ParseState::Done;
$self.state.last_error_offset = start;
Err(Error::IllFormed(IllFormedError::UnclosedReference))
}
ReadRefResult::UpToRef(bytes) if $self.state.config.allow_dangling_amp => {
Ok(Event::Text($self.state.emit_text(bytes)))
}
ReadRefResult::UpToRef(_) => {
$self.state.last_error_offset = start;
Err(Error::IllFormed(IllFormedError::UnclosedReference))
}
ReadRefResult::UpToMarkup(bytes)
if $self.state.config.allow_dangling_amp =>
{
$self.state.state = ParseState::InsideMarkup;
Ok(Event::Text($self.state.emit_text(bytes)))
}
ReadRefResult::UpToMarkup(_) => {
$self.state.state = ParseState::InsideMarkup;
$self.state.last_error_offset = start;
Err(Error::IllFormed(IllFormedError::UnclosedReference))
}
ReadRefResult::Err(e) => Err(Error::from(e)),
}
}
ParseState::InsideText => {
if $self.state.config.trim_text_start {
$reader.skip_whitespace(&mut $self.state.offset) $(.$await)? ?;
}
match $reader.read_text(
$buf,
&mut $self.state.offset,
$self.state.config.trim_text_end,
) $(.$await)? {
ReadTextResult::Markup(buf) => {
$self.state.state = ParseState::InsideMarkup;
$buf = buf;
continue;
}
ReadTextResult::Ref(buf) => {
$self.state.state = ParseState::InsideRef;
$buf = buf;
continue;
}
ReadTextResult::UpToMarkup(bytes) => {
$self.state.state = ParseState::InsideMarkup;
debug_assert!(
!$self.state.config.trim_text_end
|| !bytes.iter().all(|b| crate::utils::is_whitespace(*b)),
"an all-white-space run must be reported as `Markup`",
);
Ok(Event::Text($self.state.emit_text(bytes)))
}
ReadTextResult::UpToRef(bytes) => {
$self.state.state = ParseState::InsideRef;
debug_assert!(
!$self.state.config.trim_text_end
|| !bytes.iter().all(|b| crate::utils::is_whitespace(*b)),
"an all-white-space run must be reported as `Ref`",
);
Ok(Event::Text($self.state.emit_text(bytes)))
}
ReadTextResult::UpToEof(bytes) => {
$self.state.state = ParseState::Done;
let event = $self.state.emit_text(bytes);
if event.is_empty() {
Ok(Event::Eof)
} else {
Ok(Event::Text(event))
}
}
ReadTextResult::Err(e) => Err(Error::from(e)),
}
}
ParseState::InsideMarkup => $self.$read_until_close($buf) $(.$await)?,
ParseState::InsideEmpty => Ok(Event::End($self.state.close_expanded_empty())),
ParseState::Done => Ok(Event::Eof),
};
};
let event = match event {
Ok(ev) if $self.state.config.check_well_formedness => {
match $self.state.check_well_formedness(&ev) {
Ok(()) => Ok(ev),
Err(err) => Err(err),
}
}
other => other,
};
match event {
Err(Error::IllFormed(_)) => {}
Err(_) | Ok(Event::Eof) => $self.state.state = ParseState::Done,
_ => {}
}
event
}};
}
macro_rules! read_until_close {
(
$self:ident, $buf:ident,
$reader:expr
$(, $await:ident)?
) => {{
$self.state.state = ParseState::InsideText;
let start = $self.state.offset;
match $reader.peek_one() $(.$await)? {
Ok(Some(b'!')) => {
match $reader.read_bang_element($buf, &mut $self.state.offset) $(.$await)? {
Ok((bang_type, bytes)) => $self.state.emit_bang(bang_type, bytes),
Err(e) => {
$self.state.last_error_offset = start;
Err(e)
}
}
}
Ok(Some(b'/')) => {
match $reader.read_with(ElementParser::Outside, $buf, &mut $self.state.offset) $(.$await)? {
Ok(bytes) => $self.state.emit_end(bytes),
Err(e) => {
$self.state.last_error_offset = start;
Err(e)
}
}
}
Ok(Some(b'?')) => {
match $reader.read_with(PiParser(false), $buf, &mut $self.state.offset) $(.$await)? {
Ok(bytes) => $self.state.emit_question_mark(bytes),
Err(e) => {
$self.state.last_error_offset = start;
Err(e)
}
}
}
Ok(Some(_)) => {
match $reader.read_with(ElementParser::Outside, $buf, &mut $self.state.offset) $(.$await)? {
Ok(bytes) => Ok($self.state.emit_start(bytes)),
Err(e) => {
$self.state.last_error_offset = start;
Err(e)
}
}
}
Ok(None) => {
$self.state.last_error_offset = start;
Err(Error::Syntax(SyntaxError::UnclosedTag))
}
Err(e) => Err(Error::from(e)),
}
}};
}
macro_rules! read_to_end {
(
// $self: &mut Reader
$self:expr, $end:expr, $buf:expr,
$read_event:ident,
// Code block that performs clearing of internal buffer after read of each event
$clear:block
$(, $await:ident)?
) => {{
let config = $self.config_mut();
let trim = config.trim_text_start;
config.trim_text_start = false;
let start = $self.buffer_position();
let mut depth = 0;
loop {
$clear
let end = $self.buffer_position();
match $self.$read_event($buf) $(.$await)? {
Err(e) => {
$self.config_mut().trim_text_start = trim;
return Err(e);
}
Ok(Event::Start(e)) if e.name() == $end => depth += 1,
Ok(Event::End(e)) if e.name() == $end => {
if depth == 0 {
$self.config_mut().trim_text_start = trim;
break start..end;
}
depth -= 1;
}
Ok(Event::Eof) => {
$self.config_mut().trim_text_start = trim;
return Err(Error::missed_end($end, $self.decoder()));
}
_ => (),
}
}
}};
}
#[cfg(feature = "async-tokio")]
mod async_tokio;
mod buffered_reader;
#[cfg(test)]
mod check_tests;
mod ns_reader;
mod slice_reader;
mod state;
pub use ns_reader::NsReader;
pub type Span = Range<u64>;
#[derive(Clone, Debug)]
pub(crate) enum ParseState {
Init,
InsideRef,
InsideMarkup,
InsideText,
InsideEmpty,
Done,
}
#[cfg(feature = "encoding")]
#[derive(Clone, Copy, Debug)]
pub(crate) enum EncodingRef {
Implicit(Encoding),
Explicit(Encoding),
BomDetected(Encoding),
XmlDetected(Encoding),
}
#[cfg(feature = "encoding")]
impl EncodingRef {
#[inline]
const fn encoding(&self) -> Encoding {
match self {
Self::Implicit(e) | Self::Explicit(e) | Self::BomDetected(e) | Self::XmlDetected(e) => {
*e
}
}
}
#[inline]
const fn can_be_refined(&self) -> bool {
match self {
Self::Implicit(_) | Self::BomDetected(_) => true,
Self::Explicit(_) | Self::XmlDetected(_) => false,
}
}
}
#[derive(Debug)]
#[must_use = "streams do nothing unless read or polled"]
pub struct BinaryStream<'r, R> {
inner: &'r mut R,
offset: &'r mut u64,
}
impl<'r, R> BinaryStream<'r, R> {
#[inline]
pub const fn offset(&self) -> u64 {
*self.offset
}
#[inline]
pub const fn get_ref(&self) -> &R {
self.inner
}
#[inline]
pub fn get_mut(&mut self) -> &mut R {
self.inner
}
}
impl<R> io::Read for BinaryStream<'_, R>
where
R: io::Read,
{
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let amt = self.inner.read(buf)?;
*self.offset += amt as u64;
Ok(amt)
}
}
impl<R> io::BufRead for BinaryStream<'_, R>
where
R: io::BufRead,
{
#[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> {
self.inner.fill_buf()
}
#[inline]
fn consume(&mut self, amt: usize) {
self.inner.consume(amt);
*self.offset += amt as u64;
}
}
#[derive(Debug, Clone)]
pub struct Reader<R> {
reader: R,
state: ReaderState,
}
impl<R> Reader<R> {
pub fn from_reader(reader: R) -> Self {
Self {
reader,
state: ReaderState::default(),
}
}
pub const fn config(&self) -> &Config {
&self.state.config
}
pub fn config_mut(&mut self) -> &mut Config {
&mut self.state.config
}
#[inline]
pub(super) fn declared_entity(&self, name: &str) -> Option<&str> {
self.state.declared_entity(name)
}
#[inline]
pub(super) fn enable_namespace_constraints(&mut self) {
self.state.enable_namespace_constraints();
}
}
impl<R> Reader<R> {
pub fn into_inner(self) -> R {
self.reader
}
pub const fn get_ref(&self) -> &R {
&self.reader
}
pub fn get_mut(&mut self) -> &mut R {
&mut self.reader
}
pub const fn buffer_position(&self) -> u64 {
self.state.offset
}
pub const fn error_position(&self) -> u64 {
self.state.last_error_offset
}
#[inline]
pub const fn decoder(&self) -> Decoder {
self.state.decoder()
}
#[inline]
pub fn stream(&mut self) -> BinaryStream<'_, R> {
BinaryStream {
inner: &mut self.reader,
offset: &mut self.state.offset,
}
}
}
impl<R> Reader<R> {
fn read_event_impl<'i, B>(&mut self, mut buf: B) -> Result<Event<'i>, Error>
where
R: XmlSource<'i, B>,
{
read_event_impl!(self, buf, self.reader, read_until_close)
}
fn read_until_close<'i, B>(&mut self, buf: B) -> Result<Event<'i>, Error>
where
R: XmlSource<'i, B>,
{
read_until_close!(self, buf, self.reader)
}
}
#[derive(Debug)]
pub(crate) enum ReadTextResult<'r, B> {
Markup(B),
Ref(B),
UpToMarkup(&'r [u8]),
UpToRef(&'r [u8]),
UpToEof(&'r [u8]),
Err(io::Error),
}
#[derive(Debug)]
pub(crate) enum ReadRefResult<'r> {
Ref(&'r [u8]),
UpToEof(&'r [u8]),
UpToRef(&'r [u8]),
UpToMarkup(&'r [u8]),
Err(io::Error),
}
pub(crate) trait XmlSource<'r, B> {
#[cfg(not(feature = "encoding"))]
fn remove_utf8_bom(&mut self) -> io::Result<()>;
#[cfg(feature = "encoding")]
fn detect_encoding(&mut self) -> io::Result<Option<DetectedEncoding>>;
fn read_text(
&mut self,
buf: B,
position: &mut u64,
trim_whitespace_only: bool,
) -> ReadTextResult<'r, B>;
fn read_ref(&mut self, buf: B, position: &mut u64) -> ReadRefResult<'r>;
fn read_with<P>(&mut self, parser: P, buf: B, position: &mut u64) -> Result<&'r [u8], Error>
where
P: Parser;
fn read_bang_element(
&mut self,
buf: B,
position: &mut u64,
) -> Result<(BangType, &'r [u8]), Error>;
fn skip_whitespace(&mut self, position: &mut u64) -> io::Result<()>;
fn peek_one(&mut self) -> io::Result<Option<u8>>;
}
#[derive(Debug, PartialEq)]
pub(crate) enum BangType {
CData,
Comment,
DocType(DtdParser),
}
impl BangType {
#[inline(always)]
const fn new(byte: Option<u8>) -> Result<Self, SyntaxError> {
Ok(match byte {
Some(b'[') => Self::CData,
Some(b'-') => Self::Comment,
Some(b'D') | Some(b'd') => Self::DocType(DtdParser::BeforeInternalSubset(0)),
_ => return Err(SyntaxError::InvalidBangMarkup),
})
}
#[inline(always)]
fn feed(&mut self, buf: &[u8], chunk: &[u8]) -> Option<usize> {
match self {
Self::Comment => {
for i in crate::utils::memchr_iter(b'>', chunk) {
if buf.len() + i > 5 {
if chunk[..i].ends_with(b"--") {
return Some(i);
}
if i == 1 && buf.ends_with(b"-") && chunk[0] == b'-' {
return Some(i);
}
if i == 0 && buf.ends_with(b"--") {
return Some(i);
}
}
}
}
Self::CData => {
for i in crate::utils::memchr_iter(b'>', chunk) {
if chunk[..i].ends_with(b"]]") {
return Some(i);
}
if i == 1 && buf.ends_with(b"]") && chunk[0] == b']' {
return Some(i);
}
if i == 0 && buf.ends_with(b"]]") {
return Some(i);
}
}
}
Self::DocType(parser) => return parser.feed(buf, chunk),
}
None
}
#[inline]
const fn to_err(&self) -> SyntaxError {
match self {
Self::CData => SyntaxError::UnclosedCData,
Self::Comment => SyntaxError::UnclosedComment,
Self::DocType(_) => SyntaxError::UnclosedDoctype,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_spec() {
let config = Config::default();
assert!(!config.allow_dangling_amp);
assert!(!config.allow_unmatched_ends);
assert!(!config.check_comments);
assert!(config.check_end_names);
assert!(!config.expand_empty_elements);
assert!(config.trim_markup_names_in_closing_tags);
assert!(!config.trim_text_start);
assert!(!config.trim_text_end);
assert_eq!(config.max_element_depth, DEFAULT_MAX_ELEMENT_DEPTH);
}
#[test]
fn trim_text_sets_both() {
let mut config = Config::default();
config.trim_text(true);
assert!(config.trim_text_start);
assert!(config.trim_text_end);
}
#[test]
fn enable_all_checks_toggles() {
let mut config = Config::default();
config.enable_all_checks(true);
assert!(config.check_comments);
assert!(config.check_end_names);
config.enable_all_checks(false);
assert!(!config.check_comments);
assert!(!config.check_end_names);
}
}