Skip to main content

oxixml_xml/events/
attributes.rs

1//! XML attributes module.
2//!
3//! Provides an iterator ([`Attributes`]) over attribute key/value pairs, the
4//! [`Attribute`] value type, the raw [`Attr`] span type, and the [`AttrError`]
5//! error, mirroring quick-xml 0.41 `events::attributes`.
6
7use crate::XmlVersion;
8use crate::encoding::Decoder;
9use crate::errors::Result as XmlResult;
10use crate::escape::{escape, resolve_predefined_entity};
11use crate::name::{LocalName, Namespace, NamespaceResolver, QName};
12use crate::utils::{Bytes, is_whitespace};
13
14use std::collections::HashSet;
15use std::fmt::{self, Debug, Display, Formatter};
16use std::hash::{BuildHasherDefault, DefaultHasher, Hasher};
17use std::iter::FusedIterator;
18use std::{borrow::Cow, ops::Range};
19
20/// A key/value XML attribute.
21///
22/// The `value` field stores raw bytes, possibly containing escape sequences. Most
23/// users will want the value via [`normalized_value`](Self::normalized_value) or
24/// [`decoded_and_normalized_value`](Self::decoded_and_normalized_value).
25#[derive(Clone, Eq, PartialEq)]
26pub struct Attribute<'a> {
27    /// The key that uniquely identifies the attribute.
28    ///
29    /// If [`Attributes::with_checks`] is turned off, the key might not be unique.
30    pub key: QName<'a>,
31    /// The raw value of the attribute.
32    pub value: Cow<'a, [u8]>,
33}
34
35impl<'a> Attribute<'a> {
36    /// Returns the attribute value normalized as per [the XML specification].
37    ///
38    /// The document **must** be UTF-8 encoded. The characters `\t`, `\r`, `\n` are
39    /// replaced with a space (`0x20`), and the five predefined entities are
40    /// unescaped. This allocates unless no normalization is required.
41    ///
42    /// [the XML specification]: https://www.w3.org/TR/xml11/#AVNormalize
43    pub fn normalized_value(&self, version: XmlVersion) -> XmlResult<Cow<'a, str>> {
44        // `resolve_predefined_entity` returns only non-recursive replacements,
45        // so a depth of 1 is enough.
46        self.normalized_value_with(version, 1, resolve_predefined_entity)
47    }
48
49    /// Returns the attribute value normalized as per [the XML specification], using
50    /// a custom entity resolver. The document **must** be UTF-8 encoded.
51    ///
52    /// # Parameters
53    ///
54    /// - `depth`: maximum number of nested entities that can be expanded before
55    ///   [`EscapeError::TooManyNestedEntities`] is returned;
56    /// - `resolve_entity`: resolves named entities (predefined entities are not
57    ///   resolved by this function, but you may call [`resolve_predefined_entity`]).
58    ///
59    /// [the XML specification]: https://www.w3.org/TR/xml11/#AVNormalize
60    /// [`EscapeError::TooManyNestedEntities`]: crate::escape::EscapeError::TooManyNestedEntities
61    pub fn normalized_value_with<'entity>(
62        &self,
63        version: XmlVersion,
64        depth: usize,
65        resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
66    ) -> XmlResult<Cow<'a, str>> {
67        use crate::encoding::EncodingError;
68        use std::str::from_utf8;
69
70        let decoded = match &self.value {
71            Cow::Borrowed(bytes) => Cow::Borrowed(from_utf8(bytes).map_err(EncodingError::Utf8)?),
72            Cow::Owned(bytes) => {
73                Cow::Owned(from_utf8(bytes).map_err(EncodingError::Utf8)?.to_owned())
74            }
75        };
76
77        match version.normalize_attribute_value(&decoded, depth, resolve_entity)? {
78            Cow::Borrowed(_) => Ok(decoded),
79            Cow::Owned(s) => Ok(s.into()),
80        }
81    }
82
83    /// Decodes using the provided decoder and returns the attribute value
84    /// normalized as per [the XML specification].
85    ///
86    /// [the XML specification]: https://www.w3.org/TR/xml11/#AVNormalize
87    pub fn decoded_and_normalized_value(
88        &self,
89        version: XmlVersion,
90        decoder: Decoder,
91    ) -> XmlResult<Cow<'a, str>> {
92        self.decoded_and_normalized_value_with(version, decoder, 1, resolve_predefined_entity)
93    }
94
95    /// Decodes using the provided decoder and returns the attribute value
96    /// normalized as per [the XML specification], using a custom entity resolver.
97    ///
98    /// [the XML specification]: https://www.w3.org/TR/xml11/#AVNormalize
99    pub fn decoded_and_normalized_value_with<'entity>(
100        &self,
101        version: XmlVersion,
102        decoder: Decoder,
103        depth: usize,
104        resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
105    ) -> XmlResult<Cow<'a, str>> {
106        let decoded = match &self.value {
107            Cow::Borrowed(bytes) => decoder.decode(bytes)?,
108            Cow::Owned(bytes) => decoder.decode(bytes)?.into_owned().into(),
109        };
110
111        match version.normalize_attribute_value(&decoded, depth, resolve_entity)? {
112            Cow::Borrowed(_) => Ok(decoded),
113            Cow::Owned(s) => Ok(s.into()),
114        }
115    }
116
117    /// Returns the unescaped value.
118    ///
119    /// Escape sequences such as `&gt;` are replaced with their equivalents. This
120    /// allocates if the value contains any escape sequences.
121    #[cfg(any(doc, not(feature = "encoding")))]
122    #[deprecated = "use `Self::normalized_value()`"]
123    pub fn unescape_value(&self) -> XmlResult<Cow<'a, str>> {
124        self.normalized_value_with(XmlVersion::Implicit1_0, 1, resolve_predefined_entity)
125    }
126
127    /// Decodes using UTF-8, then unescapes the value using a custom entity resolver.
128    #[cfg(any(doc, not(feature = "encoding")))]
129    #[deprecated = "use `Self::normalized_value_with()`"]
130    #[inline]
131    pub fn unescape_value_with<'entity>(
132        &self,
133        resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
134    ) -> XmlResult<Cow<'a, str>> {
135        self.normalized_value_with(XmlVersion::Implicit1_0, 128, resolve_entity)
136    }
137
138    /// Decodes, then unescapes the value.
139    #[deprecated = "use `Self::decoded_and_normalized_value()`"]
140    pub fn decode_and_unescape_value(&self, decoder: Decoder) -> XmlResult<Cow<'a, str>> {
141        self.decoded_and_normalized_value_with(
142            XmlVersion::Implicit1_0,
143            decoder,
144            1,
145            resolve_predefined_entity,
146        )
147    }
148
149    /// Decodes, then unescapes the value using a custom entity resolver.
150    #[deprecated = "use `Self::decoded_and_normalized_value_with()`"]
151    pub fn decode_and_unescape_value_with<'entity>(
152        &self,
153        decoder: Decoder,
154        resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
155    ) -> XmlResult<Cow<'a, str>> {
156        self.decoded_and_normalized_value_with(
157            XmlVersion::Implicit1_0,
158            decoder,
159            128,
160            resolve_entity,
161        )
162    }
163
164    /// If the attribute value [represents] a valid boolean, returns `Some`,
165    /// otherwise `None`. The valid representations are `"true"`, `"false"`, `"1"`
166    /// and `"0"`.
167    ///
168    /// ```
169    /// # use oxixml_xml::events::attributes::Attribute;
170    /// assert_eq!(Attribute::from(("attr", "false")).as_bool(), Some(false));
171    /// assert_eq!(Attribute::from(("attr", "1")).as_bool(), Some(true));
172    /// assert_eq!(Attribute::from(("attr", "not bool")).as_bool(), None);
173    /// ```
174    ///
175    /// [represents]: https://www.w3.org/TR/xmlschema11-2/#boolean
176    #[inline]
177    pub fn as_bool(&self) -> Option<bool> {
178        match self.value.as_ref() {
179            b"1" | b"true" => Some(true),
180            b"0" | b"false" => Some(false),
181            _ => None,
182        }
183    }
184}
185
186impl<'a> Debug for Attribute<'a> {
187    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
188        f.debug_struct("Attribute")
189            .field("key", &Bytes(self.key.as_ref()))
190            .field("value", &Bytes(&self.value))
191            .finish()
192    }
193}
194
195impl<'a> From<(&'a [u8], &'a [u8])> for Attribute<'a> {
196    /// Creates a new attribute from raw bytes, without transforming key or value.
197    fn from(val: (&'a [u8], &'a [u8])) -> Attribute<'a> {
198        Attribute {
199            key: QName(val.0),
200            value: Cow::from(val.1),
201        }
202    }
203}
204
205impl<'a> From<(&'a str, &'a str)> for Attribute<'a> {
206    /// Creates a new attribute from text. The key is stored as-is; the value is
207    /// escaped.
208    fn from(val: (&'a str, &'a str)) -> Attribute<'a> {
209        Attribute {
210            key: QName(val.0.as_bytes()),
211            value: match escape(val.1) {
212                Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
213                Cow::Owned(s) => Cow::Owned(s.into_bytes()),
214            },
215        }
216    }
217}
218
219impl<'a> From<(&'a str, Cow<'a, str>)> for Attribute<'a> {
220    /// Creates a new attribute from text. The key is stored as-is; the value is
221    /// escaped.
222    fn from(val: (&'a str, Cow<'a, str>)) -> Attribute<'a> {
223        Attribute {
224            key: QName(val.0.as_bytes()),
225            value: match escape(val.1) {
226                Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
227                Cow::Owned(s) => Cow::Owned(s.into_bytes()),
228            },
229        }
230    }
231}
232
233impl<'a> From<Attr<&'a [u8]>> for Attribute<'a> {
234    #[inline]
235    fn from(attr: Attr<&'a [u8]>) -> Self {
236        Self {
237            key: attr.key(),
238            value: Cow::Borrowed(attr.value()),
239        }
240    }
241}
242
243////////////////////////////////////////////////////////////////////////////////////////////////////
244
245/// Iterator over XML attributes.
246///
247/// Yields `Result<Attribute, AttrError>`. An `Err` is yielded if an attribute is
248/// malformed or duplicated. The duplicate check can be turned off with
249/// [`with_checks(false)`](Self::with_checks).
250#[derive(Clone)]
251pub struct Attributes<'a> {
252    /// Slice of the `BytesStart` corresponding to the attributes.
253    bytes: &'a [u8],
254    /// Iterator state, independent of the actual source of bytes.
255    state: IterState,
256    /// Encoding used for `bytes`.
257    decoder: Decoder,
258}
259
260impl<'a> Attributes<'a> {
261    /// Internal constructor, used by `BytesStart`; supplies data in the reader's encoding.
262    #[inline]
263    pub(crate) const fn wrap(buf: &'a [u8], pos: usize, html: bool, decoder: Decoder) -> Self {
264        Self {
265            bytes: buf,
266            state: IterState::new(pos, html),
267            decoder,
268        }
269    }
270
271    /// Creates a new XML-style attribute iterator over `buf`, where attributes are
272    /// of the form `name="value"` or `name='value'`. HTML-style attributes cause
273    /// an error.
274    ///
275    /// `pos` is the position in `buf` where the tag name ends and the attributes
276    /// begin.
277    ///
278    /// ```
279    /// # use oxixml_xml::events::attributes::{Attribute, Attributes};
280    /// let mut iter = Attributes::new("tag-name attr1 = 'value1' attr2='value2' ", 9);
281    /// assert_eq!(iter.next(), Some(Ok(Attribute::from(("attr1", "value1")))));
282    /// assert_eq!(iter.next(), Some(Ok(Attribute::from(("attr2", "value2")))));
283    /// assert_eq!(iter.next(), None);
284    /// ```
285    pub const fn new(buf: &'a str, pos: usize) -> Self {
286        Self::wrap(buf.as_bytes(), pos, false, Decoder::utf8())
287    }
288
289    /// Creates a new attribute iterator over `buf`, allowing HTML attribute syntax
290    /// (unquoted values and key-only attributes).
291    ///
292    /// ```
293    /// # use oxixml_xml::events::attributes::{Attribute, Attributes};
294    /// let mut iter = Attributes::html("tag-name attr1 = value1 attr2 ", 9);
295    /// assert_eq!(iter.next(), Some(Ok(Attribute::from(("attr1", "value1")))));
296    /// assert_eq!(iter.next(), Some(Ok(Attribute::from(("attr2", "")))));
297    /// assert_eq!(iter.next(), None);
298    /// ```
299    pub const fn html(buf: &'a str, pos: usize) -> Self {
300        Self::wrap(buf.as_bytes(), pos, true, Decoder::utf8())
301    }
302
303    /// Changes whether attributes are checked for uniqueness (`true` by default).
304    pub fn with_checks(&mut self, val: bool) -> &mut Attributes<'a> {
305        self.state.check_duplicates = val;
306        self
307    }
308
309    /// Checks whether the current tag has an [`xsi:nil`] attribute set to a truthy
310    /// value, resolving prefixes with `resolver`. Ignores attribute errors.
311    ///
312    /// ```
313    /// # use oxixml_xml::events::BytesStart;
314    /// # use oxixml_xml::name::{Namespace, NamespaceResolver, PrefixDeclaration};
315    /// let mut resolver = NamespaceResolver::default();
316    /// resolver.add(
317    ///     PrefixDeclaration::Named(b"xsi"),
318    ///     Namespace(b"http://www.w3.org/2001/XMLSchema-instance"),
319    /// ).expect("doc example");
320    ///
321    /// let tag = BytesStart::from_content("e xsi:nil='true'", 1);
322    /// assert!(tag.attributes().has_nil(&resolver));
323    ///
324    /// let tag = BytesStart::from_content("e xsi:nil='false'", 1);
325    /// assert!(!tag.attributes().has_nil(&resolver));
326    /// ```
327    ///
328    /// [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil
329    pub fn has_nil(&mut self, resolver: &NamespaceResolver) -> bool {
330        use crate::name::ResolveResult::*;
331
332        self.any(|attr| {
333            if let Ok(attr) = attr {
334                match resolver.resolve_attribute(attr.key) {
335                    (
336                        Bound(Namespace(b"http://www.w3.org/2001/XMLSchema-instance")),
337                        LocalName(b"nil"),
338                    ) => attr.as_bool().unwrap_or_default(),
339                    _ => false,
340                }
341            } else {
342                false
343            }
344        })
345    }
346
347    /// Gets the decoder used to decode the bytes of the reader that produced this
348    /// iterator. For manually-created iterators the encoding is UTF-8.
349    #[inline]
350    pub const fn decoder(&self) -> Decoder {
351        self.decoder
352    }
353}
354
355impl<'a> Debug for Attributes<'a> {
356    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
357        f.debug_struct("Attributes")
358            .field("bytes", &Bytes(self.bytes))
359            .field("state", &self.state)
360            .field("decoder", &self.decoder)
361            .finish()
362    }
363}
364
365impl<'a> Iterator for Attributes<'a> {
366    type Item = Result<Attribute<'a>, AttrError>;
367
368    #[inline]
369    fn next(&mut self) -> Option<Self::Item> {
370        match self.state.next(self.bytes) {
371            None => None,
372            Some(Ok(a)) => Some(Ok(a.map(|range| &self.bytes[range]).into())),
373            Some(Err(e)) => Some(Err(e)),
374        }
375    }
376}
377
378impl<'a> FusedIterator for Attributes<'a> {}
379
380////////////////////////////////////////////////////////////////////////////////////////////////////
381
382/// Errors that can be raised while parsing attributes.
383///
384/// The recovery positions in the examples show where parsing of the next
385/// attribute will be attempted.
386#[derive(Clone, Debug, PartialEq, Eq)]
387pub enum AttrError {
388    /// The attribute key was not followed by `=`. Contains the position relative
389    /// to the start of the owning tag. Only raised in XML mode.
390    ExpectedEq(usize),
391    /// No attribute value was found after `=`. Contains the position relative to
392    /// the start of the owning tag.
393    ExpectedValue(usize),
394    /// An attribute value was not quoted. Contains the position relative to the
395    /// start of the owning tag. Only raised in XML mode.
396    UnquotedValue(usize),
397    /// An attribute value was not terminated with a matching quote. Contains the
398    /// position and the expected quote byte.
399    ExpectedQuote(usize, u8),
400    /// An attribute with the same name was already encountered. Contains the
401    /// position of the new attribute and the position of the previous one.
402    Duplicated(usize, usize),
403}
404
405impl Display for AttrError {
406    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
407        match self {
408            Self::ExpectedEq(pos) => write!(
409                f,
410                r#"position {}: attribute key must be directly followed by `=` or space"#,
411                pos
412            ),
413            Self::ExpectedValue(pos) => write!(
414                f,
415                r#"position {}: `=` must be followed by an attribute value"#,
416                pos
417            ),
418            Self::UnquotedValue(pos) => write!(
419                f,
420                r#"position {}: attribute value must be enclosed in `"` or `'`"#,
421                pos
422            ),
423            Self::ExpectedQuote(pos, quote) => write!(
424                f,
425                r#"position {}: missing closing quote `{}` in attribute value"#,
426                pos, *quote as char
427            ),
428            Self::Duplicated(pos1, pos2) => write!(
429                f,
430                r#"position {}: duplicated attribute, previous declaration at position {}"#,
431                pos1, pos2
432            ),
433        }
434    }
435}
436
437impl std::error::Error for AttrError {}
438
439////////////////////////////////////////////////////////////////////////////////////////////////////
440
441/// A key/value XML or HTML [attribute] span.
442///
443/// [attribute]: https://www.w3.org/TR/xml11/#NT-Attribute
444#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
445pub enum Attr<T> {
446    /// Attribute with a value enclosed in double quotes (`"`). Canonical XML style.
447    DoubleQ(T, T),
448    /// Attribute with a value enclosed in single quotes (`'`). XML style.
449    SingleQ(T, T),
450    /// Attribute with a value not enclosed in quotes. HTML style; only returned in
451    /// HTML mode.
452    Unquoted(T, T),
453    /// Attribute without a value. HTML style; only returned in HTML mode.
454    Empty(T),
455}
456
457impl<T> Attr<T> {
458    /// Maps an `Attr<T>` to `Attr<U>` by applying `f` to the key and value.
459    #[inline]
460    pub fn map<U, F>(self, mut f: F) -> Attr<U>
461    where
462        F: FnMut(T) -> U,
463    {
464        match self {
465            Attr::DoubleQ(key, value) => Attr::DoubleQ(f(key), f(value)),
466            Attr::SingleQ(key, value) => Attr::SingleQ(f(key), f(value)),
467            Attr::Empty(key) => Attr::Empty(f(key)),
468            Attr::Unquoted(key, value) => Attr::Unquoted(f(key), f(value)),
469        }
470    }
471}
472
473impl<'a> Attr<&'a [u8]> {
474    /// Returns the key.
475    #[inline]
476    pub const fn key(&self) -> QName<'a> {
477        QName(match self {
478            Attr::DoubleQ(key, _) => key,
479            Attr::SingleQ(key, _) => key,
480            Attr::Empty(key) => key,
481            Attr::Unquoted(key, _) => key,
482        })
483    }
484    /// Returns the value. For [`Attr::Empty`] an empty slice is returned.
485    #[inline]
486    pub const fn value(&self) -> &'a [u8] {
487        match self {
488            Attr::DoubleQ(_, value) => value,
489            Attr::SingleQ(_, value) => value,
490            Attr::Empty(_) => &[],
491            Attr::Unquoted(_, value) => value,
492        }
493    }
494}
495
496impl<T: AsRef<[u8]>> Debug for Attr<T> {
497    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
498        match self {
499            Attr::DoubleQ(key, value) => f
500                .debug_tuple("Attr::DoubleQ")
501                .field(&Bytes(key.as_ref()))
502                .field(&Bytes(value.as_ref()))
503                .finish(),
504            Attr::SingleQ(key, value) => f
505                .debug_tuple("Attr::SingleQ")
506                .field(&Bytes(key.as_ref()))
507                .field(&Bytes(value.as_ref()))
508                .finish(),
509            Attr::Empty(key) => f
510                .debug_tuple("Attr::Empty")
511                .field(&Bytes(key.as_ref()))
512                .finish(),
513            Attr::Unquoted(key, value) => f
514                .debug_tuple("Attr::Unquoted")
515                .field(&Bytes(key.as_ref()))
516                .field(&Bytes(value.as_ref()))
517                .finish(),
518        }
519    }
520}
521
522/// Unpacks the attribute key and value into a tuple. The value is `None` only for
523/// [`Attr::Empty`].
524impl<T> From<Attr<T>> for (T, Option<T>) {
525    #[inline]
526    fn from(attr: Attr<T>) -> Self {
527        match attr {
528            Attr::DoubleQ(key, value) => (key, Some(value)),
529            Attr::SingleQ(key, value) => (key, Some(value)),
530            Attr::Empty(key) => (key, None),
531            Attr::Unquoted(key, value) => (key, Some(value)),
532        }
533    }
534}
535
536////////////////////////////////////////////////////////////////////////////////////////////////////
537
538type AttrResult = Result<Attr<Range<usize>>, AttrError>;
539
540#[derive(Clone, Copy, Debug)]
541enum State {
542    /// Iteration finished.
543    Done,
544    /// The last attribute was parsed successfully; contains the offset from which
545    /// the next attribute should be searched.
546    Next(usize),
547    /// The last attribute returned [`AttrError::UnquotedValue`]; the offset points
548    /// to the beginning of the value.
549    SkipValue(usize),
550    /// The last attribute returned [`AttrError::Duplicated`]; the offset points to
551    /// the equals (`=`) sign.
552    SkipEqValue(usize),
553}
554
555/// Number of attributes a start tag may have before the duplicate-name check
556/// switches from a linear scan to a hash pre-filter, keeping the whole tag O(N).
557const SMALL_ATTRIBUTE_COUNT: usize = 32;
558
559/// A no-op [`Hasher`] for the `key_hashes` set, whose values are already 64-bit
560/// hashes of attribute names.
561#[derive(Default)]
562struct IdentityHasher(u64);
563
564impl Hasher for IdentityHasher {
565    #[inline]
566    fn finish(&self) -> u64 {
567        self.0
568    }
569
570    #[inline]
571    fn write(&mut self, _: &[u8]) {
572        // The set only ever stores `u64` keys, which route through `write_u64`.
573        unreachable!("IdentityHasher only supports u64 keys")
574    }
575
576    #[inline]
577    fn write_u64(&mut self, n: u64) {
578        self.0 = n;
579    }
580}
581
582/// Hashes a single attribute name with a fresh [`DefaultHasher`] (DoS-resistant).
583#[inline]
584fn hash_name(name: &[u8]) -> u64 {
585    let mut hasher = DefaultHasher::new();
586    hasher.write(name);
587    hasher.finish()
588}
589
590/// External iterator over the spans of attribute keys and values.
591#[derive(Clone, Debug)]
592pub(crate) struct IterState {
593    /// State that determines what should be done before parsing the next attribute.
594    state: State,
595    /// If `true`, allows parsing of unquoted values and key-only attributes.
596    html: bool,
597    /// If `true`, checks for duplicate names.
598    check_duplicates: bool,
599    /// Ranges of already-parsed attribute names (only if `check_duplicates`).
600    keys: Vec<Range<usize>>,
601    /// 64-bit hashes of `keys`, used as an O(1) pre-filter once a start tag
602    /// declares more than `SMALL_ATTRIBUTE_COUNT` attributes.
603    key_hashes: Option<HashSet<u64, BuildHasherDefault<IdentityHasher>>>,
604}
605
606impl IterState {
607    pub const fn new(offset: usize, html: bool) -> Self {
608        Self {
609            state: State::Next(offset),
610            html,
611            check_duplicates: true,
612            keys: Vec::new(),
613            key_hashes: None,
614        }
615    }
616
617    /// Recovers from an error made on a previous step, returning the offset from
618    /// which parsing should continue, or `None` if there is no input left.
619    fn recover(&self, slice: &[u8]) -> Option<usize> {
620        match self.state {
621            State::Done => None,
622            State::Next(offset) => Some(offset),
623            State::SkipValue(offset) => self.skip_value(slice, offset),
624            State::SkipEqValue(offset) => self.skip_eq_value(slice, offset),
625        }
626    }
627
628    /// Skips all characters up to the first white space or end-of-input.
629    #[inline]
630    fn skip_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
631        let mut iter = (offset..).zip(slice[offset..].iter());
632        iter.find(|&(_, &b)| is_whitespace(b)).map(|(e, _)| e)
633    }
634
635    /// Skips an `= value` up to the first white space or end-of-input.
636    #[inline]
637    fn skip_eq_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
638        let mut iter = (offset..).zip(slice[offset..].iter());
639
640        // Skip up to the quote and determine its type.
641        let quote = match iter.find(|&(_, &b)| !is_whitespace(b)) {
642            Some((_, b'"')) => b'"',
643            Some((_, b'\'')) => b'\'',
644            Some((offset, _)) => return self.skip_value(slice, offset),
645            None => return None,
646        };
647
648        match iter.find(|&(_, &b)| b == quote) {
649            Some((e, b'"')) => Some(e),
650            Some((e, _)) => Some(e),
651            None => None,
652        }
653    }
654
655    /// Checks that the attribute name `key` was not seen earlier in the same tag,
656    /// recording it. Uses a linear scan for small tags and a hash pre-filter above
657    /// `SMALL_ATTRIBUTE_COUNT`.
658    #[inline]
659    fn check_for_duplicates(
660        &mut self,
661        slice: &[u8],
662        key: Range<usize>,
663    ) -> Result<Range<usize>, AttrError> {
664        if self.check_duplicates {
665            if self.keys.len() >= SMALL_ATTRIBUTE_COUNT {
666                return self.check_for_duplicates_hashed(slice, key);
667            }
668            if let Some(prev) = self
669                .keys
670                .iter()
671                .find(|r| slice[(*r).clone()] == slice[key.clone()])
672            {
673                return Err(AttrError::Duplicated(key.start, prev.start));
674            }
675            self.keys.push(key.clone());
676        }
677        Ok(key)
678    }
679
680    /// Cold path of [`check_for_duplicates`](Self::check_for_duplicates) for tags
681    /// with many attributes: an O(1) hash pre-filter keeps iteration O(N).
682    #[cold]
683    fn check_for_duplicates_hashed(
684        &mut self,
685        slice: &[u8],
686        key: Range<usize>,
687    ) -> Result<Range<usize>, AttrError> {
688        let keys = &self.keys;
689        let key_hashes = self.key_hashes.get_or_insert_with(|| {
690            let mut set = HashSet::with_capacity_and_hasher(
691                keys.len() * 2,
692                BuildHasherDefault::<IdentityHasher>::default(),
693            );
694            for r in keys {
695                set.insert(hash_name(&slice[r.clone()]));
696            }
697            set
698        });
699        if !key_hashes.insert(hash_name(&slice[key.clone()])) {
700            if let Some(prev) = self
701                .keys
702                .iter()
703                .find(|r| slice[(*r).clone()] == slice[key.clone()])
704            {
705                return Err(AttrError::Duplicated(key.start, prev.start));
706            }
707        }
708        self.keys.push(key.clone());
709        Ok(key)
710    }
711
712    #[inline]
713    fn key_only(&mut self, slice: &[u8], key: Range<usize>, offset: usize) -> Option<AttrResult> {
714        Some(if self.html {
715            self.check_for_duplicates(slice, key).map(Attr::Empty)
716        } else {
717            Err(AttrError::ExpectedEq(offset))
718        })
719    }
720
721    #[inline]
722    fn double_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
723        self.state = State::Next(value.end + 1); // +1 for `"`
724        Some(Ok(Attr::DoubleQ(key, value)))
725    }
726
727    #[inline]
728    fn single_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
729        self.state = State::Next(value.end + 1); // +1 for `'`
730        Some(Ok(Attr::SingleQ(key, value)))
731    }
732
733    pub fn next(&mut self, slice: &[u8]) -> Option<AttrResult> {
734        let mut iter = match self.recover(slice) {
735            Some(offset) => (offset..).zip(slice[offset..].iter()),
736            None => return None,
737        };
738
739        // Index where the next key starts.
740        let start_key = match iter.find(|&(_, &b)| !is_whitespace(b)) {
741            Some((s, _)) => s,
742            None => {
743                self.state = State::Done;
744                return None;
745            }
746        };
747        // Span of the key.
748        let (key, offset) = match iter.find(|&(_, &b)| b == b'=' || is_whitespace(b)) {
749            Some((e, b'=')) => (start_key..e, e),
750
751            Some((e, _)) => match iter.find(|&(_, &b)| !is_whitespace(b)) {
752                Some((offset, b'=')) => (start_key..e, offset),
753                Some((offset, _)) => {
754                    self.state = State::Next(offset);
755                    return self.key_only(slice, start_key..e, offset);
756                }
757                None => {
758                    self.state = State::Done;
759                    return self.key_only(slice, start_key..e, slice.len());
760                }
761            },
762
763            None => {
764                self.state = State::Done;
765                let e = slice.len();
766                return self.key_only(slice, start_key..e, e);
767            }
768        };
769
770        let key = match self.check_for_duplicates(slice, key) {
771            Err(e) => {
772                self.state = State::SkipEqValue(offset);
773                return Some(Err(e));
774            }
775            Ok(key) => key,
776        };
777
778        // Position of the quote and its type.
779        let (start_value, quote) = match iter.find(|&(_, &b)| !is_whitespace(b)) {
780            Some((s, b'"')) => (s + 1, b'"'),
781            Some((s, b'\'')) => (s + 1, b'\''),
782
783            Some((s, _)) if self.html => {
784                let end = match iter.find(|&(_, &b)| is_whitespace(b)) {
785                    Some((e, _)) => e,
786                    None => slice.len(),
787                };
788                self.state = State::Next(end);
789                return Some(Ok(Attr::Unquoted(key, s..end)));
790            }
791            Some((s, _)) => {
792                self.state = State::SkipValue(s);
793                return Some(Err(AttrError::UnquotedValue(s)));
794            }
795
796            None => {
797                self.state = State::Done;
798                return Some(Err(AttrError::ExpectedValue(slice.len())));
799            }
800        };
801
802        match iter.find(|&(_, &b)| b == quote) {
803            Some((e, b'"')) => self.double_q(key, start_value..e),
804            Some((e, _)) => self.single_q(key, start_value..e),
805            None => {
806                self.state = State::Done;
807                Some(Err(AttrError::ExpectedQuote(slice.len(), quote)))
808            }
809        }
810    }
811}
812
813#[cfg(test)]
814mod tests;