Coverage Report

Created: 2026-08-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}