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