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
12pub 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
20pub trait Element<'a> {
22 fn events_mut(&mut self) -> &mut Events<'a>;
24 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 #[derive(Debug, Clone)]
55 pub enum Node<'a> {
56 Text(Cow<'a, str>),
58 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 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 pub global: GlobalAttributes<'a>,
94 pub events: Events<'a>,
96 $(
97 #[doc = "The `" $attr "` attribute"]
98 pub $attr: attribute::[<$attr _t>]<'a>,
99 )*
100 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#[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);