Skip to main content

tl/parser/
tag.rs

1#[cfg(feature = "std")]
2use crate::queryselector::{self, QuerySelectorIterator};
3use crate::{
4    Bytes, InnerNodeHandle, ParseError,
5    inline::{hashmap::InlineHashMap, vec::InlineVec},
6};
7use core::{fmt, mem};
8#[cfg(feature = "std")]
9use std::borrow::Cow;
10
11use super::{Parser, handle::NodeHandle};
12
13const INLINED_ATTRIBUTES: usize = 8;
14const INLINED_SUBNODES: usize = 256;
15const HTML_VOID_ELEMENTS: [&str; 16] = [
16    "area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link",
17    "meta", "param", "source", "track", "wbr",
18];
19
20/// The type of map for "raw" attributes
21pub type RawAttributesMap<'a> = InlineHashMap<Bytes<'a>, Option<Bytes<'a>>, INLINED_ATTRIBUTES>;
22
23/// The type of vector for children of an HTML tag
24pub type RawChildren = InlineVec<NodeHandle, INLINED_SUBNODES>;
25
26/// Stores all attributes of an HTML tag, as well as additional metadata such as `id` and `class`
27#[derive(Debug, Clone)]
28pub struct Attributes<'a> {
29    /// Raw attributes (maps attribute key to attribute value)
30    pub(crate) raw: RawAttributesMap<'a>,
31    /// The ID of this HTML element, if present
32    pub(crate) id: Option<Bytes<'a>>,
33    /// A list of class names of this HTML element, if present
34    pub(crate) class: Option<Bytes<'a>>,
35}
36
37impl<'a> Attributes<'a> {
38    /// Creates a new `Attributes
39    pub(crate) fn new() -> Self {
40        Self {
41            raw: InlineHashMap::new(),
42            id: None,
43            class: None,
44        }
45    }
46
47    /// Counts the number of attributes
48    pub fn len(&self) -> usize {
49        let mut raw = self.raw.len();
50        if self.id.is_some() {
51            raw += 1;
52        }
53        if self.class.is_some() {
54            raw += 1;
55        }
56        raw
57    }
58
59    /// Checks whether this collection of attributes is empty
60    pub fn is_empty(&self) -> bool {
61        self.len() == 0
62    }
63
64    /// Checks whether a given string is in the class names list
65    pub fn is_class_member<B: AsRef<[u8]>>(&self, member: B) -> bool {
66        self.class_iter()
67            .is_some_and(|mut i| i.any(|s| s.as_bytes() == member.as_ref()))
68    }
69
70    /// Checks whether this attributes collection contains a given key and returns its value
71    ///
72    /// Attributes that exist in this tag but have no value set will have their inner Option set to None
73    pub fn get<B>(&self, key: B) -> Option<Option<&Bytes<'a>>>
74    where
75        B: Into<Bytes<'a>>,
76    {
77        let key: Bytes = key.into();
78
79        match key.as_bytes() {
80            b"id" => self.id.as_ref().map(Some),
81            b"class" => self.class.as_ref().map(Some),
82            _ => self.raw.get(&key).map(|x| x.as_ref()),
83        }
84    }
85
86    /// Checks whether this attributes collection contains a given key
87    pub fn contains<B>(&self, key: B) -> bool
88    where
89        B: Into<Bytes<'a>>,
90    {
91        self.get(key).is_some()
92    }
93
94    /// Removes an attribute from this collection and returns it.
95    ///
96    /// As with [`Attributes::get()`], the outer Option is set to None if the attribute does not exist.
97    /// The inner option is set to None if the attribute exists but has no value.
98    ///
99    /// # Example
100    /// ```
101    /// #[cfg(feature = "std")]
102    /// let mut dom = tl::parse("<span contenteditable=\"true\"></span>", Default::default()).unwrap();
103    /// #[cfg(not(feature = "std"))]
104    /// let mut dom = tl::parse::<4, 4, 4, 4, 4, 4>("<span contenteditable=\"true\"></span>", Default::default()).unwrap();
105    /// let element = dom.nodes_mut()[0].as_tag_mut().unwrap();
106    /// let attributes = element.attributes_mut();
107    ///
108    /// assert_eq!(attributes.remove("contenteditable"), Some(Some("true".into())));
109    /// assert_eq!(attributes.len(), 0);
110    /// ```
111    pub fn remove<B>(&mut self, key: B) -> Option<Option<Bytes<'a>>>
112    where
113        B: Into<Bytes<'a>>,
114    {
115        let key: Bytes = key.into();
116
117        match key.as_bytes() {
118            b"id" => self.id.take().map(Some),
119            b"class" => self.class.take().map(Some),
120            _ => self.raw.remove(&key),
121        }
122    }
123
124    /// Removes the value of an attribute in this collection and returns it.
125    ///
126    /// # Example
127    /// ```
128    /// #[cfg(feature = "std")]
129    /// let mut dom = tl::parse("<span contenteditable=\"true\"></span>", Default::default()).unwrap();
130    /// #[cfg(not(feature = "std"))]
131    /// let mut dom = tl::parse::<4, 4, 4, 4, 4, 4>("<span contenteditable=\"true\"></span>", Default::default()).unwrap();
132    /// let element = dom.nodes_mut()[0].as_tag_mut().unwrap();
133    /// let attributes = element.attributes_mut();
134    ///
135    /// assert_eq!(attributes.remove_value("contenteditable"), Some("true".into()));
136    /// assert_eq!(attributes.get("contenteditable"), Some(None));
137    /// ```
138    pub fn remove_value<B>(&mut self, key: B) -> Option<Bytes<'a>>
139    where
140        B: Into<Bytes<'a>>,
141    {
142        let key: Bytes = key.into();
143
144        match key.as_bytes() {
145            b"id" => self.id.take(),
146            b"class" => self.class.take(),
147            _ => self.raw.get_mut(&key).and_then(mem::take),
148        }
149    }
150
151    /// Checks whether this attributes collection contains a given key and returns its value
152    pub fn get_mut<B>(&mut self, key: B) -> Option<Option<&mut Bytes<'a>>>
153    where
154        B: Into<Bytes<'a>>,
155    {
156        let key: Bytes = key.into();
157
158        match key.as_bytes() {
159            b"id" => self.id.as_mut().map(Some),
160            b"class" => self.class.as_mut().map(Some),
161            _ => self.raw.get_mut(&key).map(Option::as_mut),
162        }
163    }
164
165    /// Inserts a new attribute into this attributes collection
166    pub fn insert<K, V>(&mut self, key: K, value: Option<V>) -> Result<(), ParseError>
167    where
168        K: Into<Bytes<'a>>,
169        V: Into<Bytes<'a>>,
170    {
171        let key: Bytes = key.into();
172        let value = value.map(Into::into);
173
174        match key.as_bytes() {
175            b"id" => self.id = value,
176            b"class" => self.class = value,
177            _ => {
178                self.raw
179                    .insert(key, value)
180                    .map_err(|_| ParseError::AttributeCapacityExceeded)?;
181            }
182        };
183        Ok(())
184    }
185
186    /// Returns an iterator `(attribute_key, attribute_value)` over the attributes of this `HTMLTag`
187    #[cfg(feature = "std")]
188    pub fn iter(&self) -> impl Iterator<Item = (Cow<'_, str>, Option<Cow<'_, str>>)> + '_ {
189        self.raw
190            .iter()
191            .map(|(k, v)| {
192                let k = k.as_utf8_str();
193                let v = v.as_ref().map(|x| x.as_utf8_str());
194
195                (Some(k), v)
196            })
197            .chain([
198                (
199                    self.id.is_some().then_some(Cow::Borrowed("id")),
200                    self.id.as_ref().map(|x| x.as_utf8_str()),
201                ),
202                (
203                    self.class.is_some().then_some(Cow::Borrowed("class")),
204                    self.class.as_ref().map(|x| x.as_utf8_str()),
205                ),
206            ])
207            .flat_map(|(k, v)| k.map(|k| (k, v)))
208    }
209
210    /// Returns the `id` attribute of this HTML tag, if present
211    pub fn id(&self) -> Option<&Bytes<'a>> {
212        self.id.as_ref()
213    }
214
215    /// Returns the `class` attribute of this HTML tag, if present
216    pub fn class(&self) -> Option<&Bytes<'a>> {
217        self.class.as_ref()
218    }
219
220    /// Returns an iterator over all of the class members
221    pub fn class_iter(&self) -> Option<impl Iterator<Item = &'_ str> + '_> {
222        self.class
223            .as_ref()
224            .and_then(Bytes::try_as_utf8_str)
225            .map(str::split_ascii_whitespace)
226    }
227
228    /// Returns the underlying raw map for attributes
229    ///
230    /// ## A note on stability
231    /// It is not guaranteed for the returned map to include all attributes.
232    /// Some attributes may be stored in `Attributes` itself and not in the raw map.
233    /// For that reason you should prefer to call methods on `Attributes` directly,
234    /// i.e. `Attributes::get()` to lookup an attribute by its key.
235    pub fn unstable_raw(&self) -> &RawAttributesMap<'a> {
236        &self.raw
237    }
238}
239
240/// Represents a single HTML element
241#[derive(Debug, Clone)]
242pub struct HTMLTag<'a> {
243    pub(crate) _name: Bytes<'a>,
244    pub(crate) _attributes: Attributes<'a>,
245    pub(crate) _children: RawChildren,
246    pub(crate) _raw: Bytes<'a>,
247}
248
249impl<'a> HTMLTag<'a> {
250    /// Creates a new HTMLTag
251    #[inline(always)]
252    pub(crate) fn new(
253        name: Bytes<'a>,
254        attr: Attributes<'a>,
255        children: InlineVec<NodeHandle, INLINED_SUBNODES>,
256        raw: Bytes<'a>,
257    ) -> Self {
258        Self {
259            _name: name,
260            _attributes: attr,
261            _children: children,
262            _raw: raw,
263        }
264    }
265
266    /// Returns a wrapper around the children of this HTML tag
267    #[inline]
268    pub fn children(&self) -> Children<'a, '_> {
269        Children(self)
270    }
271
272    /// Returns a mutable wrapper around the children of this HTML tag.
273    pub fn children_mut(&mut self) -> ChildrenMut<'a, '_> {
274        ChildrenMut(self)
275    }
276
277    /// Returns the name of this HTML tag
278    #[inline]
279    pub fn name(&self) -> &Bytes<'a> {
280        &self._name
281    }
282
283    /// Returns a mutable reference to the name of this HTML tag
284    #[inline]
285    pub fn name_mut(&mut self) -> &mut Bytes<'a> {
286        &mut self._name
287    }
288
289    /// Returns attributes of this HTML tag
290    #[inline]
291    pub fn attributes(&self) -> &Attributes<'a> {
292        &self._attributes
293    }
294
295    /// Returns a mutable reference to the attributes of this HTML tag
296    #[inline]
297    pub fn attributes_mut(&mut self) -> &mut Attributes<'a> {
298        &mut self._attributes
299    }
300
301    /// Writes the contained markup to a formatter without allocating.
302    pub fn write_outer_html<
303        W: fmt::Write,
304        const MAX_NODES: usize,
305        const MAX_STACK: usize,
306        const MAX_ROOTS: usize,
307        const MAX_IDS: usize,
308        const MAX_CLASSES: usize,
309        const MAX_SELECTOR_NODES: usize,
310    >(
311        &self,
312        parser: &Parser<
313            'a,
314            MAX_NODES,
315            MAX_STACK,
316            MAX_ROOTS,
317            MAX_IDS,
318            MAX_CLASSES,
319            MAX_SELECTOR_NODES,
320        >,
321        dest: &mut W,
322    ) -> fmt::Result {
323        let tag_name = self._name.try_as_utf8_str().unwrap_or("");
324        let is_void_element = HTML_VOID_ELEMENTS.contains(&tag_name);
325
326        dest.write_char('<')?;
327        dest.write_str(tag_name)?;
328
329        fn write_attribute<W: fmt::Write>(
330            dest: &mut W,
331            key: &Bytes<'_>,
332            value: Option<&Bytes<'_>>,
333        ) -> fmt::Result {
334            dest.write_char(' ')?;
335            dest.write_str(key.try_as_utf8_str().unwrap_or(""))?;
336
337            if let Some(value) = value {
338                dest.write_str("=\"")?;
339                dest.write_str(value.try_as_utf8_str().unwrap_or(""))?;
340                dest.write_char('"')?;
341            }
342
343            Ok(())
344        }
345
346        for (key, value) in self.attributes().raw.iter() {
347            write_attribute(dest, key, value.as_ref())?;
348        }
349        if let Some(id) = self.attributes().id() {
350            write_attribute(dest, &Bytes::from("id"), Some(id))?;
351        }
352        if let Some(class) = self.attributes().class() {
353            write_attribute(dest, &Bytes::from("class"), Some(class))?;
354        }
355
356        dest.write_char('>')?;
357
358        if !is_void_element {
359            self.write_inner_html(parser, dest)?;
360            dest.write_str("</")?;
361            dest.write_str(tag_name)?;
362            dest.write_char('>')?;
363        }
364
365        Ok(())
366    }
367
368    /// Writes the contained child markup to a formatter without allocating.
369    pub fn write_inner_html<
370        W: fmt::Write,
371        const MAX_NODES: usize,
372        const MAX_STACK: usize,
373        const MAX_ROOTS: usize,
374        const MAX_IDS: usize,
375        const MAX_CLASSES: usize,
376        const MAX_SELECTOR_NODES: usize,
377    >(
378        &self,
379        parser: &Parser<
380            'a,
381            MAX_NODES,
382            MAX_STACK,
383            MAX_ROOTS,
384            MAX_IDS,
385            MAX_CLASSES,
386            MAX_SELECTOR_NODES,
387        >,
388        dest: &mut W,
389    ) -> fmt::Result {
390        for handle in self.children().top().iter() {
391            if let Some(node) = handle.get(parser) {
392                node.write_outer_html(parser, dest)?;
393            }
394        }
395
396        Ok(())
397    }
398
399    /// Returns the contained markup
400    ///
401    /// ## Limitations
402    /// - The order of tag attributes is not guaranteed
403    /// - Spaces within the tag are not preserved (i.e. `<img      src="">` may become `<img src="">`)
404    ///
405    /// Equivalent to [Element#outerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML) in browsers.
406    #[cfg(feature = "std")]
407    pub fn outer_html<
408        'p,
409        const MAX_NODES: usize,
410        const MAX_STACK: usize,
411        const MAX_ROOTS: usize,
412        const MAX_IDS: usize,
413        const MAX_CLASSES: usize,
414        const MAX_SELECTOR_NODES: usize,
415    >(
416        &'p self,
417        parser: &'p Parser<
418            'a,
419            MAX_NODES,
420            MAX_STACK,
421            MAX_ROOTS,
422            MAX_IDS,
423            MAX_CLASSES,
424            MAX_SELECTOR_NODES,
425        >,
426    ) -> String {
427        let mut outer_html = String::with_capacity(self._raw.as_bytes().len());
428        let _ = self.write_outer_html(parser, &mut outer_html);
429        outer_html
430    }
431
432    /// Returns the contained markup
433    ///
434    /// ## Limitations
435    /// - The order of tag attributes is not guaranteed
436    /// - Spaces within the tag are not preserved (i.e. `<img      src="">` may become `<img src="">`)
437    ///
438    /// Equivalent to [Element#innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) in browsers.
439    #[cfg(feature = "std")]
440    pub fn inner_html<
441        'p,
442        const MAX_NODES: usize,
443        const MAX_STACK: usize,
444        const MAX_ROOTS: usize,
445        const MAX_IDS: usize,
446        const MAX_CLASSES: usize,
447        const MAX_SELECTOR_NODES: usize,
448    >(
449        &'p self,
450        parser: &'p Parser<
451            'a,
452            MAX_NODES,
453            MAX_STACK,
454            MAX_ROOTS,
455            MAX_IDS,
456            MAX_CLASSES,
457            MAX_SELECTOR_NODES,
458        >,
459    ) -> String {
460        let mut inner_html = String::with_capacity(self._raw.as_bytes().len());
461        let _ = self.write_inner_html(parser, &mut inner_html);
462        inner_html
463    }
464
465    /// Returns the raw HTML of this tag.
466    /// This is a cheaper version of `HTMLTag::inner_html` if you never mutate any nodes.
467    ///
468    /// **Note:** Mutating this tag does *not* re-compute the HTML representation of this tag.
469    /// This simply returns a reference to the substring.
470    pub fn raw(&self) -> &Bytes<'a> {
471        &self._raw
472    }
473
474    /// Returns the boundaries/position `(start, end)` of this HTML tag in the source string.
475    ///
476    /// # Example
477    /// ```
478    /// let source = "<p><span>hello</span></p>";
479    /// #[cfg(feature = "std")]
480    /// let dom = tl::parse(source, Default::default()).unwrap();
481    /// #[cfg(not(feature = "std"))]
482    /// let dom = tl::parse::<8, 8, 8, 4, 4, 4>(source, Default::default()).unwrap();
483    /// let parser = dom.parser();
484    /// let span = dom.nodes().iter().filter_map(|n| n.as_tag()).find(|n| n.name() == "span").unwrap();
485    /// let (start, end) = span.boundaries(parser);
486    /// assert_eq!((start, end), (3, 20));
487    /// assert_eq!(&source[start..=end], "<span>hello</span>");
488    /// ```
489    pub fn boundaries<
490        const MAX_NODES: usize,
491        const MAX_STACK: usize,
492        const MAX_ROOTS: usize,
493        const MAX_IDS: usize,
494        const MAX_CLASSES: usize,
495        const MAX_SELECTOR_NODES: usize,
496    >(
497        &self,
498        parser: &Parser<
499            'a,
500            MAX_NODES,
501            MAX_STACK,
502            MAX_ROOTS,
503            MAX_IDS,
504            MAX_CLASSES,
505            MAX_SELECTOR_NODES,
506        >,
507    ) -> (usize, usize) {
508        let raw = self._raw.as_bytes();
509        let input = parser.stream.data().as_ptr();
510        let start = raw.as_ptr();
511        let offset = start as usize - input as usize;
512        let end = offset + raw.len() - 1;
513        (offset, end)
514    }
515
516    /// Concatenates descendant text, excluding markup and comments.
517    /// Character references remain encoded. This does not perform browser layout,
518    /// visibility filtering or rendered-whitespace normalization.
519    /// Enable `entities` and use `decoded_inner_text` for character references.
520    /// This function may not allocate memory for a new string as it can just return the part of the tag that doesn't have markup.
521    /// For tags that *do* have more than one subnode, this will allocate memory
522    #[cfg(feature = "std")]
523    pub fn inner_text<
524        'p,
525        const MAX_NODES: usize,
526        const MAX_STACK: usize,
527        const MAX_ROOTS: usize,
528        const MAX_IDS: usize,
529        const MAX_CLASSES: usize,
530        const MAX_SELECTOR_NODES: usize,
531    >(
532        &self,
533        parser: &'p Parser<
534            'a,
535            MAX_NODES,
536            MAX_STACK,
537            MAX_ROOTS,
538            MAX_IDS,
539            MAX_CLASSES,
540            MAX_SELECTOR_NODES,
541        >,
542    ) -> Cow<'p, str> {
543        let len = self._children.len();
544
545        if len == 0 {
546            // If there are no subnodes, we can just return a static, empty, string slice
547            return Cow::Borrowed("");
548        }
549
550        let first = self._children[0].get(parser).unwrap();
551
552        if len == 1 {
553            match &first {
554                Node::Tag(t) => return t.inner_text(parser),
555                Node::Raw(e) => return e.as_utf8_str(),
556                Node::Comment(_) => return Cow::Borrowed(""),
557            }
558        }
559
560        // If there are >1 nodes, we need to allocate a new string and push each inner_text in it
561        // TODO: check if String::with_capacity() is worth it
562        let mut s = String::from(first.inner_text(parser));
563
564        for &id in self._children.iter().skip(1) {
565            let node = id.get(parser).unwrap();
566
567            match &node {
568                Node::Tag(t) => s.push_str(&t.inner_text(parser)),
569                Node::Raw(e) => s.push_str(&e.as_utf8_str()),
570                Node::Comment(_) => { /* no op */ }
571            }
572        }
573
574        Cow::Owned(s)
575    }
576
577    /// Returns text with HTML character references decoded (the `entities` feature).
578    /// Unlike `inner_text`, named and numeric references in ordinary text are decoded.
579    /// Script/style raw text and the original DOM bytes remain unchanged.
580    #[cfg(feature = "entities")]
581    pub fn decoded_inner_text<
582        's,
583        'p: 's,
584        const MAX_NODES: usize,
585        const MAX_STACK: usize,
586        const MAX_ROOTS: usize,
587        const MAX_IDS: usize,
588        const MAX_CLASSES: usize,
589        const MAX_SELECTOR_NODES: usize,
590    >(
591        &'s self,
592        parser: &'p Parser<
593            'a,
594            MAX_NODES,
595            MAX_STACK,
596            MAX_ROOTS,
597            MAX_IDS,
598            MAX_CLASSES,
599            MAX_SELECTOR_NODES,
600        >,
601    ) -> Cow<'s, str> {
602        if self.name().as_bytes().eq_ignore_ascii_case(b"script")
603            || self.name().as_bytes().eq_ignore_ascii_case(b"style")
604        {
605            return self.inner_text(parser);
606        }
607        let children = &self._children;
608        if children.is_empty() {
609            return Cow::Borrowed("");
610        }
611        if children.len() == 1 {
612            return children[0]
613                .get(parser)
614                .map_or(Cow::Borrowed(""), |node| node.decoded_inner_text(parser));
615        }
616        let mut text = String::new();
617        for handle in children.iter() {
618            if let Some(node) = handle.get(parser) {
619                node.append_decoded_text(parser, &mut text);
620            }
621        }
622        Cow::Owned(text)
623    }
624
625    /// Tries to parse the query selector and returns an iterator over elements that match the given query selector.
626    ///
627    /// # Example
628    /// ```
629    /// let dom = tl::parse(r#"
630    ///     <div class="x">
631    ///     <div class="y">
632    ///       <div class="z">MATCH</div>
633    ///       <div class="z">MATCH</div>
634    ///       <div class="z">MATCH</div>
635    ///     </div>
636    ///   </div>
637    ///   <div class="z">NO MATCH</div>
638    ///   <div class="z">NO MATCH</div>
639    ///   <div class="z">NO MATCH</div>
640    /// "#, Default::default()).unwrap();
641    /// let parser = dom.parser();
642    ///
643    /// let outer = dom
644    ///     .get_elements_by_class_name("y")
645    ///     .next()
646    ///     .unwrap()
647    ///     .get(parser)
648    ///     .unwrap()
649    ///     .as_tag()
650    ///     .unwrap();
651    ///
652    /// let inner_z = outer.query_selector(parser, ".z").unwrap();
653    ///
654    /// assert_eq!(inner_z.clone().count(), 3);
655    ///
656    /// for handle in inner_z {
657    ///     let node = handle.get(parser).unwrap().as_tag().unwrap();
658    ///     assert_eq!(node.inner_text(parser), "MATCH");
659    /// }
660    ///
661    /// ```
662    #[cfg(feature = "std")]
663    pub fn query_selector<
664        'b,
665        const MAX_NODES: usize,
666        const MAX_STACK: usize,
667        const MAX_ROOTS: usize,
668        const MAX_IDS: usize,
669        const MAX_CLASSES: usize,
670    >(
671        &'b self,
672        parser: &'b Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, 0>,
673        selector: &'b str,
674    ) -> Option<
675        QuerySelectorIterator<
676            'a,
677            'b,
678            Self,
679            MAX_NODES,
680            MAX_STACK,
681            MAX_ROOTS,
682            MAX_IDS,
683            MAX_CLASSES,
684            0,
685        >,
686    > {
687        let selector = crate::parse_query_selector(selector)?;
688        let iter = queryselector::QuerySelectorIterator::new(selector, parser, self);
689        Some(iter)
690    }
691
692    /// Calls the given closure with each tag as parameter
693    ///
694    /// The closure must return a boolean, indicating whether it should stop iterating
695    /// Returning `true` will break the loop
696    pub fn find_node<F>(&self, parser: &Parser<'a>, f: &mut F) -> Option<NodeHandle>
697    where
698        F: FnMut(&Node<'a>) -> bool,
699    {
700        for &id in self._children.iter() {
701            let node = id.get(parser).unwrap();
702
703            if f(node) {
704                return Some(id);
705            }
706        }
707        None
708    }
709}
710
711/// A thin wrapper around the children of [`HTMLTag`]
712#[derive(Debug, Clone)]
713pub struct Children<'a, 'b>(&'b HTMLTag<'a>);
714
715impl<'a, 'b> Children<'a, 'b> {
716    /// Returns the topmost, direct children of this tag.
717    ///
718    /// # Example
719    /// ```
720    /// #[cfg(feature = "std")]
721    /// let dom = tl::parse(r#"
722    ///     <div id="a">
723    ///         <div id="b">
724    ///             <span>Hello</span>
725    ///             <span>World</span>
726    ///             <span>.</span>
727    ///         </div>
728    ///     </div>
729    /// "#, Default::default()).unwrap();
730    /// #[cfg(not(feature = "std"))]
731    /// let dom = tl::parse::<16, 16, 16, 4, 4, 4>(r#"
732    ///     <div id="a">
733    ///         <div id="b">
734    ///             <span>Hello</span>
735    ///             <span>World</span>
736    ///             <span>.</span>
737    ///         </div>
738    ///     </div>
739    /// "#, Default::default()).unwrap();
740    ///
741    /// let a = dom.get_element_by_id("a")
742    ///     .unwrap()
743    ///     .get(dom.parser())
744    ///     .unwrap()
745    ///     .as_tag()
746    ///     .unwrap();
747    ///
748    /// // Calling this function on the first div tag (#a) will return a slice containing 3 elements:
749    /// // - whitespaces around (before and after) div#b
750    /// // - div#b itself
751    /// // It does **not** contain the inner span tags
752    /// assert_eq!(a.children().top().len(), 3);
753    /// ```
754    #[inline]
755    pub fn top(&self) -> &RawChildren {
756        &self.0._children
757    }
758
759    /// Returns the starting boundary of the children of this tag.
760    #[inline]
761    pub fn start(&self) -> Option<InnerNodeHandle> {
762        self.0._children.get(0).map(NodeHandle::get_inner)
763    }
764
765    /// Returns the ending boundary of the children of this tag.
766    pub fn end<
767        const MAX_NODES: usize,
768        const MAX_STACK: usize,
769        const MAX_ROOTS: usize,
770        const MAX_IDS: usize,
771        const MAX_CLASSES: usize,
772        const MAX_SELECTOR_NODES: usize,
773    >(
774        &self,
775        parser: &Parser<
776            'a,
777            MAX_NODES,
778            MAX_STACK,
779            MAX_ROOTS,
780            MAX_IDS,
781            MAX_CLASSES,
782            MAX_SELECTOR_NODES,
783        >,
784    ) -> Option<InnerNodeHandle> {
785        find_last_node_handle(self.0, parser).map(|h| h.get_inner())
786    }
787
788    /// Returns the (start, end) boundaries of the children of this tag.
789    #[inline]
790    pub fn boundaries<
791        const MAX_NODES: usize,
792        const MAX_STACK: usize,
793        const MAX_ROOTS: usize,
794        const MAX_IDS: usize,
795        const MAX_CLASSES: usize,
796        const MAX_SELECTOR_NODES: usize,
797    >(
798        &self,
799        parser: &Parser<
800            'a,
801            MAX_NODES,
802            MAX_STACK,
803            MAX_ROOTS,
804            MAX_IDS,
805            MAX_CLASSES,
806            MAX_SELECTOR_NODES,
807        >,
808    ) -> Option<(InnerNodeHandle, InnerNodeHandle)> {
809        self.start().zip(self.end(parser))
810    }
811
812    /// Returns a slice containing all of the children of this [`HTMLTag`],
813    /// including all subnodes of the children.
814    ///
815    /// The difference between `top()` and `all()` is the same as `VDom::children()` and `VDom::nodes()`
816    ///
817    /// # Example
818    /// ```
819    /// #[cfg(feature = "std")]
820    /// let dom = tl::parse(r#"
821    ///     <div id="a"><div id="b"><span>Hello</span><span>World</span><span>!</span></div></div>
822    /// "#, Default::default()).unwrap();
823    /// #[cfg(not(feature = "std"))]
824    /// let dom = tl::parse::<16, 16, 16, 4, 4, 4>(r#"
825    ///     <div id="a"><div id="b"><span>Hello</span><span>World</span><span>!</span></div></div>
826    /// "#, Default::default()).unwrap();
827    ///
828    /// let a = dom.get_element_by_id("a")
829    ///     .unwrap()
830    ///     .get(dom.parser())
831    ///     .unwrap()
832    ///     .as_tag()
833    ///     .unwrap();
834    ///
835    /// // Calling this function on the first div tag (#a) will return a slice containing all of the subnodes:
836    /// // - div#b
837    /// // - span
838    /// // - Hello
839    /// // - span
840    /// // - World
841    /// // - span
842    /// // - !
843    /// assert_eq!(a.children().all(dom.parser()).len(), 7);
844    /// ```
845    pub fn all<
846        const MAX_NODES: usize,
847        const MAX_STACK: usize,
848        const MAX_ROOTS: usize,
849        const MAX_IDS: usize,
850        const MAX_CLASSES: usize,
851        const MAX_SELECTOR_NODES: usize,
852    >(
853        &self,
854        parser: &'b Parser<
855            'a,
856            MAX_NODES,
857            MAX_STACK,
858            MAX_ROOTS,
859            MAX_IDS,
860            MAX_CLASSES,
861            MAX_SELECTOR_NODES,
862        >,
863    ) -> &'b [Node<'a>] {
864        self.boundaries(parser)
865            .map(|(start, end)| &parser.tags.as_slice()[start as usize..=end as usize])
866            .unwrap_or(&[])
867    }
868}
869
870/// A thin mutable wrapper around the children of [`HTMLTag`]
871#[derive(Debug)]
872pub struct ChildrenMut<'a, 'b>(&'b mut HTMLTag<'a>);
873
874impl<'a, 'b> ChildrenMut<'a, 'b> {
875    /// Returns the topmost, direct children of this tag as a mutable slice.
876    ///
877    /// See [`Children::top`] for more details and examples.
878    #[inline]
879    pub fn top_mut(&mut self) -> &mut RawChildren {
880        &mut self.0._children
881    }
882}
883
884/// Attempts to find the very last node handle that is contained in the given tag
885fn find_last_node_handle<
886    'a,
887    const MAX_NODES: usize,
888    const MAX_STACK: usize,
889    const MAX_ROOTS: usize,
890    const MAX_IDS: usize,
891    const MAX_CLASSES: usize,
892    const MAX_SELECTOR_NODES: usize,
893>(
894    tag: &HTMLTag<'a>,
895    parser: &Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>,
896) -> Option<NodeHandle> {
897    let last_handle = tag._children.as_slice().last().copied()?;
898
899    let child = last_handle
900        .get(parser)
901        .expect("Failed to get child node, please open a bug report") // this shouldn't happen
902        .as_tag();
903
904    if let Some(child) = child {
905        // Recursively call this function to get to the innermost node
906        find_last_node_handle(child, parser).or(Some(last_handle))
907    } else {
908        Some(last_handle)
909    }
910}
911
912/// An HTML Node
913#[derive(Debug, Clone)]
914#[allow(clippy::large_enum_variant)]
915pub enum Node<'a> {
916    /// A regular HTML element/tag
917    Tag(HTMLTag<'a>),
918    /// Raw text (no particular HTML element)
919    Raw(Bytes<'a>),
920    /// Comment (<!-- -->)
921    Comment(Bytes<'a>),
922}
923
924impl<'a> Node<'a> {
925    /// Writes the outer HTML representation of this node without allocating.
926    pub fn write_outer_html<
927        W: fmt::Write,
928        const MAX_NODES: usize,
929        const MAX_STACK: usize,
930        const MAX_ROOTS: usize,
931        const MAX_IDS: usize,
932        const MAX_CLASSES: usize,
933        const MAX_SELECTOR_NODES: usize,
934    >(
935        &self,
936        parser: &Parser<
937            'a,
938            MAX_NODES,
939            MAX_STACK,
940            MAX_ROOTS,
941            MAX_IDS,
942            MAX_CLASSES,
943            MAX_SELECTOR_NODES,
944        >,
945        dest: &mut W,
946    ) -> fmt::Result {
947        match self {
948            Node::Comment(c) | Node::Raw(c) => dest.write_str(c.try_as_utf8_str().unwrap_or("")),
949            Node::Tag(t) => t.write_outer_html(parser, dest),
950        }
951    }
952
953    /// Returns the inner text of this node
954    #[cfg(feature = "std")]
955    pub fn inner_text<
956        's,
957        'p: 's,
958        const MAX_NODES: usize,
959        const MAX_STACK: usize,
960        const MAX_ROOTS: usize,
961        const MAX_IDS: usize,
962        const MAX_CLASSES: usize,
963        const MAX_SELECTOR_NODES: usize,
964    >(
965        &'s self,
966        parser: &'p Parser<
967            'a,
968            MAX_NODES,
969            MAX_STACK,
970            MAX_ROOTS,
971            MAX_IDS,
972            MAX_CLASSES,
973            MAX_SELECTOR_NODES,
974        >,
975    ) -> Cow<'s, str> {
976        match self {
977            Node::Comment(_) => Cow::Borrowed(""),
978            Node::Raw(r) => r.as_utf8_str(),
979            Node::Tag(t) => t.inner_text(parser),
980        }
981    }
982
983    #[cfg(feature = "entities")]
984    fn append_decoded_text<
985        's,
986        'p: 's,
987        const MAX_NODES: usize,
988        const MAX_STACK: usize,
989        const MAX_ROOTS: usize,
990        const MAX_IDS: usize,
991        const MAX_CLASSES: usize,
992        const MAX_SELECTOR_NODES: usize,
993    >(
994        &'s self,
995        parser: &'p Parser<
996            'a,
997            MAX_NODES,
998            MAX_STACK,
999            MAX_ROOTS,
1000            MAX_IDS,
1001            MAX_CLASSES,
1002            MAX_SELECTOR_NODES,
1003        >,
1004        output: &mut String,
1005    ) {
1006        match self {
1007            Node::Comment(_) => {}
1008            Node::Raw(raw) => output.push_str(&crate::entities::decode(&raw.as_utf8_str())),
1009            Node::Tag(tag) => {
1010                if tag.name().as_bytes().eq_ignore_ascii_case(b"script")
1011                    || tag.name().as_bytes().eq_ignore_ascii_case(b"style")
1012                {
1013                    output.push_str(&tag.inner_text(parser));
1014                } else {
1015                    for child in tag.children().top().iter() {
1016                        if let Some(child) = child.get(parser) {
1017                            child.append_decoded_text(parser, output);
1018                        }
1019                    }
1020                }
1021            }
1022        }
1023    }
1024
1025    /// Returns text with HTML character references decoded (the `entities` feature).
1026    /// Unlike `inner_text`, named and numeric references in ordinary text are decoded.
1027    /// Raw nodes use text-context decoding. Query a script/style tag to preserve its raw text.
1028    /// The original DOM bytes remain unchanged.
1029    #[cfg(feature = "entities")]
1030    pub fn decoded_inner_text<
1031        's,
1032        'p: 's,
1033        const MAX_NODES: usize,
1034        const MAX_STACK: usize,
1035        const MAX_ROOTS: usize,
1036        const MAX_IDS: usize,
1037        const MAX_CLASSES: usize,
1038        const MAX_SELECTOR_NODES: usize,
1039    >(
1040        &'s self,
1041        parser: &'p Parser<
1042            'a,
1043            MAX_NODES,
1044            MAX_STACK,
1045            MAX_ROOTS,
1046            MAX_IDS,
1047            MAX_CLASSES,
1048            MAX_SELECTOR_NODES,
1049        >,
1050    ) -> Cow<'s, str> {
1051        match self {
1052            Node::Comment(_) => Cow::Borrowed(""),
1053            Node::Tag(tag) => tag.decoded_inner_text(parser),
1054            Node::Raw(raw) => match raw.as_utf8_str() {
1055                Cow::Borrowed(text) => crate::entities::decode(text),
1056                Cow::Owned(text) => Cow::Owned(crate::entities::decode(&text).into_owned()),
1057            },
1058        }
1059    }
1060
1061    /// Returns the outer HTML of this node
1062    #[cfg(feature = "std")]
1063    pub fn outer_html<
1064        's,
1065        const MAX_NODES: usize,
1066        const MAX_STACK: usize,
1067        const MAX_ROOTS: usize,
1068        const MAX_IDS: usize,
1069        const MAX_CLASSES: usize,
1070        const MAX_SELECTOR_NODES: usize,
1071    >(
1072        &'s self,
1073        parser: &Parser<
1074            'a,
1075            MAX_NODES,
1076            MAX_STACK,
1077            MAX_ROOTS,
1078            MAX_IDS,
1079            MAX_CLASSES,
1080            MAX_SELECTOR_NODES,
1081        >,
1082    ) -> Cow<'s, str> {
1083        match self {
1084            Node::Comment(c) => c.as_utf8_str(),
1085            Node::Raw(r) => r.as_utf8_str(),
1086            Node::Tag(t) => Cow::Owned(t.outer_html(parser)),
1087        }
1088    }
1089
1090    /// Returns the inner HTML of this node
1091    #[cfg(feature = "std")]
1092    pub fn inner_html<
1093        's,
1094        const MAX_NODES: usize,
1095        const MAX_STACK: usize,
1096        const MAX_ROOTS: usize,
1097        const MAX_IDS: usize,
1098        const MAX_CLASSES: usize,
1099        const MAX_SELECTOR_NODES: usize,
1100    >(
1101        &'s self,
1102        parser: &Parser<
1103            'a,
1104            MAX_NODES,
1105            MAX_STACK,
1106            MAX_ROOTS,
1107            MAX_IDS,
1108            MAX_CLASSES,
1109            MAX_SELECTOR_NODES,
1110        >,
1111    ) -> Cow<'s, str> {
1112        match self {
1113            Node::Comment(c) => c.as_utf8_str(),
1114            Node::Raw(r) => r.as_utf8_str(),
1115            Node::Tag(t) => Cow::Owned(t.inner_html(parser)),
1116        }
1117    }
1118
1119    /// Returns an iterator over subnodes ("children") of this HTML tag, if this is a tag
1120    pub fn children(&self) -> Option<Children<'a, '_>> {
1121        match self {
1122            Node::Tag(t) => Some(t.children()),
1123            _ => None,
1124        }
1125    }
1126
1127    /// Calls the given closure with each tag as parameter
1128    ///
1129    /// The closure must return a boolean, indicating whether it should stop iterating
1130    /// Returning `true` will break the loop and return a handle to the node
1131    pub fn find_node<F>(&self, parser: &Parser<'a>, f: &mut F) -> Option<NodeHandle>
1132    where
1133        F: FnMut(&Node<'a>) -> bool,
1134    {
1135        if let Some(children) = self.children() {
1136            for &id in children.top().iter() {
1137                let node = id.get(parser).unwrap();
1138
1139                if f(node) {
1140                    return Some(id);
1141                }
1142
1143                let subnode = node.find_node(parser, f);
1144                if subnode.is_some() {
1145                    return subnode;
1146                }
1147            }
1148        }
1149        None
1150    }
1151
1152    /// Tries to coerce this node into a `HTMLTag` variant
1153    pub fn as_tag(&self) -> Option<&HTMLTag<'a>> {
1154        match self {
1155            Self::Tag(tag) => Some(tag),
1156            _ => None,
1157        }
1158    }
1159
1160    /// Tries to coerce this node into a `HTMLTag` variant
1161    pub fn as_tag_mut(&mut self) -> Option<&mut HTMLTag<'a>> {
1162        match self {
1163            Self::Tag(tag) => Some(tag),
1164            _ => None,
1165        }
1166    }
1167
1168    /// Tries to coerce this node into a comment, returning the text
1169    pub fn as_comment(&self) -> Option<&Bytes<'a>> {
1170        match self {
1171            Self::Comment(c) => Some(c),
1172            _ => None,
1173        }
1174    }
1175
1176    /// Tries to coerce this node into a comment, returning the text
1177    pub fn as_comment_mut(&mut self) -> Option<&mut Bytes<'a>> {
1178        match self {
1179            Self::Comment(c) => Some(c),
1180            _ => None,
1181        }
1182    }
1183
1184    /// Tries to coerce this node into a raw text node, returning the text
1185    ///
1186    /// "Raw text nodes" are nodes that are not HTML tags, but just text
1187    pub fn as_raw(&self) -> Option<&Bytes<'a>> {
1188        match self {
1189            Self::Raw(r) => Some(r),
1190            _ => None,
1191        }
1192    }
1193
1194    /// Tries to coerce this node into a mutable raw text node, returning the text
1195    ///
1196    /// "Raw text nodes" are nodes that are not HTML tags, but just text
1197    pub fn as_raw_mut(&mut self) -> Option<&mut Bytes<'a>> {
1198        match self {
1199            Self::Raw(r) => Some(r),
1200            _ => None,
1201        }
1202    }
1203}