hotman/
element.rs

1use std::{borrow::Cow, fmt};
2
3use paste::paste;
4
5use crate::{
6    attribute::{self, Events, GlobalAttributes},
7    attribute_traits,
8    format::*,
9    ElementData,
10};
11
12/// Short alias for `br(())`
13pub const BR: element_structs::Br<'static> = element_structs::Br {
14    global: GlobalAttributes::EMPTY,
15    events: Events::NONE,
16    clear: Cow::Borrowed(""),
17    children: Vec::new(),
18};
19
20/// Trait for types of elements
21pub trait Element<'a> {
22    /// Get the mutable events of this element
23    fn events_mut(&mut self) -> &mut Events<'a>;
24    /// Get the mutable children of this element
25    fn children_mut(&mut self) -> &mut Vec<Node<'a>>;
26}
27
28macro_rules! impl_global_attrs {
29    ($name:ident, $($attr:ident),* $(,)?) => {
30        $(
31            paste! {
32                impl<'a> attribute_traits::[<Has $attr:camel>]<'a> for $name<'a> {
33                    fn [<get_ $attr>](&self) -> attribute::[<$attr _ref_t>] {
34                        attribute::[<$attr _take_ref>](&self.global.$attr)
35                    }
36                    fn [<set_ $attr>](&mut self, val: impl Into<attribute::[<$attr _t>]<'a>>) {
37                        self.global.$attr = val.into();
38                    }
39                }
40            }
41        )*
42    }
43}
44
45macro_rules! write_attr {
46    ($this:expr, $f:expr, $attr:ident) => {
47        paste!(attribute::[<$attr _write>](&$this.$attr, $f.f)?);
48    };
49}
50
51macro_rules! elements {
52    ($(($name:ident $(,$attr:ident)* $(,)?)),* $(,)*) => {
53        /// An HTML node
54        #[derive(Debug, Clone)]
55        pub enum Node<'a> {
56            /// A text element
57            Text(Cow<'a, str>),
58            /// A comment,
59            Comment(Cow<'a, str>),
60            $(#[allow(missing_docs)] $name(element_structs::$name<'a>),)*
61        }
62
63        impl<'a> fmt::Display for Node<'a> {
64            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65                match self {
66                    $(Node::$name(element) => write!(f, "{element}"),)*
67                    Node::Text(text) => write!(f, "{text}"),
68                    Node::Comment(comment) => write!(f, "<!--{comment}-->"),
69                }
70            }
71        }
72
73        impl<'a> IndentFormat for Node<'a> {
74            fn indent_fmt(&self, f: &mut IndentFormatter) -> fmt::Result {
75                match self {
76                    $(Node::$name(element) => element.indent_fmt(f),)*
77                    Node::Text(text) => f.write(text),
78                    Node::Comment(comment) => f.write(format_args!("<!--{comment}-->")),
79                }
80            }
81        }
82
83        pub mod element_structs {
84            //! Structs that represent HTML elements
85
86            use super::*;
87            $(
88                paste! {
89                    #[derive(Debug, Clone, Default)]
90                    #[doc = "A [`<" [<$name:lower>] ">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/" [<$name:lower>] ") element"]
91                    pub struct $name<'a> {
92                        /// The global attributes of this element
93                        pub global: GlobalAttributes<'a>,
94                        /// The element's events
95                        pub events: Events<'a>,
96                        $(
97                            #[doc = "The `" $attr "` attribute"]
98                            pub $attr: attribute::[<$attr _t>]<'a>,
99                        )*
100                        /// The children of this element
101                        pub children: Vec<Node<'a>>,
102                    }
103                }
104
105                impl<'a> IndentFormat for $name<'a> {
106                    fn indent_fmt(&self, f: &mut IndentFormatter) -> fmt::Result {
107                        let tag = paste!(stringify!([<$name:lower>]));
108                        f.write(format_args!("<{tag}"))?;
109                        self.global.indent_fmt(f)?;
110                        $(write_attr!(self, f, $attr);)*
111                        for (event, value) in self.events.iter() {
112                            f.write(format_args!(" {event}=\"{value}\""))?;
113                        }
114                        if self.children.is_empty() {
115                            f.write(format_args!(" />"))?;
116                            return Ok(());
117                        }
118                        f.write(format_args!(">"))?;
119                        let single_line = self.children.len() == 1 || self.children.iter().any(|node| matches!(node, Node::Text(_)));
120                        if single_line {
121                            for child in &self.children {
122                                child.indent_fmt(f)?;
123                            }
124                            f.write(format_args!("</{tag}>"))?;
125                            return Ok(());
126                        }
127                        f.writeln("")?;
128                        f.indent();
129                        for child in &self.children {
130                            child.indent_fmt(f)?;
131                            f.writeln("")?;
132                        }
133                        f.dedent();
134                        f.write(format_args!("</{tag}>"))?;
135                        Ok(())
136                    }
137                }
138
139                impl<'a> fmt::Display for $name<'a> {
140                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141                        self.indent_fmt(&mut IndentFormatter::from(f))
142                    }
143                }
144
145                impl<'a> From<$name<'a>> for Node<'a> {
146                    fn from(element: $name<'a>) -> Self {
147                        Node::$name(element)
148                    }
149                }
150
151                impl<'a> Element<'a> for $name<'a> {
152                    fn events_mut(&mut self) -> &mut Events<'a> {
153                        &mut self.events
154                    }
155                    fn children_mut(&mut self) -> &mut Vec<Node<'a>> {
156                        &mut self.children
157                    }
158                }
159
160                impl_global_attrs!($name, id, class, style, title, autofocus, itemscope);
161
162                $(
163                    paste! {
164                        impl<'a> attribute_traits::[<Has $attr:camel>]<'a> for $name<'a> {
165                            fn [<get_ $attr>](&self) -> attribute::[<$attr _ref_t>] {
166                                attribute::[<$attr _take_ref>](&self.$attr)
167                            }
168                            fn [<set_ $attr>](&mut self, val: impl Into<attribute::[<$attr _t>]<'a>>) {
169                                self.$attr = val.into();
170                            }
171                        }
172                    }
173                )*
174            )*
175        }
176
177        $(paste! {
178            #[must_use]
179            #[doc = "Make a [`<" [<$name:lower>] ">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/" [<$name:lower>] ") element"]
180            #[doc = ""]
181            pub fn [<$name:lower>]<'a>(elem_data: impl ElementData<element_structs::$name<'a>>) -> element_structs::$name<'a> {
182                let mut elem = Default::default();
183                elem_data.add_to(&mut elem);
184                elem
185            }
186        })*
187    };
188}
189
190impl<'a> From<String> for Node<'a> {
191    fn from(text: String) -> Self {
192        Node::Text(text.into())
193    }
194}
195
196impl<'a> From<&'a str> for Node<'a> {
197    fn from(text: &'a str) -> Self {
198        Node::Text(text.into())
199    }
200}
201
202impl<'a> From<&'a String> for Node<'a> {
203    fn from(text: &'a String) -> Self {
204        Node::Text(text.as_str().into())
205    }
206}
207
208/// An HTML comment
209#[derive(Debug, Clone)]
210pub struct Comment<T>(pub T);
211
212impl<'a, T> From<Comment<T>> for Node<'a>
213where
214    T: Into<Cow<'a, str>>,
215{
216    fn from(comment: Comment<T>) -> Self {
217        Node::Comment(comment.0.into())
218    }
219}
220
221elements!(
222    (
223        A,
224        download,
225        href,
226        hreflang,
227        ping,
228        referrerpolicy,
229        rel,
230        target,
231        r#type,
232    ),
233    (Abbr),
234    (
235        Area,
236        alt,
237        coords,
238        download,
239        href,
240        hreflang,
241        ping,
242        referrerpolicy,
243        rel,
244        shape,
245        target
246    ),
247    (Audio, autoplay, controls, r#loop, muted, preload, src),
248    (B),
249    (Base, href, target),
250    (Bdi, dir),
251    (Bdo, dir),
252    (Blockquote, cite),
253    (Body),
254    (Br, clear),
255    (
256        Button,
257        disabled,
258        form,
259        formaction,
260        formenctype,
261        formmethod,
262        formnovalidate,
263        formtarget,
264        name,
265        r#type,
266        value
267    ),
268    (Canvas, height, width),
269    (Caption),
270    (Cite),
271    (Code, r#type),
272    (Col, span),
273    (Colgroup, span),
274    (Dd, r#type),
275    (Del, cite, datetime),
276    (Details, open),
277    (Dfn),
278    (Div),
279    (Dl, r#type),
280    (Dt, r#type),
281    (Em, r#type),
282    (Embed, height, src, r#type, width),
283    (Fieldset, disabled, form, name),
284    (
285        Form,
286        action,
287        autocomplete,
288        enctype,
289        method,
290        name,
291        novalidate,
292        target
293    ),
294    (H1),
295    (H2),
296    (H3),
297    (H4),
298    (H5),
299    (H6),
300    (Head, profile),
301    (Hr, align, color, noshade, size, width),
302    (Html, manifest, xmlns),
303    (I),
304    (
305        Iframe,
306        allow,
307        height,
308        loading,
309        name,
310        referrerpolicy,
311        sandbox,
312        src,
313        srcdoc,
314        width
315    ),
316    (
317        Img,
318        alt,
319        crossorigin,
320        decoding,
321        height,
322        importance,
323        intrinsicsize,
324        ismap,
325        loading,
326        referrerpolicy,
327        sizes,
328        src,
329        srcset,
330        usemap,
331        width
332    ),
333    (
334        Input,
335        accept,
336        alt,
337        autocomplete,
338        checked,
339        dirname,
340        disabled,
341        form,
342        formaction,
343        formenctype,
344        formmethod,
345        formnovalidate,
346        formtarget,
347        height,
348        list,
349        max,
350        max_length,
351        min,
352        min_length,
353        multiple,
354        name,
355        pattern,
356        placeholder,
357        readonly,
358        required,
359        size,
360        src,
361        step,
362        r#type,
363        value,
364        width
365    ),
366    (Ins, cite, datetime),
367    (Kbd),
368    (Label, r#for),
369    (Legend),
370    (Li, value),
371    (
372        Link,
373        href,
374        rel,
375        media,
376        hreflang,
377        r#type,
378        sizes,
379        crossorigin,
380        integrity,
381        referrerpolicy
382    ),
383    (Map, name),
384    (Mark),
385    (Menu, r#type, label),
386    (Menuitem, checked, command, default, disabled, icon, label, radiogroup, r#type),
387    (Meta, charset, content, http_equiv, name),
388    (Meter, high, low, max, min, optimum, value),
389    (Noscript),
390    (Object, data, form, height, name, r#type, usemap, width),
391    (Ol, reversed, start, r#type),
392    (Option, disabled, label, selected, value),
393    (Output, r#for, form, name),
394    (P),
395    (Param, name, value),
396    (Progress, max, value),
397    (Q, cite),
398    (Rp),
399    (Rt),
400    (Samp),
401    (
402        Script,
403        r#async,
404        crossorigin,
405        defer,
406        integrity,
407        nomodule,
408        nonce,
409        referrerpolicy,
410        r#type,
411        src
412    ),
413    (Select, disabled, form, multiple, name, required, size),
414    (Slot, name),
415    (Small),
416    (Source, media, sizes, src, srcset, r#type),
417    (Span),
418    (Strong),
419    (Style, media, nonce, r#type),
420    (Sub),
421    (Summary),
422    (Sup),
423    (Table),
424    (Tbody),
425    (Td, colspan, headers, rowspan),
426    (Template),
427    (
428        Textarea,
429        autocomplete,
430        cols,
431        dirname,
432        disabled,
433        form,
434        maxlength,
435        minlength,
436        name,
437        placeholder,
438        readonly,
439        required,
440        rows,
441        wrap
442    ),
443    (Tfoot),
444    (Th, colspan, headers, rowspan, scope),
445    (Thead),
446    (Time, datetime),
447    (Title),
448    (Tr),
449    (Track, default, kind, label, src, srclang),
450    (Ul),
451    (Var),
452    (
453        Video,
454        autoplay,
455        controls,
456        crossorigin,
457        height,
458        r#loop,
459        muted,
460        playsinline,
461        poster,
462        preload,
463        src,
464        width
465    ),
466    (Wbr),
467);