html_escape/decode/html_entity/
mod.rs1mod tables;
2
3use alloc::{borrow::Cow, string::String, vec::Vec};
4use core::{convert::TryFrom, str::from_utf8_unchecked};
5#[cfg(feature = "std")]
6use std::io::{self, Write};
7
8pub use tables::*;
9
10use crate::functions::*;
11
12#[derive(Clone, Copy)]
13enum DecodedEntityValue {
14 Named(&'static str),
15 Character(char),
16}
17
18#[derive(Clone, Copy)]
19struct DecodedEntity {
20 start: usize,
21 end: usize,
22 value: DecodedEntityValue,
23}
24
25#[inline]
26fn decode_named_entity(name: &[u8]) -> Option<DecodedEntityValue> {
27 NAMED_ENTITIES
28 .binary_search_by(|(t_name, _)| t_name.cmp(&name))
29 .ok()
30 .map(|index| DecodedEntityValue::Named(NAMED_ENTITIES[index].1))
31}
32
33#[inline]
34fn decode_decimal_entity(text: &str, start: usize, end: usize) -> Option<DecodedEntityValue> {
35 let number = text[start..end].parse::<u32>().ok()?;
36 let character = char::try_from(number).ok()?;
37
38 Some(DecodedEntityValue::Character(character))
39}
40
41#[inline]
42fn decode_hex_entity(text: &str, start: usize, end: usize) -> Option<DecodedEntityValue> {
43 let number = u32::from_str_radix(&text[start..end], 16).ok()?;
44 let character = char::try_from(number).ok()?;
45
46 Some(DecodedEntityValue::Character(character))
47}
48
49fn find_decoded_entity(text: &str, start: usize) -> Option<DecodedEntity> {
50 let text_bytes = text.as_bytes();
51 let text_length = text_bytes.len();
52
53 let mut p = start;
54
55 'search: while p < text_length {
56 if text_bytes[p] != b'&' {
57 p += 1;
58
59 continue;
60 }
61
62 let entity_start = p;
63
64 p += 1;
65
66 if p == text_length {
67 return None;
68 }
69
70 match text_bytes[p] {
71 b'&' => continue 'search,
72 b';' => {
73 p += 1;
74 },
75 b'#' => {
76 p += 1;
77
78 if p == text_length {
79 return None;
80 }
81
82 match text_bytes[p] {
83 b'&' => continue 'search,
84 b';' => {
85 p += 1;
86 },
87 b'x' | b'X' => {
88 p += 1;
89
90 if p == text_length {
91 return None;
92 }
93
94 match text_bytes[p] {
95 b'&' => continue 'search,
96 b';' => {
97 p += 1;
98 },
99 _ => {
100 let hex_start = p;
101
102 loop {
103 p += 1;
104
105 if p == text_length {
106 return None;
107 }
108
109 match text_bytes[p] {
110 b'&' => continue 'search,
111 b';' => {
112 if let Some(value) =
113 decode_hex_entity(text, hex_start, p)
114 {
115 return Some(DecodedEntity {
116 start: entity_start,
117 end: p + 1,
118 value,
119 });
120 }
121
122 p += 1;
123
124 continue 'search;
125 },
126 _ => (),
127 }
128 }
129 },
130 }
131 },
132 _ => {
133 let number_start = p;
134
135 loop {
136 p += 1;
137
138 if p == text_length {
139 return None;
140 }
141
142 match text_bytes[p] {
143 b'&' => continue 'search,
144 b';' => {
145 if let Some(value) =
146 decode_decimal_entity(text, number_start, p)
147 {
148 return Some(DecodedEntity {
149 start: entity_start,
150 end: p + 1,
151 value,
152 });
153 }
154
155 p += 1;
156
157 continue 'search;
158 },
159 _ => (),
160 }
161 }
162 },
163 }
164 },
165 _ => {
166 let name_start = p;
167
168 loop {
169 p += 1;
170
171 if p == text_length {
172 return None;
173 }
174
175 match text_bytes[p] {
176 b'&' => continue 'search,
177 b';' => {
178 if let Some(value) = decode_named_entity(&text_bytes[name_start..p]) {
179 return Some(DecodedEntity {
180 start: entity_start,
181 end: p + 1,
182 value,
183 });
184 }
185
186 p += 1;
187
188 continue 'search;
189 },
190 _ => (),
191 }
192 }
193 },
194 }
195 }
196
197 None
198}
199
200#[inline]
201fn write_decoded_entity_to_vec(value: DecodedEntityValue, output: &mut Vec<u8>) {
202 match value {
203 DecodedEntityValue::Named(entity) => output.extend_from_slice(entity.as_bytes()),
204 DecodedEntityValue::Character(character) => write_char_to_vec(character, output),
205 }
206}
207
208#[cfg(feature = "std")]
209#[inline]
210fn write_decoded_entity_to_writer<W: Write>(
211 value: DecodedEntityValue,
212 output: &mut W,
213) -> Result<(), io::Error> {
214 match value {
215 DecodedEntityValue::Named(entity) => output.write_all(entity.as_bytes()),
216 DecodedEntityValue::Character(character) => write_char_to_writer(character, output),
217 }
218}
219
220pub fn decode_html_entities<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
222 let text = text.as_ref();
223 let text_bytes = text.as_bytes();
224 let text_length = text_bytes.len();
225
226 let entity = match find_decoded_entity(text, 0) {
227 Some(entity) => entity,
228 None => return Cow::from(text),
229 };
230
231 let mut v = Vec::with_capacity(text_length);
232 let mut start = entity.end;
233
234 v.extend_from_slice(&text_bytes[..entity.start]);
235 write_decoded_entity_to_vec(entity.value, &mut v);
236
237 while let Some(entity) = find_decoded_entity(text, start) {
238 v.extend_from_slice(&text_bytes[start..entity.start]);
239 write_decoded_entity_to_vec(entity.value, &mut v);
240 start = entity.end;
241 }
242
243 v.extend_from_slice(&text_bytes[start..]);
244
245 Cow::from(unsafe { String::from_utf8_unchecked(v) })
246}
247
248pub fn decode_html_entities_to_string<S: AsRef<str>>(text: S, output: &mut String) -> &str {
250 unsafe { from_utf8_unchecked(decode_html_entities_to_vec(text, output.as_mut_vec())) }
251}
252
253pub fn decode_html_entities_to_vec<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
255 let text = text.as_ref();
256 let text_bytes = text.as_bytes();
257 let text_length = text_bytes.len();
258
259 output.reserve(text_length);
260
261 let current_length = output.len();
262
263 let mut start = 0;
264
265 while let Some(entity) = find_decoded_entity(text, start) {
266 output.extend_from_slice(&text_bytes[start..entity.start]);
267 write_decoded_entity_to_vec(entity.value, output);
268 start = entity.end;
269 }
270
271 output.extend_from_slice(&text_bytes[start..]);
272
273 &output[current_length..]
274}
275
276#[cfg(feature = "std")]
277pub fn decode_html_entities_to_writer<S: AsRef<str>, W: Write>(
279 text: S,
280 output: &mut W,
281) -> Result<(), io::Error> {
282 let text = text.as_ref();
283 let text_bytes = text.as_bytes();
284
285 let mut start = 0;
286
287 while let Some(entity) = find_decoded_entity(text, start) {
288 output.write_all(&text_bytes[start..entity.start])?;
289 write_decoded_entity_to_writer(entity.value, output)?;
290 start = entity.end;
291 }
292
293 output.write_all(&text_bytes[start..])
294}