use crate::errors::SyntaxError;
use crate::parser::Parser;
use crate::utils::{is_whitespace, memchr_iter};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PiParser(
pub bool,
);
impl Parser for PiParser {
#[inline]
fn feed(&mut self, bytes: &[u8]) -> Option<usize> {
for i in memchr_iter(b'>', bytes) {
match i {
0 if self.0 => return Some(0),
i if i > 0 && bytes[i - 1] == b'?' => return Some(i),
_ => {}
}
}
self.0 = bytes.last().copied() == Some(b'?');
None
}
#[inline]
fn eof_error(self, content: &[u8]) -> SyntaxError {
let is_xml_decl = content.starts_with(b"<?xml")
&& content
.get(5)
.is_none_or(|&b| is_whitespace(b) || b == b'?');
if is_xml_decl {
SyntaxError::UnclosedXmlDecl
} else {
SyntaxError::UnclosedPI
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_pi(bytes: &[u8], had_question_mark: bool) -> Result<usize, bool> {
let mut parser = PiParser(had_question_mark);
match parser.feed(bytes) {
Some(i) => Ok(i),
None => Err(parser.0),
}
}
#[test]
fn pi() {
assert_eq!(parse_pi(b"", false), Err(false)); assert_eq!(parse_pi(b"", true), Err(false));
assert_eq!(parse_pi(b"?", false), Err(true)); assert_eq!(parse_pi(b"?", true), Err(true));
assert_eq!(parse_pi(b">", false), Err(false)); assert_eq!(parse_pi(b">", true), Ok(0));
assert_eq!(parse_pi(b"?>", false), Ok(1)); assert_eq!(parse_pi(b"?>", true), Ok(1));
assert_eq!(parse_pi(b">?>", false), Ok(2)); assert_eq!(parse_pi(b">?>", true), Ok(0)); }
#[test]
fn unclosed_xml_declaration_needs_an_ascii_compatible_encoding() {
assert_eq!(
PiParser(false).eof_error(b"<?xml version='1.0'"),
SyntaxError::UnclosedXmlDecl
);
let utf16: Vec<u8> = "<?xml version='1.0'"
.encode_utf16()
.flat_map(u16::to_le_bytes)
.collect();
assert_eq!(PiParser(false).eof_error(&utf16), SyntaxError::UnclosedPI);
}
}