use crate::xml::parser::input::{InputBuffer, InputStack};
use std::os::raw::c_int;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum XmlToken {
Eof,
XmlDecl {
version: Vec<u8>,
encoding: Option<Vec<u8>>,
standalone: Option<Vec<u8>>,
},
DocType(Vec<u8>),
StartTag {
name: Vec<u8>,
attributes: Vec<(Vec<u8>, Vec<u8>)>,
empty: bool,
},
EndTag(Vec<u8>),
Comment(Vec<u8>),
ProcessingInstruction { target: Vec<u8>, data: Vec<u8> },
Cdata(Vec<u8>),
Characters(Vec<u8>),
Reference(Vec<u8>),
}
pub(crate) struct XmlTokenizer {
input: InputStack,
push_back: Option<XmlToken>,
}
impl XmlTokenizer {
pub fn new(input: InputStack) -> Self {
XmlTokenizer {
input,
push_back: None,
}
}
pub fn input_mut(&mut self) -> &mut InputStack {
&mut self.input
}
pub fn input(&self) -> &InputStack {
&self.input
}
pub fn into_input(self) -> InputStack {
self.input
}
pub fn push_input(&mut self, buf: InputBuffer) {
self.input.push(buf);
}
pub fn pop_input(&mut self) -> Option<InputBuffer> {
self.input.pop()
}
pub fn current_pos(&self) -> (usize, usize, usize) {
self.input.current_pos()
}
pub fn next_token(&mut self) -> XmlToken {
if let Some(token) = self.push_back.take() {
return token;
}
self.skip_whitespace();
self.next_token_raw()
}
pub fn push_back_token(&mut self, token: XmlToken) {
self.push_back = Some(token);
}
pub fn next_token_raw(&mut self) -> XmlToken {
if let Some(token) = self.push_back.take() {
return token;
}
if self.input.is_eof() {
return XmlToken::Eof;
}
match self.input.peek_char() {
Some('<') => self.scan_tag_or_markup(),
Some('&') => self.scan_reference(),
Some(_) => self.scan_characters(),
None => XmlToken::Eof,
}
}
fn skip_whitespace(&mut self) {
loop {
match self.input.peek_char() {
Some(c) if c.is_ascii_whitespace() && c != '\0' => {
self.input.read_char();
}
_ => break,
}
}
}
fn scan_tag_or_markup(&mut self) -> XmlToken {
debug_assert_eq!(self.input.peek_char(), Some('<'));
self.input.read_char();
if self.input.is_eof() {
return XmlToken::Characters(b"<".to_vec());
}
match self.input.peek_char() {
Some('/') => self.scan_end_tag(),
Some('?') => self.scan_pi_or_xml_decl(),
Some('!') => self.scan_markup_decl(),
Some(_) => self.scan_start_tag(),
None => XmlToken::Characters(b"<".to_vec()),
}
}
fn scan_end_tag(&mut self) -> XmlToken {
debug_assert_eq!(self.input.peek_char(), Some('/'));
self.input.read_char();
let name = self.scan_name();
self.skip_whitespace();
if self.input.peek_char() == Some('>') {
self.input.read_char();
}
XmlToken::EndTag(name)
}
fn scan_start_tag(&mut self) -> XmlToken {
let name = self.scan_name();
let mut attributes: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut empty = false;
loop {
self.skip_whitespace();
if self.input.is_eof() {
break;
}
match self.input.peek_char() {
Some('>') => {
self.input.read_char();
break;
}
Some('/') => {
self.input.read_char();
if self.input.peek_char() == Some('>') {
self.input.read_char();
}
empty = true;
break;
}
Some(_) => {
let attr_name = self.scan_name();
if attr_name.is_empty() {
break;
}
self.skip_whitespace();
if self.input.peek_char() == Some('=') {
self.input.read_char();
self.skip_whitespace();
let value = self.scan_attr_value();
attributes.push((attr_name, value));
} else {
attributes.push((attr_name, Vec::new()));
}
}
None => break,
}
}
XmlToken::StartTag {
name,
attributes,
empty,
}
}
fn scan_pi_or_xml_decl(&mut self) -> XmlToken {
debug_assert_eq!(self.input.peek_char(), Some('?'));
self.input.read_char();
let next_bytes = self.peek_bytes(3);
let is_xml_decl = next_bytes.len() >= 3
&& (next_bytes[0] == b'x' || next_bytes[0] == b'X')
&& (next_bytes[1] == b'm' || next_bytes[1] == b'M')
&& (next_bytes[2] == b'l' || next_bytes[2] == b'L');
if is_xml_decl {
self.input.read_char(); self.input.read_char(); self.input.read_char(); return self.scan_xml_decl_rest();
}
let target = self.scan_name();
if self
.input
.peek_char()
.map_or(false, |c| c.is_ascii_whitespace())
{
self.input.read_char();
}
let mut data = Vec::new();
loop {
if self.input.is_eof() {
break;
}
if self.input.peek_char() == Some('?') {
let saved = self.input.current().pos();
self.input.read_char();
if self.input.peek_char() == Some('>') {
self.input.read_char();
break;
}
data.push(b'?');
continue;
}
match self.input.read_char() {
Some(c) => data.push(c as u8),
None => break,
}
}
while data.last() == Some(&b' ')
|| data.last() == Some(&b'\t')
|| data.last() == Some(&b'\n')
|| data.last() == Some(&b'\r')
{
data.pop();
}
XmlToken::ProcessingInstruction { target, data }
}
fn scan_xml_decl_rest(&mut self) -> XmlToken {
let mut version = Vec::new();
let mut encoding: Option<Vec<u8>> = None;
let mut standalone: Option<Vec<u8>> = None;
loop {
self.skip_whitespace();
if self.input.is_eof() {
break;
}
if self.input.peek_char() == Some('?') {
self.input.read_char();
if self.input.peek_char() == Some('>') {
self.input.read_char();
break;
}
continue;
}
let attr_name = self.scan_name();
if attr_name.is_empty() {
break;
}
self.skip_whitespace();
if self.input.peek_char() == Some('=') {
self.input.read_char();
self.skip_whitespace();
let value = self.scan_attr_value();
let lower = attr_name.to_ascii_lowercase();
if lower == b"version" {
version = value;
} else if lower == b"encoding" {
encoding = Some(value);
} else if lower == b"standalone" {
standalone = Some(value);
}
}
}
XmlToken::XmlDecl {
version,
encoding,
standalone,
}
}
fn scan_markup_decl(&mut self) -> XmlToken {
debug_assert_eq!(self.input.peek_char(), Some('!'));
self.input.read_char();
if self.input.is_eof() {
return XmlToken::Characters(b"<!".to_vec());
}
let next = self.peek_bytes(10);
if next.len() >= 2 && next[0] == b'-' && next[1] == b'-' {
self.input.read_char(); self.input.read_char(); return self.scan_comment_body();
}
if next.len() >= 7
&& next[0] == b'['
&& next[1] == b'C'
&& next[2] == b'D'
&& next[3] == b'A'
&& next[4] == b'T'
&& next[5] == b'A'
&& next[6] == b'['
{
for _ in 0..7 {
self.input.read_char();
}
return self.scan_cdata_body();
}
if next.len() >= 7 {
let is_doctype = next[..7].eq_ignore_ascii_case(b"DOCTYPE");
if is_doctype {
for _ in 0..7 {
self.input.read_char();
}
return self.scan_doctype_body();
}
}
let mut content = vec![b'!'];
loop {
match self.input.read_char() {
Some('>') => break,
Some(c) => content.push(c as u8),
None => break,
}
}
XmlToken::Characters(content)
}
fn scan_comment_body(&mut self) -> XmlToken {
let mut content = Vec::new();
loop {
if self.input.is_eof() {
break;
}
if self.input.peek_char() == Some('-') {
self.input.read_char();
if self.input.peek_char() == Some('-') {
self.input.read_char();
if self.input.peek_char() == Some('>') {
self.input.read_char();
break;
}
content.push(b'-');
content.push(b'-');
continue;
}
content.push(b'-');
continue;
}
match self.input.read_char() {
Some(c) => content.push(c as u8),
None => break,
}
}
XmlToken::Comment(content)
}
fn scan_cdata_body(&mut self) -> XmlToken {
let mut content = Vec::new();
loop {
if self.input.is_eof() {
break;
}
if self.input.peek_char() == Some(']') {
self.input.read_char();
if self.input.peek_char() == Some(']') {
self.input.read_char();
if self.input.peek_char() == Some('>') {
self.input.read_char();
break;
}
content.push(b']');
content.push(b']');
continue;
}
content.push(b']');
continue;
}
match self.input.read_char() {
Some(c) => content.push(c as u8),
None => break,
}
}
XmlToken::Cdata(content)
}
fn scan_doctype_body(&mut self) -> XmlToken {
let mut content = Vec::new();
let mut depth: usize = 0;
loop {
if self.input.is_eof() {
break;
}
match self.input.peek_char() {
Some('>') if depth == 0 => {
self.input.read_char();
break;
}
Some('[') => {
depth += 1;
self.input.read_char();
content.push(b'[');
}
Some(']') => {
if depth > 0 {
depth -= 1;
}
self.input.read_char();
content.push(b']');
}
Some(c) => {
self.input.read_char();
content.push(c as u8);
}
None => break,
}
}
XmlToken::DocType(content)
}
fn scan_reference(&mut self) -> XmlToken {
debug_assert_eq!(self.input.peek_char(), Some('&'));
self.input.read_char();
let mut content = vec![b'&'];
loop {
match self.input.read_char() {
Some(';') => {
content.push(b';');
break;
}
Some(c) => content.push(c as u8),
None => break,
}
}
XmlToken::Reference(content)
}
fn scan_characters(&mut self) -> XmlToken {
let mut content = Vec::new();
loop {
if self.input.is_eof() {
break;
}
match self.input.peek_char() {
Some('<') | Some('&') => break,
Some(c) => {
self.input.read_char();
content.push(c as u8);
}
None => break,
}
}
XmlToken::Characters(content)
}
fn scan_name(&mut self) -> Vec<u8> {
let mut name = Vec::new();
loop {
if self.input.is_eof() {
break;
}
let c = match self.input.peek_char() {
Some(c) => c,
None => break,
};
if c.is_alphanumeric() || c == '.' || c == '-' || c == '_' || c == ':' {
self.input.read_char();
name.push(c as u8);
} else {
break;
}
}
name
}
fn scan_attr_value(&mut self) -> Vec<u8> {
let quote = match self.input.peek_char() {
Some('"') | Some('\'') => self.input.read_char().unwrap(),
_ => return Vec::new(),
};
let mut value = Vec::new();
loop {
match self.input.read_char() {
Some(c) if c == quote => break,
Some(c) => value.push(c as u8),
None => break,
}
}
value
}
fn peek_bytes(&self, n: usize) -> Vec<u8> {
let data = self.input.current_ref().remaining();
data.iter().take(n).copied().collect()
}
}