//! Contains a parser for an XML comment. use crate::errors::SyntaxError; use crate::parser::Parser; /// A parser that search a `-->` sequence in the slice. /// /// To use a parser create an instance of parser and [`feed`] data into it. /// After successful search the parser will return [`Some`] with position where /// comment is ended (the position after `-->`). If search was unsuccessful, /// a [`None`] will be returned. You typically would expect positive result of /// search, so that you should feed new data until yo'll 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::{CommentParser, Parser}; /// /// let mut parser = CommentParser::default(); /// /// // Parse `and the text follow...` /// // splitted into three chunks /// assert_eq!(parser.feed(b"and the text follow..."), Some(12)); /// // ^ ^ /// // 0 11 /// ``` /// /// [`feed`]: Self::feed() #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CommentParser { /// The parser does not yet seen any dashes at the end of previous slice. Seen0, /// The parser already seen one dash on the end of previous slice. Seen1, /// The parser already seen two dashes on the end of previous slice. Seen2, } impl Default for CommentParser { #[inline] fn default() -> Self { Self::Seen0 } } impl Parser for CommentParser { /// Determines the end position of an XML comment in the provided slice. /// Comments is a pieces of text enclosed in `` braces. /// Comment ends on the first occurrence of `-->` which cannot be escaped. /// /// Returns position after the `-->` or `None` if such sequence was not found. /// /// # Parameters /// - `bytes`: a slice to find the end of a comment. /// Should contain text in ASCII-compatible encoding #[inline] fn feed(&mut self, bytes: &[u8]) -> Option { let result = match self { Self::Seen0 => seen0(bytes), Self::Seen1 => seen1(bytes), Self::Seen2 => seen2(bytes), }; if let Some(r) = result { return Some(r); } if bytes.ends_with(b"--") { *self = Self::Seen2; } else { self.next_state(bytes.last().copied()); } None } #[inline] fn eof_error(self, _content: &[u8]) -> SyntaxError { SyntaxError::UnclosedComment } } impl CommentParser { #[inline] fn next_state(&mut self, last: Option) { match (*self, last) { (Self::Seen0, Some(b'-')) => *self = Self::Seen1, (Self::Seen1, Some(b'-')) => *self = Self::Seen2, (Self::Seen1, Some(_)) => *self = Self::Seen0, (Self::Seen2, Some(b'-')) => {} (Self::Seen2, Some(_)) => *self = Self::Seen0, _ => {} } } } #[inline] fn seen0(bytes: &[u8]) -> Option { for i in memchr::memchr_iter(b'>', bytes) { if bytes[..i].ends_with(b"--") { // +1 for `>` which should be included in event return Some(i + 1); } } None } #[inline] fn seen1(bytes: &[u8]) -> Option { // -|-> if bytes.starts_with(b"->") { return Some(2); } // Even if the first character is `-` it cannot be part of close sequence, // because we checked that condition above. That means that we can forgot that // we seen one `-` at the end of the previous chunk. // -|x... seen0(bytes) } #[inline] fn seen2(bytes: &[u8]) -> Option { match bytes.first() { // --| None => None, // --|> Some(b'>') => Some(1), // The end sequence here can be matched only if bytes starts with `->` // which is handled in seen1(). // --|x... Some(_) => seen1(bytes), } } #[test] fn parse() { use pretty_assertions::assert_eq; use CommentParser::*; /// Returns `Ok(pos)` with the position in the buffer where element is ended. /// /// Returns `Err(internal_state)` if parsing was not done yet. fn parse_comment(bytes: &[u8], mut parser: CommentParser) -> Result { match parser.feed(bytes) { Some(i) => Ok(i), None => Err(parser), } } assert_eq!(parse_comment(b"", Seen0), Err(Seen0)); // xx| assert_eq!(parse_comment(b"", Seen1), Err(Seen1)); // x-| assert_eq!(parse_comment(b"", Seen2), Err(Seen2)); // --| assert_eq!(parse_comment(b"-", Seen0), Err(Seen1)); // xx|- assert_eq!(parse_comment(b"-", Seen1), Err(Seen2)); // x-|- assert_eq!(parse_comment(b"-", Seen2), Err(Seen2)); // --|- assert_eq!(parse_comment(b">", Seen0), Err(Seen0)); // xx|> assert_eq!(parse_comment(b">", Seen1), Err(Seen0)); // x-|> assert_eq!(parse_comment(b">", Seen2), Ok(1)); // --|> assert_eq!(parse_comment(b"--", Seen0), Err(Seen2)); // xx|-- assert_eq!(parse_comment(b"--", Seen1), Err(Seen2)); // x-|-- assert_eq!(parse_comment(b"--", Seen2), Err(Seen2)); // --|-- assert_eq!(parse_comment(b"->", Seen0), Err(Seen0)); // xx|-> assert_eq!(parse_comment(b"->", Seen1), Ok(2)); // x-|-> assert_eq!(parse_comment(b"->", Seen2), Ok(2)); // --|-> assert_eq!(parse_comment(b"-->", Seen0), Ok(3)); // xx|--> assert_eq!(parse_comment(b"-->", Seen1), Ok(3)); // x-|--> assert_eq!(parse_comment(b"-->", Seen2), Ok(3)); // --|--> assert_eq!(parse_comment(b">-->", Seen0), Ok(4)); // xx|>--> assert_eq!(parse_comment(b">-->", Seen1), Ok(4)); // x-|>--> assert_eq!(parse_comment(b">-->", Seen2), Ok(1)); // --|>--> assert_eq!(parse_comment(b"->-->", Seen0), Ok(5)); // xx|->--> assert_eq!(parse_comment(b"->-->", Seen1), Ok(2)); // x-|->--> assert_eq!(parse_comment(b"->-->", Seen2), Ok(2)); // --|->--> }