lithtml/dom/
element.rs

1use super::node::Node;
2use super::options::FormattingOptions;
3use super::span::SourceSpan;
4use serde::{Deserialize, Serialize, Serializer};
5use std::borrow::Cow;
6use std::collections::{BTreeMap, HashMap};
7use std::default::Default;
8use std::fmt::Display;
9use std::result::Result;
10
11/// Normal: `<div></div>` or Void: `<meta/>`and `<meta>`
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(rename_all = "camelCase")]
14// TODO: Align with: https://html.spec.whatwg.org/multipage/syntax.html#elements-2
15pub enum ElementVariant {
16    /// A normal element can have children, ex: <div></div>.
17    Normal,
18    /// A void element can't have children, ex: <meta /> and <meta>
19    Void,
20}
21
22pub type Attributes<'s> = HashMap<Cow<'s, str>, Option<Cow<'s, str>>>;
23
24/// Most of the parsed html nodes are elements, except for text
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct Element<'s> {
28    /// The name / tag of the element
29    pub name: Cow<'s, str>,
30
31    /// The element variant, if it is of type void or not
32    pub variant: ElementVariant,
33
34    /// All of the elements attributes, except id and class
35    #[serde(skip_serializing_if = "HashMap::is_empty")]
36    #[serde(serialize_with = "ordered_map")]
37    #[serde(default)]
38    pub attributes: Attributes<'s>,
39
40    /// All of the elements classes
41    #[serde(skip_serializing_if = "Vec::is_empty")]
42    #[serde(default)]
43    pub classes: Vec<Cow<'s, str>>,
44
45    /// All of the elements child nodes
46    #[serde(default, borrow, skip_serializing_if = "Vec::is_empty")]
47    pub children: Vec<Node<'s>>,
48
49    /// Span of the element in the parsed source
50    #[serde(skip)]
51    #[serde(default)]
52    pub source_span: SourceSpan<'s>,
53}
54
55impl<'s> Element<'s> {
56    pub fn fmt_opt<W>(&self, f: &mut W, o: &FormattingOptions, depth: usize) -> std::fmt::Result
57    where
58        W: std::fmt::Write,
59    {
60        // write tabs for the depth
61        o.fmt_depth(f, depth)?;
62
63        // write node start
64        write!(f, "<{}", self.name)?;
65
66        // count length of attributes name, value, signs
67        let attr_len: usize = self
68            .attributes
69            .iter()
70            .map(|(k, v)| k.len() + v.as_ref().map(|v| v.len()).unwrap_or(0) + 4)
71            .sum();
72
73        // count classes length
74        let classes_len = if self.classes.is_empty() {
75            0
76        } else {
77            self.classes.iter().map(|c| c.len() + 1).sum::<usize>() + 8
78        };
79
80        // calculate the length of this element
81        let e_len = depth + 1 + self.name.len() + attr_len + 1 + classes_len;
82
83        // print in one line or multiline with depth - depending on space
84        let c_inline = if e_len > o.max_len && o.new_lines {
85            let mut c_inline = String::new();
86            c_inline.push('\n');
87            o.fmt_depth(&mut c_inline, depth + o.tab_size as usize)?;
88            c_inline
89        } else {
90            String::from(" ")
91        };
92
93        // print the classes seperatly
94        if !self.classes.is_empty() {
95            let classes = self
96                .classes
97                .iter()
98                .enumerate()
99                .map(|(i, c)| {
100                    let c = c.trim();
101                    if c.is_empty() {
102                        String::new()
103                    } else if i == 0 {
104                        c.to_string()
105                    } else {
106                        format!(" {c}")
107                    }
108                })
109                .collect::<String>();
110            write!(f, "{0}class={1}{classes}{1}", c_inline, o.quotes())?
111        }
112
113        // print the attributes ordered
114        let ordered_attributes: BTreeMap<_, _> = self.attributes.iter().collect();
115        for (k, v) in ordered_attributes {
116            match v {
117                Some(v) => {
118                    let v = match o.double_quot {
119                        true => v.replace('\"', "\\\""),
120                        false => v.replace('\'', "\\\'"),
121                    };
122                    write!(f, "{0}{k}={1}{v}{1}", c_inline, o.quotes())?
123                }
124                None => write!(f, "{0}{k}", c_inline)?,
125            }
126        }
127
128        // end tag - continue only when not void element
129        match (
130            e_len > o.max_len,
131            self.variant == ElementVariant::Normal && !self.children.is_empty(),
132        ) {
133            (true, true) => {
134                write!(f, "\n")?;
135                o.fmt_depth(f, depth)?;
136                write!(f, ">")?
137            }
138            (true, false) => {
139                write!(f, "\n")?;
140                o.fmt_depth(f, depth)?;
141                write!(f, "/>")?;
142                return Ok(());
143            }
144            (false, true) => write!(f, ">")?,
145            (false, false) => {
146                write!(f, "/>")?;
147                return Ok(());
148            }
149        }
150
151        // print single text children in the same line when not too long
152        if let Some(text) = self.children.get(0).and_then(|c| c.text()) {
153            if self.children.len() == 1
154                && depth + o.tab_size as usize + text.len() + self.name.len() + 3 <= o.max_len
155            {
156                write!(f, "{}", text)?;
157                write!(f, "</{0}>", self.name)?;
158                return Ok(());
159            }
160        }
161
162        // print the normal children
163        for child in self.children.iter() {
164            write!(f, "\n")?;
165            child.fmt_opt(f, o, depth + o.tab_size as usize)?;
166        }
167        write!(f, "\n")?;
168        o.fmt_depth(f, depth)?;
169        write!(f, "</{0}>", self.name)?;
170
171        Ok(())
172    }
173}
174
175impl<'s> Display for Element<'s> {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        self.fmt_opt(f, &FormattingOptions::pretty(), 0)
178    }
179}
180
181impl<'s> Default for Element<'s> {
182    fn default() -> Self {
183        Self {
184            name: Cow::Borrowed(""),
185            variant: ElementVariant::Void,
186            classes: vec![],
187            attributes: HashMap::new(),
188            children: vec![],
189            source_span: SourceSpan::default(),
190        }
191    }
192}
193
194fn ordered_map<S: Serializer>(value: &Attributes, serializer: S) -> Result<S::Ok, S::Error> {
195    let ordered: BTreeMap<_, _> = value.iter().collect();
196    ordered.serialize(serializer)
197}