1use crate::base::Bytes;
2use memchr::{memchr, memchr3};
3
4#[macro_use]
5mod tag;
6
7mod local_name;
8mod namespace;
9mod text_type;
10
11pub use self::local_name::{LocalName, LocalNameHash};
12pub use self::namespace::Namespace;
13pub use self::tag::Tag;
14pub use self::text_type::TextType;
15
16#[inline]
18pub(crate) fn escape_body_text(mut content: &str, output_handler: &mut impl FnMut(&str)) {
19 loop {
20 if let Some(pos) = memchr3(b'&', b'<', b'>', content.as_bytes()) {
21 let Some((chunk_before, rest)) = content.split_at_checked(pos) else {
22 return;
23 };
24 let Some((matched, rest)) = rest.split_at_checked(1) else {
25 return;
26 };
27
28 content = rest;
29 let matched = matched.as_bytes()[0];
30
31 if !chunk_before.is_empty() {
32 (output_handler)(chunk_before);
33 }
34 (output_handler)(match matched {
35 b'<' => "<",
36 b'>' => ">",
37 _ => "&",
38 });
39 } else {
40 if !content.is_empty() {
41 (output_handler)(content);
42 }
43 return;
44 }
45 }
46}
47
48pub(crate) fn escape_double_quotes_only(content: Bytes<'_>, output_handler: &mut dyn FnMut(&[u8])) {
50 let mut content = &*content;
51 loop {
52 if let Some(pos) = memchr(b'"', content) {
53 let Some((chunk_before, rest)) = content
54 .split_at_checked(pos)
55 .and_then(|(before, rest)| Some((before, rest.get(1..)?)))
56 else {
57 return;
58 };
59 content = rest;
60
61 if !chunk_before.is_empty() {
62 (output_handler)(chunk_before);
63 }
64 (output_handler)(b""");
65 } else {
66 if !content.is_empty() {
67 (output_handler)(content);
68 }
69 return;
70 }
71 }
72}