/src/quick-xml/src/parser/comment.rs
Line | Count | Source |
1 | | //! Contains a parser for an XML comment. |
2 | | |
3 | | use crate::errors::SyntaxError; |
4 | | use crate::parser::Parser; |
5 | | |
6 | | /// A parser that search a `-->` sequence in the slice. |
7 | | /// |
8 | | /// To use a parser create an instance of parser and [`feed`] data into it. |
9 | | /// After successful search the parser will return [`Some`] with position where |
10 | | /// comment is ended (the position after `-->`). If search was unsuccessful, |
11 | | /// a [`None`] will be returned. You typically would expect positive result of |
12 | | /// search, so that you should feed new data until yo'll get it. |
13 | | /// |
14 | | /// NOTE: after successful match the parser does not returned to the initial |
15 | | /// state and should not be used anymore. Create a new parser if you want to perform |
16 | | /// new search. |
17 | | /// |
18 | | /// # Example |
19 | | /// |
20 | | /// ``` |
21 | | /// # use pretty_assertions::assert_eq; |
22 | | /// use quick_xml::parser::{CommentParser, Parser}; |
23 | | /// |
24 | | /// let mut parser = CommentParser::default(); |
25 | | /// |
26 | | /// // Parse `<!-- comment with some -> and --- inside-->and the text follow...` |
27 | | /// // split into three chunks |
28 | | /// assert_eq!(parser.feed(b"<!-- comment"), None); |
29 | | /// // ...get new chunk of data |
30 | | /// assert_eq!(parser.feed(b" with some -> and -"), None); |
31 | | /// // ...get another chunk of data |
32 | | /// assert_eq!(parser.feed(b"-- inside-->and the text follow..."), Some(12)); |
33 | | /// // ^ ^ |
34 | | /// // 0 11 |
35 | | /// ``` |
36 | | /// |
37 | | /// [`feed`]: Self::feed() |
38 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
39 | | pub enum CommentParser { |
40 | | /// The parser does not yet seen any dashes at the end of previous slice. |
41 | | Seen0, |
42 | | /// The parser already seen one dash on the end of previous slice. |
43 | | Seen1, |
44 | | /// The parser already seen two dashes on the end of previous slice. |
45 | | Seen2, |
46 | | } |
47 | | |
48 | | impl Default for CommentParser { |
49 | | #[inline] |
50 | | fn default() -> Self { |
51 | | Self::Seen0 |
52 | | } |
53 | | } |
54 | | |
55 | | impl Parser for CommentParser { |
56 | | /// Determines the end position of an XML comment in the provided slice. |
57 | | /// Comments is a pieces of text enclosed in `<!--` and `-->` braces. |
58 | | /// Comment ends on the first occurrence of `-->` which cannot be escaped. |
59 | | /// |
60 | | /// Returns position after the `-->` or `None` if such sequence was not found. |
61 | | /// |
62 | | /// # Parameters |
63 | | /// - `bytes`: a slice to find the end of a comment. |
64 | | /// Should contain text in ASCII-compatible encoding |
65 | | #[inline] |
66 | 42.7k | fn feed(&mut self, bytes: &[u8]) -> Option<usize> { |
67 | 42.7k | let result = match self { |
68 | 39.2k | Self::Seen0 => seen0(bytes), |
69 | 1.64k | Self::Seen1 => seen1(bytes), |
70 | 1.82k | Self::Seen2 => seen2(bytes), |
71 | | }; |
72 | 42.7k | if let Some(r) = result { |
73 | 7.80k | return Some(r); |
74 | 34.9k | } |
75 | 34.9k | if bytes.ends_with(b"--") { |
76 | 919 | *self = Self::Seen2; |
77 | 34.0k | } else { |
78 | 34.0k | self.next_state(bytes.last().copied()); |
79 | 34.0k | } |
80 | 34.9k | None |
81 | 42.7k | } |
82 | | |
83 | | #[inline] |
84 | | fn eof_error(self, _content: &[u8]) -> SyntaxError { |
85 | | SyntaxError::UnclosedComment |
86 | | } |
87 | | } |
88 | | |
89 | | impl CommentParser { |
90 | | #[inline] |
91 | 34.0k | fn next_state(&mut self, last: Option<u8>) { |
92 | 34.0k | match (*self, last) { |
93 | 1.67k | (Self::Seen0, Some(b'-')) => *self = Self::Seen1, |
94 | | |
95 | 471 | (Self::Seen1, Some(b'-')) => *self = Self::Seen2, |
96 | 694 | (Self::Seen1, Some(_)) => *self = Self::Seen0, |
97 | | |
98 | 464 | (Self::Seen2, Some(b'-')) => {} |
99 | 347 | (Self::Seen2, Some(_)) => *self = Self::Seen0, |
100 | | |
101 | 30.3k | _ => {} |
102 | | } |
103 | 34.0k | } |
104 | | } |
105 | | |
106 | | #[inline] |
107 | 42.1k | fn seen0(bytes: &[u8]) -> Option<usize> { |
108 | 286k | for i in memchr::memchr_iter(b'>', bytes) { |
109 | 286k | if bytes[..i].ends_with(b"--") { |
110 | | // +1 for `>` which should be included in event |
111 | 7.20k | return Some(i + 1); |
112 | 279k | } |
113 | | } |
114 | 34.9k | None |
115 | 42.1k | } |
116 | | |
117 | | #[inline] |
118 | 3.25k | fn seen1(bytes: &[u8]) -> Option<usize> { |
119 | | // -|-> |
120 | 3.25k | if bytes.starts_with(b"->") { |
121 | 392 | return Some(2); |
122 | 2.86k | } |
123 | | // Even if the first character is `-` it cannot be part of close sequence, |
124 | | // because we checked that condition above. That means that we can forgot that |
125 | | // we seen one `-` at the end of the previous chunk. |
126 | | // -|x... |
127 | 2.86k | seen0(bytes) |
128 | 3.25k | } |
129 | | |
130 | | #[inline] |
131 | 1.82k | fn seen2(bytes: &[u8]) -> Option<usize> { |
132 | 1.82k | match bytes.first() { |
133 | | // --| |
134 | 0 | None => None, |
135 | | // --|> |
136 | 212 | Some(b'>') => Some(1), |
137 | | // The end sequence here can be matched only if bytes starts with `->` |
138 | | // which is handled in seen1(). |
139 | | // --|x... |
140 | 1.60k | Some(_) => seen1(bytes), |
141 | | } |
142 | 1.82k | } |
143 | | |
144 | | #[test] |
145 | | fn parse() { |
146 | | use pretty_assertions::assert_eq; |
147 | | use CommentParser::*; |
148 | | |
149 | | /// Returns `Ok(pos)` with the position in the buffer where element is ended. |
150 | | /// |
151 | | /// Returns `Err(internal_state)` if parsing was not done yet. |
152 | | fn parse_comment(bytes: &[u8], mut parser: CommentParser) -> Result<usize, CommentParser> { |
153 | | match parser.feed(bytes) { |
154 | | Some(i) => Ok(i), |
155 | | None => Err(parser), |
156 | | } |
157 | | } |
158 | | |
159 | | assert_eq!(parse_comment(b"", Seen0), Err(Seen0)); // xx| |
160 | | assert_eq!(parse_comment(b"", Seen1), Err(Seen1)); // x-| |
161 | | assert_eq!(parse_comment(b"", Seen2), Err(Seen2)); // --| |
162 | | |
163 | | assert_eq!(parse_comment(b"-", Seen0), Err(Seen1)); // xx|- |
164 | | assert_eq!(parse_comment(b"-", Seen1), Err(Seen2)); // x-|- |
165 | | assert_eq!(parse_comment(b"-", Seen2), Err(Seen2)); // --|- |
166 | | |
167 | | assert_eq!(parse_comment(b">", Seen0), Err(Seen0)); // xx|> |
168 | | assert_eq!(parse_comment(b">", Seen1), Err(Seen0)); // x-|> |
169 | | assert_eq!(parse_comment(b">", Seen2), Ok(1)); // --|> |
170 | | |
171 | | assert_eq!(parse_comment(b"--", Seen0), Err(Seen2)); // xx|-- |
172 | | assert_eq!(parse_comment(b"--", Seen1), Err(Seen2)); // x-|-- |
173 | | assert_eq!(parse_comment(b"--", Seen2), Err(Seen2)); // --|-- |
174 | | |
175 | | assert_eq!(parse_comment(b"->", Seen0), Err(Seen0)); // xx|-> |
176 | | assert_eq!(parse_comment(b"->", Seen1), Ok(2)); // x-|-> |
177 | | assert_eq!(parse_comment(b"->", Seen2), Ok(2)); // --|-> |
178 | | |
179 | | assert_eq!(parse_comment(b"-->", Seen0), Ok(3)); // xx|--> |
180 | | assert_eq!(parse_comment(b"-->", Seen1), Ok(3)); // x-|--> |
181 | | assert_eq!(parse_comment(b"-->", Seen2), Ok(3)); // --|--> |
182 | | |
183 | | assert_eq!(parse_comment(b">-->", Seen0), Ok(4)); // xx|>--> |
184 | | assert_eq!(parse_comment(b">-->", Seen1), Ok(4)); // x-|>--> |
185 | | assert_eq!(parse_comment(b">-->", Seen2), Ok(1)); // --|>--> |
186 | | |
187 | | assert_eq!(parse_comment(b"->-->", Seen0), Ok(5)); // xx|->--> |
188 | | assert_eq!(parse_comment(b"->-->", Seen1), Ok(2)); // x-|->--> |
189 | | assert_eq!(parse_comment(b"->-->", Seen2), Ok(2)); // --|->--> |
190 | | } |