1use std::fmt;
2
3use syn::{
4 parse::{Parse, ParseStream},
5 Ident,
6};
7
8#[derive(Debug, Clone)]
9pub enum HtmlElement {
10 Html,
11 Base,
12 Head,
13 Link,
14 Meta,
15 Style,
16 Title,
17 Body,
18 Address,
19 Article,
20 Aside,
21 Footer,
22 Header,
23 H1,
24 H2,
25 H3,
26 H4,
27 H5,
28 H6,
29 Hgroup,
30 Main,
31 Nav,
32 Section,
33 Search,
34 Blockquote,
35 Dd,
36 Div,
37 Dl,
38 Dt,
39 Figcaption,
40 Figure,
41 Hr,
42 Li,
43 Menu,
44 Ol,
45 P,
46 Pre,
47 Ul,
48 A,
49 Abbr,
50 B,
51 Bdi,
52 Bdo,
53 Br,
54 Cite,
55 Code,
56 Data,
57 Dfn,
58 Em,
59 I,
60 Kbd,
61 Mark,
62 Q,
63 Rp,
64 Rt,
65 Ruby,
66 S,
67 Samp,
68 Small,
69 Span,
70 Strong,
71 Sub,
72 Sup,
73 Time,
74 U,
75 Var,
76 Wbr,
77 Area,
78 Audio,
79 Img,
80 Map,
81 Track,
82 Video,
83 Embed,
84 Fencedframe,
85 Iframe,
86 Object,
87 Picture,
88 Source,
89 Svg,
90 Path,
91 Math,
92 Canvas,
93 Noscript,
94 Script,
95 Del,
96 Ins,
97 Caption,
98 Col,
99 Colgroup,
100 Table,
101 Tbody,
102 Td,
103 Tfoot,
104 Th,
105 Thead,
106 Tr,
107 Button,
108 Datalist,
109 FieldSet,
110 Form,
111 Input,
112 Label,
113 Legend,
114 Meter,
115 Optgroup,
116 Option,
117 Output,
118 Progress,
119 Select,
120 Selectedcontent,
121 Textarea,
122 Details,
123 Dialog,
124 Summary,
125 Slot,
126 Template,
127 Fragment,
128}
129
130impl HtmlElement {
131 const VOID_ELEMENTS: [HtmlElement; 14] = [
132 HtmlElement::Area,
133 HtmlElement::Base,
134 HtmlElement::Br,
135 HtmlElement::Col,
136 HtmlElement::Embed,
137 HtmlElement::Hr,
138 HtmlElement::Img,
139 HtmlElement::Input,
140 HtmlElement::Link,
141 HtmlElement::Meta,
142 HtmlElement::Source,
143 HtmlElement::Track,
144 HtmlElement::Wbr,
145 HtmlElement::Path,
146 ];
147
148 #[allow(dead_code)]
149 const OPTIONAL_CLOSING_ELEMENTS: [HtmlElement; 15] = [
150 HtmlElement::Html,
151 HtmlElement::Head,
152 HtmlElement::Body,
153 HtmlElement::P,
154 HtmlElement::Li,
155 HtmlElement::Dt,
156 HtmlElement::Dd,
157 HtmlElement::Option,
158 HtmlElement::Thead,
159 HtmlElement::Th,
160 HtmlElement::Tbody,
161 HtmlElement::Tr,
162 HtmlElement::Td,
163 HtmlElement::Tfoot,
164 HtmlElement::Colgroup,
165 ];
166
167 pub fn is_void(&self) -> bool {
168 Self::VOID_ELEMENTS.contains(&self)
169 }
170}
171
172impl fmt::Display for HtmlElement {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 let tag = match self {
175 HtmlElement::Html => "html",
176 HtmlElement::Base => "base",
177 HtmlElement::Head => "head",
178 HtmlElement::Link => "link",
179 HtmlElement::Meta => "meta",
180 HtmlElement::Style => "style",
181 HtmlElement::Title => "title",
182 HtmlElement::Body => "body",
183 HtmlElement::Address => "address",
184 HtmlElement::Article => "article",
185 HtmlElement::Aside => "aside",
186 HtmlElement::Footer => "footer",
187 HtmlElement::Header => "header",
188 HtmlElement::H1 => "h1",
189 HtmlElement::H2 => "h2",
190 HtmlElement::H3 => "h3",
191 HtmlElement::H4 => "h4",
192 HtmlElement::H5 => "h5",
193 HtmlElement::H6 => "h6",
194 HtmlElement::Hgroup => "hgroup",
195 HtmlElement::Main => "main",
196 HtmlElement::Nav => "nav",
197 HtmlElement::Section => "section",
198 HtmlElement::Search => "search",
199 HtmlElement::Blockquote => "blockquote",
200 HtmlElement::Dd => "dd",
201 HtmlElement::Div => "div",
202 HtmlElement::Dl => "dl",
203 HtmlElement::Dt => "dt",
204 HtmlElement::Figcaption => "figcaption",
205 HtmlElement::Figure => "figure",
206 HtmlElement::Hr => "hr",
207 HtmlElement::Li => "li",
208 HtmlElement::Menu => "menu",
209 HtmlElement::Ol => "ol",
210 HtmlElement::P => "p",
211 HtmlElement::Pre => "pre",
212 HtmlElement::Ul => "ul",
213 HtmlElement::A => "a",
214 HtmlElement::Abbr => "abbr",
215 HtmlElement::B => "b",
216 HtmlElement::Bdi => "bdi",
217 HtmlElement::Bdo => "bdo",
218 HtmlElement::Br => "br",
219 HtmlElement::Cite => "cite",
220 HtmlElement::Code => "code",
221 HtmlElement::Data => "data",
222 HtmlElement::Dfn => "dfn",
223 HtmlElement::Em => "em",
224 HtmlElement::I => "i",
225 HtmlElement::Kbd => "kbd",
226 HtmlElement::Mark => "mark",
227 HtmlElement::Q => "q",
228 HtmlElement::Rp => "rp",
229 HtmlElement::Rt => "rt",
230 HtmlElement::Ruby => "ruby",
231 HtmlElement::S => "s",
232 HtmlElement::Samp => "samp",
233 HtmlElement::Small => "small",
234 HtmlElement::Span => "span",
235 HtmlElement::Strong => "strong",
236 HtmlElement::Sub => "sub",
237 HtmlElement::Sup => "sup",
238 HtmlElement::Time => "time",
239 HtmlElement::U => "u",
240 HtmlElement::Var => "var",
241 HtmlElement::Wbr => "wbr",
242 HtmlElement::Area => "area",
243 HtmlElement::Audio => "audio",
244 HtmlElement::Img => "img",
245 HtmlElement::Map => "map",
246 HtmlElement::Track => "track",
247 HtmlElement::Video => "video",
248 HtmlElement::Embed => "embed",
249 HtmlElement::Fencedframe => "fencedframe",
250 HtmlElement::Iframe => "iframe",
251 HtmlElement::Object => "object",
252 HtmlElement::Picture => "picture",
253 HtmlElement::Source => "source",
254 HtmlElement::Svg => "svg",
255 HtmlElement::Path => "path",
256 HtmlElement::Math => "math",
257 HtmlElement::Canvas => "canvas",
258 HtmlElement::Noscript => "noscript",
259 HtmlElement::Script => "script",
260 HtmlElement::Del => "del",
261 HtmlElement::Ins => "ins",
262 HtmlElement::Caption => "caption",
263 HtmlElement::Col => "col",
264 HtmlElement::Colgroup => "colgroup",
265 HtmlElement::Table => "table",
266 HtmlElement::Tbody => "tbody",
267 HtmlElement::Td => "td",
268 HtmlElement::Tfoot => "tfoot",
269 HtmlElement::Th => "th",
270 HtmlElement::Thead => "thead",
271 HtmlElement::Tr => "tr",
272 HtmlElement::Button => "button",
273 HtmlElement::Datalist => "datalist",
274 HtmlElement::FieldSet => "fieldset",
275 HtmlElement::Form => "form",
276 HtmlElement::Input => "input",
277 HtmlElement::Label => "label",
278 HtmlElement::Legend => "legend",
279 HtmlElement::Meter => "meter",
280 HtmlElement::Optgroup => "optgroup",
281 HtmlElement::Option => "option",
282 HtmlElement::Output => "output",
283 HtmlElement::Progress => "progress",
284 HtmlElement::Select => "select",
285 HtmlElement::Selectedcontent => "selectedcontent",
286 HtmlElement::Textarea => "textarea",
287 HtmlElement::Details => "details",
288 HtmlElement::Dialog => "dialog",
289 HtmlElement::Summary => "summary",
290 HtmlElement::Slot => "slot",
291 HtmlElement::Template => "template",
292 HtmlElement::Fragment => "",
293 };
294
295 write!(f, "{tag}")
296 }
297}
298
299impl HtmlElement {
300 pub fn from_str(s: &str) -> Option<Self> {
301 use HtmlElement::*;
302 match s {
303 "html" => Some(Html),
304 "base" => Some(Base),
305 "head" => Some(Head),
306 "link" => Some(Link),
307 "meta" => Some(Meta),
308 "style" => Some(Style),
309 "title" => Some(Title),
310 "body" => Some(Body),
311 "address" => Some(Address),
312 "article" => Some(Article),
313 "aside" => Some(Aside),
314 "footer" => Some(Footer),
315 "header" => Some(Header),
316 "h1" => Some(H1),
317 "h2" => Some(H2),
318 "h3" => Some(H3),
319 "h4" => Some(H4),
320 "h5" => Some(H5),
321 "h6" => Some(H6),
322 "hgroup" => Some(Hgroup),
323 "main" => Some(Main),
324 "nav" => Some(Nav),
325 "section" => Some(Section),
326 "search" => Some(Search),
327 "blockquote" => Some(Blockquote),
328 "dd" => Some(Dd),
329 "div" => Some(Div),
330 "dl" => Some(Dl),
331 "dt" => Some(Dt),
332 "figcaption" => Some(Figcaption),
333 "figure" => Some(Figure),
334 "hr" => Some(Hr),
335 "li" => Some(Li),
336 "menu" => Some(Menu),
337 "ol" => Some(Ol),
338 "p" => Some(P),
339 "pre" => Some(Pre),
340 "ul" => Some(Ul),
341 "a" => Some(A),
342 "abbr" => Some(Abbr),
343 "b" => Some(B),
344 "bdi" => Some(Bdi),
345 "bdo" => Some(Bdo),
346 "br" => Some(Br),
347 "cite" => Some(Cite),
348 "code" => Some(Code),
349 "data" => Some(Data),
350 "dfn" => Some(Dfn),
351 "em" => Some(Em),
352 "i" => Some(I),
353 "kbd" => Some(Kbd),
354 "mark" => Some(Mark),
355 "q" => Some(Q),
356 "rp" => Some(Rp),
357 "rt" => Some(Rt),
358 "ruby" => Some(Ruby),
359 "s" => Some(S),
360 "samp" => Some(Samp),
361 "small" => Some(Small),
362 "span" => Some(Span),
363 "strong" => Some(Strong),
364 "sub" => Some(Sub),
365 "sup" => Some(Sup),
366 "time" => Some(Time),
367 "u" => Some(U),
368 "var" => Some(Var),
369 "wbr" => Some(Wbr),
370 "area" => Some(Area),
371 "audio" => Some(Audio),
372 "img" => Some(Img),
373 "map" => Some(Map),
374 "track" => Some(Track),
375 "video" => Some(Video),
376 "embed" => Some(Embed),
377 "fencedframe" => Some(Fencedframe),
378 "iframe" => Some(Iframe),
379 "object" => Some(Object),
380 "picture" => Some(Picture),
381 "source" => Some(Source),
382 "svg" => Some(Svg),
383 "path" => Some(Path),
384 "math" => Some(Math),
385 "canvas" => Some(Canvas),
386 "noscript" => Some(Noscript),
387 "script" => Some(Script),
388 "del" => Some(Del),
389 "ins" => Some(Ins),
390 "caption" => Some(Caption),
391 "col" => Some(Col),
392 "colgroup" => Some(Colgroup),
393 "table" => Some(Table),
394 "tbody" => Some(Tbody),
395 "td" => Some(Td),
396 "tfoot" => Some(Tfoot),
397 "th" => Some(Th),
398 "thead" => Some(Thead),
399 "tr" => Some(Tr),
400 "button" => Some(Button),
401 "datalist" => Some(Datalist),
402 "fieldset" => Some(FieldSet),
403 "form" => Some(Form),
404 "input" => Some(Input),
405 "label" => Some(Label),
406 "legend" => Some(Legend),
407 "meter" => Some(Meter),
408 "optgroup" => Some(Optgroup),
409 "option" => Some(Option),
410 "output" => Some(Output),
411 "progress" => Some(Progress),
412 "select" => Some(Select),
413 "selectedcontent" => Some(Selectedcontent),
414 "textarea" => Some(Textarea),
415 "details" => Some(Details),
416 "dialog" => Some(Dialog),
417 "summary" => Some(Summary),
418 "slot" => Some(Slot),
419 "template" => Some(Template),
420 "fragment" => Some(Fragment),
421 _ => None,
422 }
423 }
424}
425
426impl PartialEq for HtmlElement {
427 fn eq(&self, other: &Self) -> bool {
428 self.to_string() == other.to_string()
429 }
430}
431
432impl Parse for HtmlElement {
433 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
434 let element: Ident = input.parse()?;
435 let element = HtmlElement::from_str(&element.to_string())
436 .ok_or(HtmlElement::Fragment)
437 .map_err(|_| syn::Error::new(input.span(), "The element is unavailable in HTML5"))?;
438 Ok(element)
439 }
440}