Skip to main content

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/// * `<` => `&lt;`
14/// * `>` => `&gt;`
15/// * `"` => `&quot;`
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/// * `&` => `&amp;`
66/// * `<` => `&lt;`
67/// * `>` => `&gt;`
68/// * `"` => `&quot;`
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/// * `&` => `&amp;`
85/// * `<` => `&lt;`
86/// * `>` => `&gt;`
87/// * `"` => `&quot;`
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/// * `&` => `&amp;`
129/// * `<` => `&lt;`
130/// * `>` => `&gt;`
131/// * `"` => `&quot;`
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}