css_ast/selector/
tag.rs

1use css_parse::{Build, Cursor, Parser, Peek, T, keyword_set};
2use csskit_derives::{IntoCursor, ToCursors, Visitable};
3
4#[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
6#[visit]
7pub enum Tag {
8	Html(HtmlTag),
9	HtmlNonConforming(HtmlNonConformingTag),
10	HtmlNonStandard(HtmlNonStandardTag),
11	Svg(SvgTag),
12	Mathml(MathmlTag),
13	CustomElement(CustomElementTag),
14	Unknown(UnknownTag),
15}
16
17impl<'a> Peek<'a> for Tag {
18	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
19		<T![Ident]>::peek(p, c)
20	}
21}
22
23impl<'a> Build<'a> for Tag {
24	fn build(p: &Parser<'a>, c: Cursor) -> Self {
25		if HtmlTag::peek(p, c) {
26			Self::Html(HtmlTag::build(p, c))
27		} else if SvgTag::peek(p, c) {
28			Self::Svg(SvgTag::build(p, c))
29		} else if MathmlTag::peek(p, c) {
30			Self::Mathml(MathmlTag::build(p, c))
31		} else if CustomElementTag::peek(p, c) {
32			Self::CustomElement(CustomElementTag::build(p, c))
33		} else if HtmlNonConformingTag::peek(p, c) {
34			Self::HtmlNonConforming(HtmlNonConformingTag::build(p, c))
35		} else if HtmlNonStandardTag::peek(p, c) {
36			Self::HtmlNonStandard(HtmlNonStandardTag::build(p, c))
37		} else {
38			Self::Unknown(UnknownTag::build(p, c))
39		}
40	}
41}
42
43#[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
45#[visit(self)]
46pub struct CustomElementTag(T![Ident]);
47
48impl CustomElementTag {
49	const INVALID: phf::Map<&'static str, bool> = phf::phf_map! {
50		"annotation-xml" => true,
51		"color-profile" => true,
52		"font-face" => true,
53		"font-face-src" => true,
54		"font-face-uri" => true,
55		"font-face-format" => true,
56		"font-face-name" => true,
57		"missing-glyph" => true,
58	};
59}
60
61impl<'a> Peek<'a> for CustomElementTag {
62	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
63		let str = p.parse_str_lower(c);
64		if *Self::INVALID.get(str).unwrap_or(&false) {
65			return false;
66		}
67		let mut chars = str.chars();
68		if !matches!(chars.next(), Some('a'..='z')) {
69			return false;
70		}
71		let mut has_dash = false;
72		for char in chars {
73			if char == '-' {
74				has_dash = true;
75				continue;
76			}
77			if !matches!(char,
78				'.' |
79				'_' |
80				'0'..='9' |
81				'a'..='z' |
82				'\u{b7}' |
83				'\u{c0}'..='\u{d6}' |
84				'\u{d8}'..='\u{f6}' |
85				'\u{f8}'..='\u{37d}' |
86				'\u{37F}'..='\u{1fff}' |
87				'\u{200c}'..='\u{200d}' |
88				'\u{203f}'..='\u{2040}' |
89				'\u{2070}'..='\u{218f}' |
90				'\u{2c00}'..='\u{2fef}' |
91				'\u{3001}'..='\u{d7ff}' |
92				'\u{f900}'..='\u{fdcf}' |
93				'\u{fdf0}'..='\u{fffd}' |
94				'\u{10000}'..='\u{effff}'
95			) {
96				return false;
97			}
98		}
99		has_dash
100	}
101}
102
103impl<'a> Build<'a> for CustomElementTag {
104	fn build(p: &Parser<'a>, c: Cursor) -> Self {
105		Self(<T![Ident]>::build(p, c))
106	}
107}
108
109keyword_set!(
110	/// <https://html.spec.whatwg.org/multipage/indices.html#elements-3>
111	#[derive(Visitable)]
112	#[visit(self)]
113	pub enum HtmlTag {
114		A: "a",
115		Abbr: "abbr",
116		Address: "address",
117		Area: "area",
118		Article: "article",
119		Aside: "aside",
120		Audio: "audio",
121		B: "b",
122		Base: "base",
123		Bdi: "bdi",
124		Bdo: "bdo",
125		Big: "big",
126		Blockquote: "blockquote",
127		Body: "body",
128		Br: "br",
129		Button: "button",
130		Canvas: "canvas",
131		Caption: "caption",
132		Center: "center",
133		Cite: "cite",
134		Code: "code",
135		Col: "col",
136		Colgroup: "colgroup",
137		Data: "data",
138		Datalist: "datalist",
139		Dd: "dd",
140		Del: "del",
141		Details: "details",
142		Dfn: "dfn",
143		Dialog: "dialog",
144		Dir: "dir",
145		Div: "div",
146		Dl: "dl",
147		Dt: "dt",
148		Em: "em",
149		Embed: "embed",
150		Fieldset: "fieldset",
151		Figcaption: "figcaption",
152		Figure: "figure",
153		Font: "font",
154		Footer: "footer",
155		Form: "form",
156		Frame: "frame",
157		Frameset: "frameset",
158		H1: "h1",
159		H2: "h2",
160		H3: "h3",
161		H4: "h4",
162		H5: "h5",
163		H6: "h6",
164		Head: "head",
165		Header: "header",
166		Hgroup: "hgroup",
167		Hr: "hr",
168		Html: "html",
169		I: "i",
170		Iframe: "iframe",
171		Img: "img",
172		Input: "input",
173		Ins: "ins",
174		Kbd: "kbd",
175		Label: "label",
176		Legend: "legend",
177		Li: "li",
178		Link: "link",
179		Main: "main",
180		Map: "map",
181		Mark: "mark",
182		Marquee: "marquee",
183		Menu: "menu",
184		Menuitem: "menuitem",
185		Meta: "meta",
186		Meter: "meter",
187		Nav: "nav",
188		Nobr: "nobr",
189		Noembed: "noembed",
190		Noframes: "noframes",
191		Noscript: "noscript",
192		Object: "object",
193		Ol: "ol",
194		Optgroup: "optgroup",
195		Option: "option",
196		Output: "output",
197		P: "p",
198		Param: "param",
199		Picture: "picture",
200		Plaintext: "plaintext",
201		Pre: "pre",
202		Progress: "progress",
203		Q: "q",
204		Rb: "rb",
205		Rp: "rp",
206		Rt: "rt",
207		Rtc: "rtc",
208		Ruby: "ruby",
209		S: "s",
210		Samp: "samp",
211		Script: "script",
212		Search: "search",
213		Section: "section",
214		Select: "select",
215		Slot: "slot",
216		Small: "small",
217		Source: "source",
218		Span: "span",
219		Strike: "strike",
220		Strong: "strong",
221		Style: "style",
222		Sub: "sub",
223		Summary: "summary",
224		Sup: "sup",
225		Table: "table",
226		Tbody: "tbody",
227		Td: "td",
228		Template: "template",
229		Textarea: "textarea",
230		Tfoot: "tfoot",
231		Th: "th",
232		Thead: "thead",
233		Time: "time",
234		Title: "title",
235		Tr: "tr",
236		Track: "track",
237		Tt: "tt",
238		U: "u",
239		Ul: "ul",
240		Var: "var",
241		Video: "video",
242		Wbr: "wbr",
243		Xmp: "xmp",
244	}
245);
246
247keyword_set!(
248	/// <https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features>
249	#[derive(Visitable)]
250	#[visit(self)]
251	pub enum HtmlNonConformingTag {
252		Acronym: "acronym",
253		Applet: "applet",
254		Basefont: "basefont",
255		Bgsound: "bgsound",
256		Big: "big",
257		Blink: "blink",
258		Center: "center",
259		Dir: "dir",
260		Font: "font",
261		Frame: "frame",
262		Frameset: "frameset",
263		Isindex: "isindex",
264		Keygen: "keygen",
265		Listing: "listing",
266		Marquee: "marquee",
267		Menuitem: "menuitem",
268		Multicol: "multicol",
269		Nextid: "nextid",
270		Nobr: "nobr",
271		Noembed: "noembed",
272		Noframes: "noframes",
273		Param: "param",
274		Plaintext: "plaintext",
275		Rb: "rb",
276		Rtc: "rtc",
277		Spacer: "spacer",
278		Strike: "strike",
279		Tt: "tt",
280		Xmp: "xmp",
281	}
282);
283
284keyword_set!(
285	#[derive(Visitable)]
286	#[visit(self)]
287	pub enum HtmlNonStandardTag {
288		// https://wicg.github.io/fenced-frame/#the-fencedframe-element
289		Fencedframe: "fencedframe",
290		// https://wicg.github.io/portals/#the-portal-element
291		Portal: "portal",
292		// https://wicg.github.io/PEPC/permission-element.html#the-permission-element
293		Permission: "permission",
294		// https://open-ui.org/components/customizableselect/
295		Selectedcontent: "selectedcontent",
296	}
297);
298
299keyword_set!(
300	/// <https://svgwg.org/svg2-draft/eltindex.html>
301	#[derive(Visitable)]
302	#[visit(self)]
303	pub enum SvgTag {
304		A: "a",
305		Animate: "animate",
306		Animatemotion: "animatemotion",
307		Animatetransform: "animatetransform",
308		Circle: "circle",
309		Clippath: "clippath",
310		Defs: "defs",
311		Desc: "desc",
312		Discard: "discard",
313		Ellipse: "ellipse",
314		Feblend: "feblend",
315		Fecolormatrix: "fecolormatrix",
316		Fecomponenttransfer: "fecomponenttransfer",
317		Fecomposite: "fecomposite",
318		Feconvolvematrix: "feconvolvematrix",
319		Fediffuselighting: "fediffuselighting",
320		Fedisplacementmap: "fedisplacementmap",
321		Fedistantlight: "fedistantlight",
322		Fedropshadow: "fedropshadow",
323		Feflood: "feflood",
324		Fefunca: "fefunca",
325		Fefuncb: "fefuncb",
326		Fefuncg: "fefuncg",
327		Fefuncr: "fefuncr",
328		Fegaussianblur: "fegaussianblur",
329		Feimage: "feimage",
330		Femerge: "femerge",
331		Femergenode: "femergenode",
332		Femorphology: "femorphology",
333		Feoffset: "feoffset",
334		Fepointlight: "fepointlight",
335		Fespecularlighting: "fespecularlighting",
336		Fespotlight: "fespotlight",
337		Fetile: "fetile",
338		Feturbulence: "feturbulence",
339		Filter: "filter",
340		Foreignobject: "foreignobject",
341		G: "g",
342		Image: "image",
343		Line: "line",
344		Lineargradient: "lineargradient",
345		Marker: "marker",
346		Mask: "mask",
347		Metadata: "metadata",
348		Mpath: "mpath",
349		Path: "path",
350		Pattern: "pattern",
351		Polygon: "polygon",
352		Polyline: "polyline",
353		Radialgradient: "radialgradient",
354		Rect: "rect",
355		Script: "script",
356		Set: "set",
357		Stop: "stop",
358		Style: "style",
359		Svg: "svg",
360		Switch: "switch",
361		Symbol: "symbol",
362		Text: "text",
363		Textpath: "textpath",
364		Title: "title",
365		Tspan: "tspan",
366		Use: "use",
367		View: "view",
368	}
369);
370
371keyword_set!(
372	/// <https://w3c.github.io/mathml/#mmlindex_elements>
373	#[derive(Visitable)]
374	#[visit(self)]
375	pub enum MathmlTag {
376		Abs: "abs",
377		And: "and",
378		Annotation: "annotation",
379		AnnotationXml: "annotation-xml",
380		Apply: "apply",
381		Approx: "approx",
382		Arg: "arg",
383		Bind: "bind",
384		Bvar: "bvar",
385		Card: "card",
386		Cartesianproduct: "cartesianproduct",
387		Cbytes: "cbytes",
388		Ceiling: "ceiling",
389		Cerror: "cerror",
390		Ci: "ci",
391		Cn: "cn",
392		Codomain: "codomain",
393		Compose: "compose",
394		Condition: "condition",
395		Conjugate: "conjugate",
396		Cs: "cs",
397		Csymbol: "csymbol",
398		Curl: "curl",
399		Declare: "declare",
400		Degree: "degree",
401		Determinant: "determinant",
402		Diff: "diff",
403		Divergence: "divergence",
404		Divide: "divide",
405		Domain: "domain",
406		Domainofapplication: "domainofapplication",
407		Emptyset: "emptyset",
408		Eq: "eq",
409		Equivalent: "equivalent",
410		Exists: "exists",
411		Exp: "exp",
412		Factorial: "factorial",
413		Factorof: "factorof",
414		Floor: "floor",
415		Fn: "fn",
416		Forall: "forall",
417		Gcd: "gcd",
418		Geq: "geq",
419		Grad: "grad",
420		Gt: "gt",
421		Ident: "ident",
422		Image: "image",
423		Imaginary: "imaginary",
424		Img: "img",
425		Implies: "implies",
426		In: "in",
427		Int: "int",
428		Intersect: "intersect",
429		Interval: "interval",
430		Inverse: "inverse",
431		Lambda: "lambda",
432		Laplacian: "laplacian",
433		Lcm: "lcm",
434		Leq: "leq",
435		Limit: "limit",
436		List: "list",
437		Ln: "ln",
438		Log: "log",
439		Logbase: "logbase",
440		Lowlimit: "lowlimit",
441		Lt: "lt",
442		Maction: "maction",
443		Maligngroup: "maligngroup",
444		Malignmark: "malignmark",
445		Math: "math",
446		Matrix: "matrix",
447		Matrixrow: "matrixrow",
448		Max: "max",
449		Mean: "mean",
450		Median: "median",
451		Menclose: "menclose",
452		Merror: "merror",
453		Mfenced: "mfenced",
454		Mfrac: "mfrac",
455		Mfraction: "mfraction",
456		Mglyph: "mglyph",
457		Mi: "mi",
458		Min: "min",
459		Minus: "minus",
460		Mlabeledtr: "mlabeledtr",
461		Mlongdiv: "mlongdiv",
462		Mmultiscripts: "mmultiscripts",
463		Mn: "mn",
464		Mo: "mo",
465		Mode: "mode",
466		Moment: "moment",
467		Momentabout: "momentabout",
468		Mover: "mover",
469		Mpadded: "mpadded",
470		Mphantom: "mphantom",
471		Mprescripts: "mprescripts",
472		Mroot: "mroot",
473		Mrow: "mrow",
474		Ms: "ms",
475		Mscarries: "mscarries",
476		Mscarry: "mscarry",
477		Msgroup: "msgroup",
478		Msline: "msline",
479		Mspace: "mspace",
480		Msqrt: "msqrt",
481		Msrow: "msrow",
482		Mstack: "mstack",
483		Mstyle: "mstyle",
484		Msub: "msub",
485		Msubsup: "msubsup",
486		Msup: "msup",
487		Mtable: "mtable",
488		Mtd: "mtd",
489		Mtext: "mtext",
490		Mtr: "mtr",
491		Munder: "munder",
492		Munderover: "munderover",
493		Neq: "neq",
494		None: "none",
495		Not: "not",
496		Notin: "notin",
497		Notprsubset: "notprsubset",
498		Notsubset: "notsubset",
499		Or: "or",
500		Otherwise: "otherwise",
501		Outerproduct: "outerproduct",
502		Partialdiff: "partialdiff",
503		Piece: "piece",
504		Piecewise: "piecewise",
505		Plus: "plus",
506		Power: "power",
507		Product: "product",
508		Prsubset: "prsubset",
509		Quotient: "quotient",
510		Real: "real",
511		Reln: "reln",
512		Rem: "rem",
513		Root: "root",
514		Scalarproduct: "scalarproduct",
515		Sdev: "sdev",
516		Selector: "selector",
517		Semantics: "semantics",
518		Sep: "sep",
519		Set: "set",
520		Setdiff: "setdiff",
521		Share: "share",
522		Sin: "sin",
523		Subset: "subset",
524		Sum: "sum",
525		Tendsto: "tendsto",
526		Times: "times",
527		Transpose: "transpose",
528		Union: "union",
529		Uplimit: "uplimit",
530		Variance: "variance",
531		Vector: "vector",
532		Vectorproduct: "vectorproduct",
533		Xo: "xo",
534	}
535);
536
537#[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
538#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
539#[visit(self)]
540pub struct UnknownTag(T![Ident]);
541
542impl<'a> Peek<'a> for UnknownTag {
543	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
544		<T![Ident]>::peek(p, c)
545	}
546}
547
548impl<'a> Build<'a> for UnknownTag {
549	fn build(p: &Parser<'a>, c: Cursor) -> Self {
550		Self(<T![Ident]>::build(p, c))
551	}
552}
553
554#[cfg(test)]
555mod tests {
556	use super::*;
557	use css_parse::assert_parse;
558
559	#[test]
560	fn size_test() {
561		assert_eq!(std::mem::size_of::<Tag>(), 20);
562		assert_eq!(std::mem::size_of::<HtmlTag>(), 16);
563		assert_eq!(std::mem::size_of::<SvgTag>(), 16);
564		assert_eq!(std::mem::size_of::<MathmlTag>(), 16);
565		assert_eq!(std::mem::size_of::<CustomElementTag>(), 12);
566		assert_eq!(std::mem::size_of::<HtmlNonConformingTag>(), 16);
567		assert_eq!(std::mem::size_of::<HtmlNonStandardTag>(), 16);
568	}
569
570	#[test]
571	fn test_writes() {
572		assert_parse!(Tag, "div");
573	}
574}