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    /// Returns the contained text of this element, excluding any markup.
517    /// Equivalent to [Element#innerText](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText) in browsers.
518    /// 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.
519    /// For tags that *do* have more than one subnode, this will allocate memory
520    #[cfg(feature = "std")]
521    pub fn inner_text<
522        'p,
523        const MAX_NODES: usize,
524        const MAX_STACK: usize,
525        const MAX_ROOTS: usize,
526        const MAX_IDS: usize,
527        const MAX_CLASSES: usize,
528        const MAX_SELECTOR_NODES: usize,
529    >(
530        &self,
531        parser: &'p Parser<
532            'a,
533            MAX_NODES,
534            MAX_STACK,
535            MAX_ROOTS,
536            MAX_IDS,
537            MAX_CLASSES,
538            MAX_SELECTOR_NODES,
539        >,
540    ) -> Cow<'p, str> {
541        let len = self._children.len();
542
543        if len == 0 {
544            // If there are no subnodes, we can just return a static, empty, string slice
545            return Cow::Borrowed("");
546        }
547
548        let first = self._children[0].get(parser).unwrap();
549
550        if len == 1 {
551            match &first {
552                Node::Tag(t) => return t.inner_text(parser),
553                Node::Raw(e) => return e.as_utf8_str(),
554                Node::Comment(_) => return Cow::Borrowed(""),
555            }
556        }
557
558        // If there are >1 nodes, we need to allocate a new string and push each inner_text in it
559        // TODO: check if String::with_capacity() is worth it
560        let mut s = String::from(first.inner_text(parser));
561
562        for &id in self._children.iter().skip(1) {
563            let node = id.get(parser).unwrap();
564
565            match &node {
566                Node::Tag(t) => s.push_str(&t.inner_text(parser)),
567                Node::Raw(e) => s.push_str(&e.as_utf8_str()),
568                Node::Comment(_) => { /* no op */ }
569            }
570        }
571
572        Cow::Owned(s)
573    }
574
575    /// Tries to parse the query selector and returns an iterator over elements that match the given query selector.
576    ///
577    /// # Example
578    /// ```
579    /// let dom = tl::parse(r#"
580    ///     <div class="x">
581    ///     <div class="y">
582    ///       <div class="z">MATCH</div>
583    ///       <div class="z">MATCH</div>
584    ///       <div class="z">MATCH</div>
585    ///     </div>
586    ///   </div>
587    ///   <div class="z">NO MATCH</div>
588    ///   <div class="z">NO MATCH</div>
589    ///   <div class="z">NO MATCH</div>
590    /// "#, Default::default()).unwrap();
591    /// let parser = dom.parser();
592    ///
593    /// let outer = dom
594    ///     .get_elements_by_class_name("y")
595    ///     .next()
596    ///     .unwrap()
597    ///     .get(parser)
598    ///     .unwrap()
599    ///     .as_tag()
600    ///     .unwrap();
601    ///
602    /// let inner_z = outer.query_selector(parser, ".z").unwrap();
603    ///
604    /// assert_eq!(inner_z.clone().count(), 3);
605    ///
606    /// for handle in inner_z {
607    ///     let node = handle.get(parser).unwrap().as_tag().unwrap();
608    ///     assert_eq!(node.inner_text(parser), "MATCH");
609    /// }
610    ///
611    /// ```
612    #[cfg(feature = "std")]
613    pub fn query_selector<
614        'b,
615        const MAX_NODES: usize,
616        const MAX_STACK: usize,
617        const MAX_ROOTS: usize,
618        const MAX_IDS: usize,
619        const MAX_CLASSES: usize,
620    >(
621        &'b self,
622        parser: &'b Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, 0>,
623        selector: &'b str,
624    ) -> Option<
625        QuerySelectorIterator<
626            'a,
627            'b,
628            Self,
629            MAX_NODES,
630            MAX_STACK,
631            MAX_ROOTS,
632            MAX_IDS,
633            MAX_CLASSES,
634            0,
635        >,
636    > {
637        let selector = crate::parse_query_selector(selector)?;
638        let iter = queryselector::QuerySelectorIterator::new(selector, parser, self);
639        Some(iter)
640    }
641
642    /// Calls the given closure with each tag as parameter
643    ///
644    /// The closure must return a boolean, indicating whether it should stop iterating
645    /// Returning `true` will break the loop
646    pub fn find_node<F>(&self, parser: &Parser<'a>, f: &mut F) -> Option<NodeHandle>
647    where
648        F: FnMut(&Node<'a>) -> bool,
649    {
650        for &id in self._children.iter() {
651            let node = id.get(parser).unwrap();
652
653            if f(node) {
654                return Some(id);
655            }
656        }
657        None
658    }
659}
660
661/// A thin wrapper around the children of [`HTMLTag`]
662#[derive(Debug, Clone)]
663pub struct Children<'a, 'b>(&'b HTMLTag<'a>);
664
665impl<'a, 'b> Children<'a, 'b> {
666    /// Returns the topmost, direct children of this tag.
667    ///
668    /// # Example
669    /// ```
670    /// #[cfg(feature = "std")]
671    /// let dom = tl::parse(r#"
672    ///     <div id="a">
673    ///         <div id="b">
674    ///             <span>Hello</span>
675    ///             <span>World</span>
676    ///             <span>.</span>
677    ///         </div>
678    ///     </div>
679    /// "#, Default::default()).unwrap();
680    /// #[cfg(not(feature = "std"))]
681    /// let dom = tl::parse::<16, 16, 16, 4, 4, 4>(r#"
682    ///     <div id="a">
683    ///         <div id="b">
684    ///             <span>Hello</span>
685    ///             <span>World</span>
686    ///             <span>.</span>
687    ///         </div>
688    ///     </div>
689    /// "#, Default::default()).unwrap();
690    ///
691    /// let a = dom.get_element_by_id("a")
692    ///     .unwrap()
693    ///     .get(dom.parser())
694    ///     .unwrap()
695    ///     .as_tag()
696    ///     .unwrap();
697    ///
698    /// // Calling this function on the first div tag (#a) will return a slice containing 3 elements:
699    /// // - whitespaces around (before and after) div#b
700    /// // - div#b itself
701    /// // It does **not** contain the inner span tags
702    /// assert_eq!(a.children().top().len(), 3);
703    /// ```
704    #[inline]
705    pub fn top(&self) -> &RawChildren {
706        &self.0._children
707    }
708
709    /// Returns the starting boundary of the children of this tag.
710    #[inline]
711    pub fn start(&self) -> Option<InnerNodeHandle> {
712        self.0._children.get(0).map(NodeHandle::get_inner)
713    }
714
715    /// Returns the ending boundary of the children of this tag.
716    pub fn end<
717        const MAX_NODES: usize,
718        const MAX_STACK: usize,
719        const MAX_ROOTS: usize,
720        const MAX_IDS: usize,
721        const MAX_CLASSES: usize,
722        const MAX_SELECTOR_NODES: usize,
723    >(
724        &self,
725        parser: &Parser<
726            'a,
727            MAX_NODES,
728            MAX_STACK,
729            MAX_ROOTS,
730            MAX_IDS,
731            MAX_CLASSES,
732            MAX_SELECTOR_NODES,
733        >,
734    ) -> Option<InnerNodeHandle> {
735        find_last_node_handle(self.0, parser).map(|h| h.get_inner())
736    }
737
738    /// Returns the (start, end) boundaries of the children of this tag.
739    #[inline]
740    pub fn boundaries<
741        const MAX_NODES: usize,
742        const MAX_STACK: usize,
743        const MAX_ROOTS: usize,
744        const MAX_IDS: usize,
745        const MAX_CLASSES: usize,
746        const MAX_SELECTOR_NODES: usize,
747    >(
748        &self,
749        parser: &Parser<
750            'a,
751            MAX_NODES,
752            MAX_STACK,
753            MAX_ROOTS,
754            MAX_IDS,
755            MAX_CLASSES,
756            MAX_SELECTOR_NODES,
757        >,
758    ) -> Option<(InnerNodeHandle, InnerNodeHandle)> {
759        self.start().zip(self.end(parser))
760    }
761
762    /// Returns a slice containing all of the children of this [`HTMLTag`],
763    /// including all subnodes of the children.
764    ///
765    /// The difference between `top()` and `all()` is the same as `VDom::children()` and `VDom::nodes()`
766    ///
767    /// # Example
768    /// ```
769    /// #[cfg(feature = "std")]
770    /// let dom = tl::parse(r#"
771    ///     <div id="a"><div id="b"><span>Hello</span><span>World</span><span>!</span></div></div>
772    /// "#, Default::default()).unwrap();
773    /// #[cfg(not(feature = "std"))]
774    /// let dom = tl::parse::<16, 16, 16, 4, 4, 4>(r#"
775    ///     <div id="a"><div id="b"><span>Hello</span><span>World</span><span>!</span></div></div>
776    /// "#, Default::default()).unwrap();
777    ///
778    /// let a = dom.get_element_by_id("a")
779    ///     .unwrap()
780    ///     .get(dom.parser())
781    ///     .unwrap()
782    ///     .as_tag()
783    ///     .unwrap();
784    ///
785    /// // Calling this function on the first div tag (#a) will return a slice containing all of the subnodes:
786    /// // - div#b
787    /// // - span
788    /// // - Hello
789    /// // - span
790    /// // - World
791    /// // - span
792    /// // - !
793    /// assert_eq!(a.children().all(dom.parser()).len(), 7);
794    /// ```
795    pub fn all<
796        const MAX_NODES: usize,
797        const MAX_STACK: usize,
798        const MAX_ROOTS: usize,
799        const MAX_IDS: usize,
800        const MAX_CLASSES: usize,
801        const MAX_SELECTOR_NODES: usize,
802    >(
803        &self,
804        parser: &'b Parser<
805            'a,
806            MAX_NODES,
807            MAX_STACK,
808            MAX_ROOTS,
809            MAX_IDS,
810            MAX_CLASSES,
811            MAX_SELECTOR_NODES,
812        >,
813    ) -> &'b [Node<'a>] {
814        self.boundaries(parser)
815            .map(|(start, end)| &parser.tags.as_slice()[start as usize..=end as usize])
816            .unwrap_or(&[])
817    }
818}
819
820/// A thin mutable wrapper around the children of [`HTMLTag`]
821#[derive(Debug)]
822pub struct ChildrenMut<'a, 'b>(&'b mut HTMLTag<'a>);
823
824impl<'a, 'b> ChildrenMut<'a, 'b> {
825    /// Returns the topmost, direct children of this tag as a mutable slice.
826    ///
827    /// See [`Children::top`] for more details and examples.
828    #[inline]
829    pub fn top_mut(&mut self) -> &mut RawChildren {
830        &mut self.0._children
831    }
832}
833
834/// Attempts to find the very last node handle that is contained in the given tag
835fn find_last_node_handle<
836    'a,
837    const MAX_NODES: usize,
838    const MAX_STACK: usize,
839    const MAX_ROOTS: usize,
840    const MAX_IDS: usize,
841    const MAX_CLASSES: usize,
842    const MAX_SELECTOR_NODES: usize,
843>(
844    tag: &HTMLTag<'a>,
845    parser: &Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>,
846) -> Option<NodeHandle> {
847    let last_handle = tag._children.as_slice().last().copied()?;
848
849    let child = last_handle
850        .get(parser)
851        .expect("Failed to get child node, please open a bug report") // this shouldn't happen
852        .as_tag();
853
854    if let Some(child) = child {
855        // Recursively call this function to get to the innermost node
856        find_last_node_handle(child, parser).or(Some(last_handle))
857    } else {
858        Some(last_handle)
859    }
860}
861
862/// An HTML Node
863#[derive(Debug, Clone)]
864#[allow(clippy::large_enum_variant)]
865pub enum Node<'a> {
866    /// A regular HTML element/tag
867    Tag(HTMLTag<'a>),
868    /// Raw text (no particular HTML element)
869    Raw(Bytes<'a>),
870    /// Comment (<!-- -->)
871    Comment(Bytes<'a>),
872}
873
874impl<'a> Node<'a> {
875    /// Writes the outer HTML representation of this node without allocating.
876    pub fn write_outer_html<
877        W: fmt::Write,
878        const MAX_NODES: usize,
879        const MAX_STACK: usize,
880        const MAX_ROOTS: usize,
881        const MAX_IDS: usize,
882        const MAX_CLASSES: usize,
883        const MAX_SELECTOR_NODES: usize,
884    >(
885        &self,
886        parser: &Parser<
887            'a,
888            MAX_NODES,
889            MAX_STACK,
890            MAX_ROOTS,
891            MAX_IDS,
892            MAX_CLASSES,
893            MAX_SELECTOR_NODES,
894        >,
895        dest: &mut W,
896    ) -> fmt::Result {
897        match self {
898            Node::Comment(c) | Node::Raw(c) => dest.write_str(c.try_as_utf8_str().unwrap_or("")),
899            Node::Tag(t) => t.write_outer_html(parser, dest),
900        }
901    }
902
903    /// Returns the inner text of this node
904    #[cfg(feature = "std")]
905    pub fn inner_text<
906        's,
907        'p: 's,
908        const MAX_NODES: usize,
909        const MAX_STACK: usize,
910        const MAX_ROOTS: usize,
911        const MAX_IDS: usize,
912        const MAX_CLASSES: usize,
913        const MAX_SELECTOR_NODES: usize,
914    >(
915        &'s self,
916        parser: &'p Parser<
917            'a,
918            MAX_NODES,
919            MAX_STACK,
920            MAX_ROOTS,
921            MAX_IDS,
922            MAX_CLASSES,
923            MAX_SELECTOR_NODES,
924        >,
925    ) -> Cow<'s, str> {
926        match self {
927            Node::Comment(_) => Cow::Borrowed(""),
928            Node::Raw(r) => r.as_utf8_str(),
929            Node::Tag(t) => t.inner_text(parser),
930        }
931    }
932
933    /// Returns the outer HTML of this node
934    #[cfg(feature = "std")]
935    pub fn outer_html<
936        's,
937        const MAX_NODES: usize,
938        const MAX_STACK: usize,
939        const MAX_ROOTS: usize,
940        const MAX_IDS: usize,
941        const MAX_CLASSES: usize,
942        const MAX_SELECTOR_NODES: usize,
943    >(
944        &'s self,
945        parser: &Parser<
946            'a,
947            MAX_NODES,
948            MAX_STACK,
949            MAX_ROOTS,
950            MAX_IDS,
951            MAX_CLASSES,
952            MAX_SELECTOR_NODES,
953        >,
954    ) -> Cow<'s, str> {
955        match self {
956            Node::Comment(c) => c.as_utf8_str(),
957            Node::Raw(r) => r.as_utf8_str(),
958            Node::Tag(t) => Cow::Owned(t.outer_html(parser)),
959        }
960    }
961
962    /// Returns the inner HTML of this node
963    #[cfg(feature = "std")]
964    pub fn inner_html<
965        's,
966        const MAX_NODES: usize,
967        const MAX_STACK: usize,
968        const MAX_ROOTS: usize,
969        const MAX_IDS: usize,
970        const MAX_CLASSES: usize,
971        const MAX_SELECTOR_NODES: usize,
972    >(
973        &'s self,
974        parser: &Parser<
975            'a,
976            MAX_NODES,
977            MAX_STACK,
978            MAX_ROOTS,
979            MAX_IDS,
980            MAX_CLASSES,
981            MAX_SELECTOR_NODES,
982        >,
983    ) -> Cow<'s, str> {
984        match self {
985            Node::Comment(c) => c.as_utf8_str(),
986            Node::Raw(r) => r.as_utf8_str(),
987            Node::Tag(t) => Cow::Owned(t.inner_html(parser)),
988        }
989    }
990
991    /// Returns an iterator over subnodes ("children") of this HTML tag, if this is a tag
992    pub fn children(&self) -> Option<Children<'a, '_>> {
993        match self {
994            Node::Tag(t) => Some(t.children()),
995            _ => None,
996        }
997    }
998
999    /// Calls the given closure with each tag as parameter
1000    ///
1001    /// The closure must return a boolean, indicating whether it should stop iterating
1002    /// Returning `true` will break the loop and return a handle to the node
1003    pub fn find_node<F>(&self, parser: &Parser<'a>, f: &mut F) -> Option<NodeHandle>
1004    where
1005        F: FnMut(&Node<'a>) -> bool,
1006    {
1007        if let Some(children) = self.children() {
1008            for &id in children.top().iter() {
1009                let node = id.get(parser).unwrap();
1010
1011                if f(node) {
1012                    return Some(id);
1013                }
1014
1015                let subnode = node.find_node(parser, f);
1016                if subnode.is_some() {
1017                    return subnode;
1018                }
1019            }
1020        }
1021        None
1022    }
1023
1024    /// Tries to coerce this node into a `HTMLTag` variant
1025    pub fn as_tag(&self) -> Option<&HTMLTag<'a>> {
1026        match self {
1027            Self::Tag(tag) => Some(tag),
1028            _ => None,
1029        }
1030    }
1031
1032    /// Tries to coerce this node into a `HTMLTag` variant
1033    pub fn as_tag_mut(&mut self) -> Option<&mut HTMLTag<'a>> {
1034        match self {
1035            Self::Tag(tag) => Some(tag),
1036            _ => None,
1037        }
1038    }
1039
1040    /// Tries to coerce this node into a comment, returning the text
1041    pub fn as_comment(&self) -> Option<&Bytes<'a>> {
1042        match self {
1043            Self::Comment(c) => Some(c),
1044            _ => None,
1045        }
1046    }
1047
1048    /// Tries to coerce this node into a comment, returning the text
1049    pub fn as_comment_mut(&mut self) -> Option<&mut Bytes<'a>> {
1050        match self {
1051            Self::Comment(c) => Some(c),
1052            _ => None,
1053        }
1054    }
1055
1056    /// Tries to coerce this node into a raw text node, returning the text
1057    ///
1058    /// "Raw text nodes" are nodes that are not HTML tags, but just text
1059    pub fn as_raw(&self) -> Option<&Bytes<'a>> {
1060        match self {
1061            Self::Raw(r) => Some(r),
1062            _ => None,
1063        }
1064    }
1065
1066    /// Tries to coerce this node into a mutable raw text node, returning the text
1067    ///
1068    /// "Raw text nodes" are nodes that are not HTML tags, but just text
1069    pub fn as_raw_mut(&mut self) -> Option<&mut Bytes<'a>> {
1070        match self {
1071            Self::Raw(r) => Some(r),
1072            _ => None,
1073        }
1074    }
1075}