html_escape/encode/html_entity/unquoted_attribute.rs
1use alloc::{borrow::Cow, string::String, vec::Vec};
2use core::str::from_utf8_unchecked;
3#[cfg(feature = "std")]
4use std::io::{self, Write};
5
6use crate::functions::*;
7
8/// Encode text used in an unquoted attribute. Except for alphanumeric characters, escape all characters which are less than 128.
9///
10/// The following characters are escaped to named entities:
11///
12/// * `&` => `&`
13/// * `<` => `<`
14/// * `>` => `>`
15/// * `"` => `"`
16///
17/// Other non-alphanumeric characters are escaped to `&#xHH;`.
18/// Use a quoted attribute for an empty value; this function does not add quotes.
19pub fn encode_unquoted_attribute<S: ?Sized + AsRef<str>>(text: &S) -> Cow<'_, str> {
20 let text = text.as_ref();
21 let text_bytes = text.as_bytes();
22
23 let text_length = text_bytes.len();
24
25 let mut p = 0;
26 let mut e;
27
28 loop {
29 if p == text_length {
30 return Cow::from(text);
31 }
32
33 e = text_bytes[p];
34
35 if e.is_ascii() && !e.is_ascii_alphanumeric() {
36 break;
37 }
38
39 p += 1;
40 }
41
42 let mut v = Vec::with_capacity(text_length + 5);
43
44 v.extend_from_slice(&text_bytes[..p]);
45
46 write_html_entity_to_vec(e, &mut v);
47
48 encode_unquoted_attribute_to_vec(
49 // SAFETY: `text_bytes[p]` is the ASCII byte which has just been escaped, so the rest is valid UTF-8.
50 unsafe { from_utf8_unchecked(&text_bytes[(p + 1)..]) },
51 &mut v,
52 );
53
54 // SAFETY: `v` only contains slices of `text`, split at ASCII bytes, plus ASCII entities, so it is valid UTF-8.
55 Cow::from(unsafe { String::from_utf8_unchecked(v) })
56}
57
58/// Write text used in an unquoted attribute to a mutable `String` reference and return the encoded string slice. Except for alphanumeric characters, escape all characters which are less than 128.
59///
60/// This appends to `output` and returns only the new string slice.
61/// Use a quoted attribute for an empty value; this function does not add quotes.
62///
63/// The following characters are escaped to named entities:
64///
65/// * `&` => `&`
66/// * `<` => `<`
67/// * `>` => `>`
68/// * `"` => `"`
69///
70/// Other non-alphanumeric characters are escaped to `&#xHH;`.
71#[inline]
72pub fn encode_unquoted_attribute_to_string<S: AsRef<str>>(text: S, output: &mut String) -> &str {
73 // SAFETY: the encoded data is valid UTF-8, so `output` remains a valid `String`.
74 unsafe { from_utf8_unchecked(encode_unquoted_attribute_to_vec(text, output.as_mut_vec())) }
75}
76
77/// Write text used in an unquoted attribute to a mutable `Vec<u8>` reference and return the encoded data slice. Except for alphanumeric characters, escape all characters which are less than 128.
78///
79/// This appends to `output` and returns only the new data slice.
80/// Use a quoted attribute for an empty value; this function does not add quotes.
81///
82/// The following characters are escaped to named entities:
83///
84/// * `&` => `&`
85/// * `<` => `<`
86/// * `>` => `>`
87/// * `"` => `"`
88///
89/// Other non-alphanumeric characters are escaped to `&#xHH;`.
90pub fn encode_unquoted_attribute_to_vec<S: AsRef<str>>(text: S, output: &mut Vec<u8>) -> &[u8] {
91 let text = text.as_ref();
92 let text_bytes = text.as_bytes();
93 let text_length = text_bytes.len();
94
95 output.reserve(text_length);
96
97 let current_length = output.len();
98
99 let mut p = 0;
100 let mut e;
101
102 let mut start = 0;
103
104 while p < text_length {
105 e = text_bytes[p];
106
107 if e.is_ascii() && !e.is_ascii_alphanumeric() {
108 output.extend_from_slice(&text_bytes[start..p]);
109 start = p + 1;
110 write_html_entity_to_vec(e, output);
111 }
112
113 p += 1;
114 }
115
116 output.extend_from_slice(&text_bytes[start..p]);
117
118 &output[current_length..]
119}
120
121#[cfg(feature = "std")]
122/// Write text used in an unquoted attribute to a writer. Except for alphanumeric characters, escape all characters which are less than 128.
123///
124/// Use a quoted attribute for an empty value; this function does not add quotes.
125///
126/// The following characters are escaped to named entities:
127///
128/// * `&` => `&`
129/// * `<` => `<`
130/// * `>` => `>`
131/// * `"` => `"`
132///
133/// Other non-alphanumeric characters are escaped to `&#xHH;`.
134pub fn encode_unquoted_attribute_to_writer<S: AsRef<str>, W: Write + ?Sized>(
135 text: S,
136 output: &mut W,
137) -> Result<(), io::Error> {
138 let text = text.as_ref();
139 let text_bytes = text.as_bytes();
140 let text_length = text_bytes.len();
141
142 let mut p = 0;
143 let mut e;
144
145 let mut start = 0;
146
147 while p < text_length {
148 e = text_bytes[p];
149
150 if e.is_ascii() && !e.is_ascii_alphanumeric() {
151 output.write_all(&text_bytes[start..p])?;
152 start = p + 1;
153 write_html_entity_to_writer(e, output)?;
154 }
155
156 p += 1;
157 }
158
159 output.write_all(&text_bytes[start..p])
160}