rusteerio 0.1.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +7 -0
- package/THIRD_PARTY_NOTICES.md +32 -1
- package/dist/{chunk-DKULX6VO.mjs → chunk-DMRD3FGC.mjs} +1 -1
- package/dist/chunk-DMRD3FGC.mjs.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/slim.cjs.map +1 -1
- package/dist/slim.mjs +1 -1
- package/native/src/arena.rs +54 -535
- package/native/src/codec.rs +62 -18
- package/native/src/hook_sink.rs +77 -3
- package/native/src/lib.rs +245 -18
- package/native/src/markup_tokenizer.rs +668 -0
- package/native/src/tokenizer.rs +355 -199
- package/native/vendor/README.md +3 -2
- package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +23 -13
- package/package.json +1 -1
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/scripts/generate-notices.mjs +11 -0
- package/scripts/notices/htmlparser2-MIT.txt +18 -0
- package/scripts/notices/sources.json +6 -0
- package/src/router.ts +3 -2
- package/dist/chunk-DKULX6VO.mjs.map +0 -1
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
//! A port of htmlparser2's Tokenizer state machine over UTF-16 code units.
|
|
2
|
+
//!
|
|
3
|
+
//! Offsets are UTF-16 indices, as in htmlparser2. Text and attribute values are
|
|
4
|
+
//! reported as raw spans; the tree builder decodes them, which yields the same
|
|
5
|
+
//! text as htmlparser2's per-entity callbacks because adjacent text merges.
|
|
6
|
+
|
|
7
|
+
const TAB: u16 = 0x09;
|
|
8
|
+
const NEW_LINE: u16 = 0x0a;
|
|
9
|
+
const FORM_FEED: u16 = 0x0c;
|
|
10
|
+
const CARRIAGE_RETURN: u16 = 0x0d;
|
|
11
|
+
const SPACE: u16 = 0x20;
|
|
12
|
+
const EXCLAMATION_MARK: u16 = 0x21;
|
|
13
|
+
const DOUBLE_QUOTE: u16 = 0x22;
|
|
14
|
+
const AMP: u16 = 0x26;
|
|
15
|
+
const SINGLE_QUOTE: u16 = 0x27;
|
|
16
|
+
const DASH: u16 = 0x2d;
|
|
17
|
+
const SLASH: u16 = 0x2f;
|
|
18
|
+
const LT: u16 = 0x3c;
|
|
19
|
+
const EQ: u16 = 0x3d;
|
|
20
|
+
const GT: u16 = 0x3e;
|
|
21
|
+
const QUESTION_MARK: u16 = 0x3f;
|
|
22
|
+
const OPENING_SQUARE_BRACKET: u16 = 0x5b;
|
|
23
|
+
|
|
24
|
+
const CDATA: &[u8] = b"CDATA[";
|
|
25
|
+
const CDATA_END: &[u8] = b"]]>";
|
|
26
|
+
const COMMENT_END: &[u8] = b"-->";
|
|
27
|
+
const SCRIPT_END: &[u8] = b"</script";
|
|
28
|
+
const STYLE_END: &[u8] = b"</style";
|
|
29
|
+
const TITLE_END: &[u8] = b"</title";
|
|
30
|
+
const TEXTAREA_END: &[u8] = b"</textarea";
|
|
31
|
+
const XMP_END: &[u8] = b"</xmp";
|
|
32
|
+
|
|
33
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
34
|
+
pub(crate) enum Token {
|
|
35
|
+
/// Raw text between `start` and `end`; `decode` marks entity decoding.
|
|
36
|
+
Text {
|
|
37
|
+
start: usize,
|
|
38
|
+
end: usize,
|
|
39
|
+
decode: bool,
|
|
40
|
+
},
|
|
41
|
+
OpenTagName {
|
|
42
|
+
start: usize,
|
|
43
|
+
end: usize,
|
|
44
|
+
},
|
|
45
|
+
OpenTagEnd {
|
|
46
|
+
end: usize,
|
|
47
|
+
},
|
|
48
|
+
SelfClosingTag {
|
|
49
|
+
end: usize,
|
|
50
|
+
},
|
|
51
|
+
AttributeName {
|
|
52
|
+
start: usize,
|
|
53
|
+
end: usize,
|
|
54
|
+
},
|
|
55
|
+
/// Raw attribute value data; `decode` marks entity decoding.
|
|
56
|
+
AttributeData {
|
|
57
|
+
start: usize,
|
|
58
|
+
end: usize,
|
|
59
|
+
decode: bool,
|
|
60
|
+
},
|
|
61
|
+
AttributeEnd {
|
|
62
|
+
end: usize,
|
|
63
|
+
},
|
|
64
|
+
CloseTag {
|
|
65
|
+
start: usize,
|
|
66
|
+
end: usize,
|
|
67
|
+
},
|
|
68
|
+
Comment {
|
|
69
|
+
start: usize,
|
|
70
|
+
end: usize,
|
|
71
|
+
end_offset: usize,
|
|
72
|
+
},
|
|
73
|
+
Cdata {
|
|
74
|
+
start: usize,
|
|
75
|
+
end: usize,
|
|
76
|
+
end_offset: usize,
|
|
77
|
+
},
|
|
78
|
+
Declaration {
|
|
79
|
+
start: usize,
|
|
80
|
+
end: usize,
|
|
81
|
+
},
|
|
82
|
+
ProcessingInstruction {
|
|
83
|
+
start: usize,
|
|
84
|
+
end: usize,
|
|
85
|
+
},
|
|
86
|
+
End,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
90
|
+
enum State {
|
|
91
|
+
Text,
|
|
92
|
+
BeforeTagName,
|
|
93
|
+
InTagName,
|
|
94
|
+
InSelfClosingTag,
|
|
95
|
+
BeforeClosingTagName,
|
|
96
|
+
InClosingTagName,
|
|
97
|
+
AfterClosingTagName,
|
|
98
|
+
BeforeAttributeName,
|
|
99
|
+
InAttributeName,
|
|
100
|
+
AfterAttributeName,
|
|
101
|
+
BeforeAttributeValue,
|
|
102
|
+
InAttributeValueDq,
|
|
103
|
+
InAttributeValueSq,
|
|
104
|
+
InAttributeValueNq,
|
|
105
|
+
BeforeDeclaration,
|
|
106
|
+
InDeclaration,
|
|
107
|
+
InProcessingInstruction,
|
|
108
|
+
BeforeComment,
|
|
109
|
+
CdataSequence,
|
|
110
|
+
InSpecialComment,
|
|
111
|
+
InCommentLike,
|
|
112
|
+
BeforeSpecialS,
|
|
113
|
+
BeforeSpecialT,
|
|
114
|
+
SpecialStartSequence,
|
|
115
|
+
InSpecialTag,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn is_whitespace(c: u16) -> bool {
|
|
119
|
+
matches!(c, SPACE | NEW_LINE | TAB | FORM_FEED | CARRIAGE_RETURN)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn is_end_of_tag_section(c: u16) -> bool {
|
|
123
|
+
c == SLASH || c == GT || is_whitespace(c)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn is_ascii_alpha(c: u16) -> bool {
|
|
127
|
+
(u16::from(b'a')..=u16::from(b'z')).contains(&c)
|
|
128
|
+
|| (u16::from(b'A')..=u16::from(b'Z')).contains(&c)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fn lower(c: u16) -> u16 {
|
|
132
|
+
c | 0x20
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Tokenizes `units` and passes each token to `emit`, in htmlparser2's order.
|
|
136
|
+
pub(crate) fn tokenize(units: &[u16], xml: bool, decode: bool, emit: impl FnMut(Token)) {
|
|
137
|
+
let mut tokenizer = Tokenizer {
|
|
138
|
+
units,
|
|
139
|
+
xml,
|
|
140
|
+
decode,
|
|
141
|
+
emit,
|
|
142
|
+
state: State::Text,
|
|
143
|
+
section_start: Some(0),
|
|
144
|
+
index: 0,
|
|
145
|
+
is_special: false,
|
|
146
|
+
sequence: COMMENT_END,
|
|
147
|
+
sequence_index: 0,
|
|
148
|
+
};
|
|
149
|
+
tokenizer.run();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
struct Tokenizer<'a, F: FnMut(Token)> {
|
|
153
|
+
units: &'a [u16],
|
|
154
|
+
xml: bool,
|
|
155
|
+
decode: bool,
|
|
156
|
+
emit: F,
|
|
157
|
+
state: State,
|
|
158
|
+
/// Start of the section being read; `None` matches htmlparser2's `-1`.
|
|
159
|
+
section_start: Option<usize>,
|
|
160
|
+
index: usize,
|
|
161
|
+
is_special: bool,
|
|
162
|
+
sequence: &'static [u8],
|
|
163
|
+
sequence_index: usize,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
impl<F: FnMut(Token)> Tokenizer<'_, F> {
|
|
167
|
+
fn run(&mut self) {
|
|
168
|
+
while self.index < self.units.len() {
|
|
169
|
+
let c = self.units[self.index];
|
|
170
|
+
match self.state {
|
|
171
|
+
State::Text => self.state_text(c),
|
|
172
|
+
State::SpecialStartSequence => self.state_special_start_sequence(c),
|
|
173
|
+
State::InSpecialTag => self.state_in_special_tag(c),
|
|
174
|
+
State::CdataSequence => self.state_cdata_sequence(c),
|
|
175
|
+
State::InAttributeValueDq => self.state_in_attribute_value_quoted(c, DOUBLE_QUOTE),
|
|
176
|
+
State::InAttributeName => self.state_in_attribute_name(c),
|
|
177
|
+
State::InCommentLike => self.state_in_comment_like(c),
|
|
178
|
+
State::InSpecialComment => self.state_in_special_comment(c),
|
|
179
|
+
State::BeforeAttributeName => self.state_before_attribute_name(c),
|
|
180
|
+
State::InTagName => self.state_in_tag_name(c),
|
|
181
|
+
State::InClosingTagName => self.state_in_closing_tag_name(c),
|
|
182
|
+
State::BeforeTagName => self.state_before_tag_name(c),
|
|
183
|
+
State::AfterAttributeName => self.state_after_attribute_name(c),
|
|
184
|
+
State::InAttributeValueSq => self.state_in_attribute_value_quoted(c, SINGLE_QUOTE),
|
|
185
|
+
State::BeforeAttributeValue => self.state_before_attribute_value(c),
|
|
186
|
+
State::BeforeClosingTagName => self.state_before_closing_tag_name(c),
|
|
187
|
+
State::AfterClosingTagName => self.state_after_closing_tag_name(c),
|
|
188
|
+
State::BeforeSpecialS => self.state_before_special_s(c),
|
|
189
|
+
State::BeforeSpecialT => self.state_before_special_t(c),
|
|
190
|
+
State::InAttributeValueNq => self.state_in_attribute_value_unquoted(c),
|
|
191
|
+
State::InSelfClosingTag => self.state_in_self_closing_tag(c),
|
|
192
|
+
State::InDeclaration => self.state_in_declaration(c),
|
|
193
|
+
State::BeforeDeclaration => self.state_before_declaration(c),
|
|
194
|
+
State::BeforeComment => self.state_before_comment(c),
|
|
195
|
+
State::InProcessingInstruction => self.state_in_processing_instruction(c),
|
|
196
|
+
}
|
|
197
|
+
self.index += 1;
|
|
198
|
+
}
|
|
199
|
+
self.finish();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn start(&self) -> usize {
|
|
203
|
+
self.section_start.unwrap_or(0)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn emit_text(&mut self, start: usize, end: usize, decode: bool) {
|
|
207
|
+
if start < end {
|
|
208
|
+
(self.emit)(Token::Text { start, end, decode });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Advances to the next `c`; leaves `index` on the last unit when absent.
|
|
213
|
+
fn fast_forward_to(&mut self, c: u16) -> bool {
|
|
214
|
+
while self.index + 1 < self.units.len() {
|
|
215
|
+
self.index += 1;
|
|
216
|
+
if self.units[self.index] == c {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
self.index = self.units.len().saturating_sub(1);
|
|
221
|
+
false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Entities are decoded per span, so `&` never changes state and text can
|
|
225
|
+
// skip ahead to the next `<` whether or not entities are decoded.
|
|
226
|
+
fn state_text(&mut self, c: u16) {
|
|
227
|
+
if c == LT || self.fast_forward_to(LT) {
|
|
228
|
+
let start = self.start();
|
|
229
|
+
if self.index > start {
|
|
230
|
+
self.emit_text(start, self.index, self.decode);
|
|
231
|
+
}
|
|
232
|
+
self.state = State::BeforeTagName;
|
|
233
|
+
self.section_start = Some(self.index);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn state_special_start_sequence(&mut self, c: u16) {
|
|
238
|
+
let is_end = self.sequence_index == self.sequence.len();
|
|
239
|
+
let is_match = if is_end {
|
|
240
|
+
is_end_of_tag_section(c)
|
|
241
|
+
} else {
|
|
242
|
+
lower(c) == u16::from(self.sequence[self.sequence_index])
|
|
243
|
+
};
|
|
244
|
+
if !is_match {
|
|
245
|
+
self.is_special = false;
|
|
246
|
+
} else if !is_end {
|
|
247
|
+
self.sequence_index += 1;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
self.sequence_index = 0;
|
|
251
|
+
self.state = State::InTagName;
|
|
252
|
+
self.state_in_tag_name(c);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn state_in_special_tag(&mut self, c: u16) {
|
|
256
|
+
if self.sequence_index == self.sequence.len() {
|
|
257
|
+
if c == GT || is_whitespace(c) {
|
|
258
|
+
let end_of_text = self.index - self.sequence.len();
|
|
259
|
+
let start = self.start();
|
|
260
|
+
if start < end_of_text {
|
|
261
|
+
let is_title = self.sequence == TITLE_END;
|
|
262
|
+
self.emit_text(start, end_of_text, is_title && self.decode);
|
|
263
|
+
}
|
|
264
|
+
self.is_special = false;
|
|
265
|
+
self.section_start = Some(end_of_text + 2);
|
|
266
|
+
self.state_in_closing_tag_name(c);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
self.sequence_index = 0;
|
|
270
|
+
}
|
|
271
|
+
let is_title = self.sequence == TITLE_END;
|
|
272
|
+
if lower(c) == u16::from(self.sequence[self.sequence_index]) {
|
|
273
|
+
self.sequence_index += 1;
|
|
274
|
+
} else if self.sequence_index == 0 {
|
|
275
|
+
// htmlparser2 examines every unit of a title to decode entities.
|
|
276
|
+
if !is_title && self.fast_forward_to(LT) {
|
|
277
|
+
self.sequence_index = 1;
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
if is_title && self.decode && c == AMP {
|
|
281
|
+
// After a partial `</title` match, htmlparser2 consumes `&`
|
|
282
|
+
// without starting an entity, so it stays undecoded.
|
|
283
|
+
let start = self.start();
|
|
284
|
+
self.emit_text(start, self.index, true);
|
|
285
|
+
self.emit_text(self.index, self.index + 1, false);
|
|
286
|
+
self.section_start = Some(self.index + 1);
|
|
287
|
+
}
|
|
288
|
+
self.sequence_index = usize::from(c == LT);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fn state_cdata_sequence(&mut self, c: u16) {
|
|
293
|
+
if c == u16::from(CDATA[self.sequence_index]) {
|
|
294
|
+
self.sequence_index += 1;
|
|
295
|
+
if self.sequence_index == CDATA.len() {
|
|
296
|
+
self.state = State::InCommentLike;
|
|
297
|
+
self.sequence = CDATA_END;
|
|
298
|
+
self.sequence_index = 0;
|
|
299
|
+
self.section_start = Some(self.index + 1);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
self.sequence_index = 0;
|
|
303
|
+
self.state = State::InDeclaration;
|
|
304
|
+
self.state_in_declaration(c);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn state_in_comment_like(&mut self, c: u16) {
|
|
309
|
+
if c == u16::from(self.sequence[self.sequence_index]) {
|
|
310
|
+
self.sequence_index += 1;
|
|
311
|
+
if self.sequence_index == self.sequence.len() {
|
|
312
|
+
let start = self.start();
|
|
313
|
+
let token = if self.sequence == CDATA_END {
|
|
314
|
+
Token::Cdata {
|
|
315
|
+
start,
|
|
316
|
+
end: self.index,
|
|
317
|
+
end_offset: 2,
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
Token::Comment {
|
|
321
|
+
start,
|
|
322
|
+
end: self.index,
|
|
323
|
+
end_offset: 2,
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
(self.emit)(token);
|
|
327
|
+
self.sequence_index = 0;
|
|
328
|
+
self.section_start = Some(self.index + 1);
|
|
329
|
+
self.state = State::Text;
|
|
330
|
+
}
|
|
331
|
+
} else if self.sequence_index == 0 {
|
|
332
|
+
if self.fast_forward_to(u16::from(self.sequence[0])) {
|
|
333
|
+
self.sequence_index = 1;
|
|
334
|
+
}
|
|
335
|
+
} else if c != u16::from(self.sequence[self.sequence_index - 1]) {
|
|
336
|
+
self.sequence_index = 0;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// HTML tag names start with an ASCII letter; XML allows anything that
|
|
341
|
+
/// does not end the tag section.
|
|
342
|
+
fn is_tag_start_char(&self, c: u16) -> bool {
|
|
343
|
+
if self.xml {
|
|
344
|
+
!is_end_of_tag_section(c)
|
|
345
|
+
} else {
|
|
346
|
+
is_ascii_alpha(c)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
fn start_special(&mut self, sequence: &'static [u8], offset: usize) {
|
|
351
|
+
self.is_special = true;
|
|
352
|
+
self.sequence = sequence;
|
|
353
|
+
self.sequence_index = offset;
|
|
354
|
+
self.state = State::SpecialStartSequence;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fn state_before_tag_name(&mut self, c: u16) {
|
|
358
|
+
if c == EXCLAMATION_MARK {
|
|
359
|
+
self.state = State::BeforeDeclaration;
|
|
360
|
+
self.section_start = Some(self.index + 1);
|
|
361
|
+
} else if c == QUESTION_MARK {
|
|
362
|
+
self.state = State::InProcessingInstruction;
|
|
363
|
+
self.section_start = Some(self.index + 1);
|
|
364
|
+
} else if self.is_tag_start_char(c) {
|
|
365
|
+
let lowered = lower(c);
|
|
366
|
+
self.section_start = Some(self.index);
|
|
367
|
+
self.state = if self.xml {
|
|
368
|
+
State::InTagName
|
|
369
|
+
} else if lowered == u16::from(SCRIPT_END[2]) {
|
|
370
|
+
State::BeforeSpecialS
|
|
371
|
+
} else if lowered == u16::from(TITLE_END[2]) || lowered == u16::from(XMP_END[2]) {
|
|
372
|
+
State::BeforeSpecialT
|
|
373
|
+
} else {
|
|
374
|
+
State::InTagName
|
|
375
|
+
};
|
|
376
|
+
} else if c == SLASH {
|
|
377
|
+
self.state = State::BeforeClosingTagName;
|
|
378
|
+
} else {
|
|
379
|
+
self.state = State::Text;
|
|
380
|
+
self.state_text(c);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fn state_in_tag_name(&mut self, c: u16) {
|
|
385
|
+
if is_end_of_tag_section(c) {
|
|
386
|
+
let start = self.start();
|
|
387
|
+
(self.emit)(Token::OpenTagName {
|
|
388
|
+
start,
|
|
389
|
+
end: self.index,
|
|
390
|
+
});
|
|
391
|
+
self.section_start = None;
|
|
392
|
+
self.state = State::BeforeAttributeName;
|
|
393
|
+
self.state_before_attribute_name(c);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
fn state_before_closing_tag_name(&mut self, c: u16) {
|
|
398
|
+
if c == GT {
|
|
399
|
+
self.state = State::Text;
|
|
400
|
+
} else if !is_whitespace(c) {
|
|
401
|
+
self.state = if self.is_tag_start_char(c) {
|
|
402
|
+
State::InClosingTagName
|
|
403
|
+
} else {
|
|
404
|
+
State::InSpecialComment
|
|
405
|
+
};
|
|
406
|
+
self.section_start = Some(self.index);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fn state_in_closing_tag_name(&mut self, c: u16) {
|
|
411
|
+
if c == GT || is_whitespace(c) {
|
|
412
|
+
let start = self.start();
|
|
413
|
+
(self.emit)(Token::CloseTag {
|
|
414
|
+
start,
|
|
415
|
+
end: self.index,
|
|
416
|
+
});
|
|
417
|
+
self.section_start = None;
|
|
418
|
+
self.state = State::AfterClosingTagName;
|
|
419
|
+
self.state_after_closing_tag_name(c);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fn state_after_closing_tag_name(&mut self, c: u16) {
|
|
424
|
+
if c == GT || self.fast_forward_to(GT) {
|
|
425
|
+
self.state = State::Text;
|
|
426
|
+
self.section_start = Some(self.index + 1);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
fn state_before_attribute_name(&mut self, c: u16) {
|
|
431
|
+
if c == GT {
|
|
432
|
+
(self.emit)(Token::OpenTagEnd { end: self.index });
|
|
433
|
+
if self.is_special {
|
|
434
|
+
self.state = State::InSpecialTag;
|
|
435
|
+
self.sequence_index = 0;
|
|
436
|
+
} else {
|
|
437
|
+
self.state = State::Text;
|
|
438
|
+
}
|
|
439
|
+
self.section_start = Some(self.index + 1);
|
|
440
|
+
} else if c == SLASH {
|
|
441
|
+
self.state = State::InSelfClosingTag;
|
|
442
|
+
} else if !is_whitespace(c) {
|
|
443
|
+
self.state = State::InAttributeName;
|
|
444
|
+
self.section_start = Some(self.index);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
fn state_in_self_closing_tag(&mut self, c: u16) {
|
|
449
|
+
if c == GT {
|
|
450
|
+
(self.emit)(Token::SelfClosingTag { end: self.index });
|
|
451
|
+
self.state = State::Text;
|
|
452
|
+
self.section_start = Some(self.index + 1);
|
|
453
|
+
self.is_special = false;
|
|
454
|
+
} else if !is_whitespace(c) {
|
|
455
|
+
self.state = State::BeforeAttributeName;
|
|
456
|
+
self.state_before_attribute_name(c);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fn state_in_attribute_name(&mut self, c: u16) {
|
|
461
|
+
if c == EQ || is_end_of_tag_section(c) {
|
|
462
|
+
let start = self.start();
|
|
463
|
+
(self.emit)(Token::AttributeName {
|
|
464
|
+
start,
|
|
465
|
+
end: self.index,
|
|
466
|
+
});
|
|
467
|
+
self.section_start = Some(self.index);
|
|
468
|
+
self.state = State::AfterAttributeName;
|
|
469
|
+
self.state_after_attribute_name(c);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
fn state_after_attribute_name(&mut self, c: u16) {
|
|
474
|
+
if c == EQ {
|
|
475
|
+
self.state = State::BeforeAttributeValue;
|
|
476
|
+
} else if c == SLASH || c == GT {
|
|
477
|
+
let end = self.start();
|
|
478
|
+
(self.emit)(Token::AttributeEnd { end });
|
|
479
|
+
self.section_start = None;
|
|
480
|
+
self.state = State::BeforeAttributeName;
|
|
481
|
+
self.state_before_attribute_name(c);
|
|
482
|
+
} else if !is_whitespace(c) {
|
|
483
|
+
let end = self.start();
|
|
484
|
+
(self.emit)(Token::AttributeEnd { end });
|
|
485
|
+
self.state = State::InAttributeName;
|
|
486
|
+
self.section_start = Some(self.index);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
fn state_before_attribute_value(&mut self, c: u16) {
|
|
491
|
+
if c == DOUBLE_QUOTE {
|
|
492
|
+
self.state = State::InAttributeValueDq;
|
|
493
|
+
self.section_start = Some(self.index + 1);
|
|
494
|
+
} else if c == SINGLE_QUOTE {
|
|
495
|
+
self.state = State::InAttributeValueSq;
|
|
496
|
+
self.section_start = Some(self.index + 1);
|
|
497
|
+
} else if !is_whitespace(c) {
|
|
498
|
+
self.section_start = Some(self.index);
|
|
499
|
+
self.state = State::InAttributeValueNq;
|
|
500
|
+
self.state_in_attribute_value_unquoted(c);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
fn state_in_attribute_value_quoted(&mut self, c: u16, quote: u16) {
|
|
505
|
+
if c == quote || self.fast_forward_to(quote) {
|
|
506
|
+
let start = self.start();
|
|
507
|
+
(self.emit)(Token::AttributeData {
|
|
508
|
+
start,
|
|
509
|
+
end: self.index,
|
|
510
|
+
decode: self.decode,
|
|
511
|
+
});
|
|
512
|
+
self.section_start = None;
|
|
513
|
+
(self.emit)(Token::AttributeEnd {
|
|
514
|
+
end: self.index + 1,
|
|
515
|
+
});
|
|
516
|
+
self.state = State::BeforeAttributeName;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
fn state_in_attribute_value_unquoted(&mut self, c: u16) {
|
|
521
|
+
if is_whitespace(c) || c == GT {
|
|
522
|
+
let start = self.start();
|
|
523
|
+
(self.emit)(Token::AttributeData {
|
|
524
|
+
start,
|
|
525
|
+
end: self.index,
|
|
526
|
+
decode: self.decode,
|
|
527
|
+
});
|
|
528
|
+
self.section_start = None;
|
|
529
|
+
(self.emit)(Token::AttributeEnd { end: self.index });
|
|
530
|
+
self.state = State::BeforeAttributeName;
|
|
531
|
+
self.state_before_attribute_name(c);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fn state_before_declaration(&mut self, c: u16) {
|
|
536
|
+
if c == OPENING_SQUARE_BRACKET {
|
|
537
|
+
self.state = State::CdataSequence;
|
|
538
|
+
self.sequence_index = 0;
|
|
539
|
+
} else {
|
|
540
|
+
self.state = if c == DASH {
|
|
541
|
+
State::BeforeComment
|
|
542
|
+
} else {
|
|
543
|
+
State::InDeclaration
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
fn state_in_declaration(&mut self, c: u16) {
|
|
549
|
+
if c == GT || self.fast_forward_to(GT) {
|
|
550
|
+
let start = self.start();
|
|
551
|
+
(self.emit)(Token::Declaration {
|
|
552
|
+
start,
|
|
553
|
+
end: self.index,
|
|
554
|
+
});
|
|
555
|
+
self.state = State::Text;
|
|
556
|
+
self.section_start = Some(self.index + 1);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
fn state_in_processing_instruction(&mut self, c: u16) {
|
|
561
|
+
if c == GT || self.fast_forward_to(GT) {
|
|
562
|
+
let start = self.start();
|
|
563
|
+
(self.emit)(Token::ProcessingInstruction {
|
|
564
|
+
start,
|
|
565
|
+
end: self.index,
|
|
566
|
+
});
|
|
567
|
+
self.state = State::Text;
|
|
568
|
+
self.section_start = Some(self.index + 1);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
fn state_before_comment(&mut self, c: u16) {
|
|
573
|
+
if c == DASH {
|
|
574
|
+
self.state = State::InCommentLike;
|
|
575
|
+
self.sequence = COMMENT_END;
|
|
576
|
+
// A comment may close immediately, as in `<!-->`.
|
|
577
|
+
self.sequence_index = 2;
|
|
578
|
+
self.section_start = Some(self.index + 1);
|
|
579
|
+
} else {
|
|
580
|
+
self.state = State::InDeclaration;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
fn state_in_special_comment(&mut self, c: u16) {
|
|
585
|
+
if c == GT || self.fast_forward_to(GT) {
|
|
586
|
+
let start = self.start();
|
|
587
|
+
(self.emit)(Token::Comment {
|
|
588
|
+
start,
|
|
589
|
+
end: self.index,
|
|
590
|
+
end_offset: 0,
|
|
591
|
+
});
|
|
592
|
+
self.state = State::Text;
|
|
593
|
+
self.section_start = Some(self.index + 1);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
fn state_before_special_s(&mut self, c: u16) {
|
|
598
|
+
let lowered = lower(c);
|
|
599
|
+
if lowered == u16::from(SCRIPT_END[3]) {
|
|
600
|
+
self.start_special(SCRIPT_END, 4);
|
|
601
|
+
} else if lowered == u16::from(STYLE_END[3]) {
|
|
602
|
+
self.start_special(STYLE_END, 4);
|
|
603
|
+
} else {
|
|
604
|
+
self.state = State::InTagName;
|
|
605
|
+
self.state_in_tag_name(c);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
fn state_before_special_t(&mut self, c: u16) {
|
|
610
|
+
let lowered = lower(c);
|
|
611
|
+
if lowered == u16::from(TITLE_END[3]) {
|
|
612
|
+
self.start_special(TITLE_END, 4);
|
|
613
|
+
} else if lowered == u16::from(TEXTAREA_END[3]) {
|
|
614
|
+
self.start_special(TEXTAREA_END, 4);
|
|
615
|
+
} else if lowered == u16::from(XMP_END[3]) {
|
|
616
|
+
self.start_special(XMP_END, 4);
|
|
617
|
+
} else {
|
|
618
|
+
self.state = State::InTagName;
|
|
619
|
+
self.state_in_tag_name(c);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/// Mirrors htmlparser2's end-of-buffer cleanup followed by `finish`.
|
|
624
|
+
fn finish(&mut self) {
|
|
625
|
+
let end = self.units.len();
|
|
626
|
+
let start = self.start();
|
|
627
|
+
match self.state {
|
|
628
|
+
State::Text => self.emit_text(start, end, self.decode),
|
|
629
|
+
State::InSpecialTag => {
|
|
630
|
+
let decode = self.sequence == TITLE_END && self.decode;
|
|
631
|
+
self.emit_text(start, end, decode);
|
|
632
|
+
}
|
|
633
|
+
State::InAttributeValueDq | State::InAttributeValueSq | State::InAttributeValueNq => {
|
|
634
|
+
// An unfinished tag is dropped, as htmlparser2 does.
|
|
635
|
+
}
|
|
636
|
+
State::InCommentLike if start < end => {
|
|
637
|
+
let token = if self.sequence == CDATA_END {
|
|
638
|
+
Token::Cdata {
|
|
639
|
+
start,
|
|
640
|
+
end,
|
|
641
|
+
end_offset: 0,
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
Token::Comment {
|
|
645
|
+
start,
|
|
646
|
+
end,
|
|
647
|
+
end_offset: 0,
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
(self.emit)(token);
|
|
651
|
+
}
|
|
652
|
+
State::InTagName
|
|
653
|
+
| State::BeforeAttributeName
|
|
654
|
+
| State::BeforeAttributeValue
|
|
655
|
+
| State::AfterAttributeName
|
|
656
|
+
| State::InAttributeName
|
|
657
|
+
| State::InClosingTagName => {}
|
|
658
|
+
// Other states report their remaining input as undecoded text. An
|
|
659
|
+
// unset section start is -1 in htmlparser2, whose `slice(-1)` keeps
|
|
660
|
+
// only the final unit.
|
|
661
|
+
_ => {
|
|
662
|
+
let start = self.section_start.unwrap_or(end.saturating_sub(1));
|
|
663
|
+
self.emit_text(start, end, false);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
(self.emit)(Token::End);
|
|
667
|
+
}
|
|
668
|
+
}
|