1extern crate alloc;
4
5mod error;
6
7use self::error::MacroError;
8use alloc::collections::{BTreeMap, BTreeSet};
9use core::mem::replace;
10use muffy_rnc::{
11 Combine, Grammar, GrammarContent, Identifier, NameClass, Pattern, SchemaBody, parse_schema,
12};
13use proc_macro::TokenStream;
14use proc_macro2::Span;
15use quote::quote;
16use std::{fs::read_to_string, path::Path};
17
18#[proc_macro]
20pub fn html(_input: TokenStream) -> TokenStream {
21 generate_html().unwrap_or_else(|error| {
22 syn::Error::new(Span::call_site(), error)
23 .to_compile_error()
24 .into()
25 })
26}
27
28fn generate_html() -> Result<TokenStream, MacroError> {
29 let mut definitions = Default::default();
30
31 for file in ["html5.rnc", "rdfa.rnc"] {
32 load_schema(
33 &Path::new(env!("CARGO_MANIFEST_DIR"))
34 .join("src")
35 .join("schema")
36 .join("html5")
37 .join(file),
38 &mut definitions,
39 )?;
40 }
41
42 let mut element_rules = BTreeMap::<String, (Vec<String>, Vec<String>)>::new();
44
45 for pattern in definitions.values() {
46 for (name_class, pattern) in collect_elements(pattern) {
47 let Some(element) = get_name(name_class) else {
48 continue;
49 };
50
51 let (attributes, children) = element_rules
52 .entry(element)
53 .or_insert_with(|| (vec![], vec![]));
54
55 attributes.extend(collect_attributes(pattern, &definitions)?);
56 children.extend(collect_children(pattern, &definitions)?);
57 }
58 }
59
60 let mut element_matches = vec![];
61
62 for (element, (mut attributes, mut children)) in element_rules {
63 attributes.sort();
64 attributes.dedup();
65 children.sort();
66 children.dedup();
67
68 let attributes = attributes.iter().map(|attribute| quote!(#attribute));
69 let children = children.iter().map(|child| quote!(#child));
70
71 element_matches.push(quote! {
72 #element => {
73 let mut attributes = ::alloc::collections::BTreeMap::<
74 String,
75 ::alloc::collections::BTreeSet<AttributeError>,
76 >::new();
77
78 for (attribute, _) in element.attributes() {
79 if ignored_attributes.iter().any(|pattern| pattern.is_match(attribute)) {
80 continue;
81 }
82
83 match attribute {
84 #(#attributes |)* "_DUMMY_" => {}
85 _ => {
86 attributes
87 .entry(attribute.into())
88 .or_insert_with(Default::default)
89 .insert(AttributeError::NotAllowed);
90 }
91 }
92 }
93
94 let mut children = ::alloc::collections::BTreeMap::<
95 String,
96 ::alloc::collections::BTreeSet<ChildError>,
97 >::new();
98
99 for child in element.children() {
100 if let muffy_document::html::Node::Element(element) = child {
101 let name = element.name();
102
103 if ignored_elements.iter().any(|pattern| pattern.is_match(name)) {
104 continue;
105 }
106
107 match name {
108 #(#children |)* "_DUMMY_" => {}
109 _ => {
110 children
111 .entry(name.into())
112 .or_insert_with(Default::default)
113 .insert(ChildError::NotAllowed);
114 }
115 }
116 }
117 }
118
119 if attributes.is_empty() && children.is_empty() {
120 Ok(())
121 } else {
122 Err(MarkupError::InvalidElement {
123 attributes,
124 children,
125 })
126 }
127 }
128 });
129 }
130
131 Ok(quote! {
132 pub fn validate_html_element(
134 element: &Element,
135 ignored_attributes: &[::regex::Regex],
136 ignored_elements: &[::regex::Regex],
137 ) -> Result<(), MarkupError> {
138 match element.name() {
139 name if ignored_elements.iter().any(|pattern| pattern.is_match(name)) => Ok(()),
140 #(#element_matches)*
141 _ => Err(MarkupError::UnknownTag(element.name().to_string())),
142 }
143 }
144 }
145 .into())
146}
147
148fn load_schema(
149 path: &Path,
150 definitions: &mut BTreeMap<Identifier, Pattern>,
151) -> Result<(), MacroError> {
152 let schema = parse_schema(&read_to_string(path)?)?;
153
154 match schema.body {
157 SchemaBody::Grammar(grammar) => {
158 load_grammar(
159 &grammar,
160 definitions,
161 path.parent().ok_or(MacroError::NoParentDirectory)?,
162 )?;
163 }
164 SchemaBody::Pattern(_) => return Err(MacroError::RncSyntax("top-level pattern")),
165 }
166
167 Ok(())
168}
169
170fn load_grammar(
171 grammar: &Grammar,
172 definitions: &mut BTreeMap<Identifier, Pattern>,
173 directory: &Path,
174) -> Result<(), MacroError> {
175 for content in &grammar.contents {
176 match content {
177 GrammarContent::Definition(definition) => {
178 let name = definition.name.clone();
179 let pattern = definition.pattern.clone();
180
181 if let Some(combine) = definition.combine {
182 combine_patterns(
183 definitions.entry(name).or_insert(Pattern::NotAllowed),
184 pattern,
185 combine,
186 );
187 } else {
188 definitions.insert(name, pattern);
189 }
190 }
191 GrammarContent::Div(grammar) => load_grammar(grammar, definitions, directory)?,
192 GrammarContent::Include(include) => {
193 let include_path = directory.join(&include.uri);
194
195 load_schema(&include_path, definitions)?;
196
197 if let Some(grammar) = &include.grammar {
198 load_grammar(grammar, definitions, directory)?;
199 }
200 }
201 GrammarContent::Annotation(_) | GrammarContent::Start { .. } => {}
202 }
203 }
204
205 Ok(())
206}
207
208fn combine_patterns(existing: &mut Pattern, new: Pattern, combine: Combine) {
209 match combine {
210 Combine::Choice => match existing {
211 Pattern::Choice(choices) => choices.push(new),
212 Pattern::NotAllowed => *existing = new,
213 Pattern::Attribute { .. }
214 | Pattern::Data { .. }
215 | Pattern::Element { .. }
216 | Pattern::Empty
217 | Pattern::External(_)
218 | Pattern::Grammar(_)
219 | Pattern::Group(_)
220 | Pattern::Interleave(_)
221 | Pattern::List(_)
222 | Pattern::Many0(_)
223 | Pattern::Many1(_)
224 | Pattern::Name(_)
225 | Pattern::Optional(_)
226 | Pattern::Text
227 | Pattern::Value { .. } => {
228 let old = replace(existing, Pattern::Choice(vec![]));
229
230 if let Pattern::Choice(choices) = existing {
231 choices.push(old);
232 choices.push(new);
233 }
234 }
235 },
236 Combine::Interleave => match existing {
237 Pattern::Interleave(patterns) => patterns.push(new),
238 Pattern::NotAllowed => *existing = new,
239 Pattern::Attribute { .. }
240 | Pattern::Choice(_)
241 | Pattern::Data { .. }
242 | Pattern::Element { .. }
243 | Pattern::Empty
244 | Pattern::External(_)
245 | Pattern::Grammar(_)
246 | Pattern::Group(_)
247 | Pattern::List(_)
248 | Pattern::Many0(_)
249 | Pattern::Many1(_)
250 | Pattern::Name(_)
251 | Pattern::Optional(_)
252 | Pattern::Text
253 | Pattern::Value { .. } => {
254 let old = replace(existing, Pattern::Interleave(vec![]));
255
256 if let Pattern::Interleave(patterns) = existing {
257 patterns.push(old);
258 patterns.push(new);
259 }
260 }
261 },
262 }
263}
264
265fn collect_elements(pattern: &Pattern) -> Vec<(&NameClass, &Pattern)> {
266 match pattern {
267 Pattern::Element {
268 name_class,
269 pattern,
270 } => vec![(name_class, pattern)],
271 Pattern::Choice(patterns) | Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
272 patterns.iter().flat_map(collect_elements).collect()
273 }
274 Pattern::Many0(pattern) | Pattern::Many1(pattern) | Pattern::Optional(pattern) => {
275 collect_elements(pattern)
276 }
277 Pattern::Attribute { .. }
278 | Pattern::Data { .. }
279 | Pattern::Empty
280 | Pattern::External(_)
281 | Pattern::Grammar(_)
282 | Pattern::List(_)
283 | Pattern::Name(_)
284 | Pattern::NotAllowed
285 | Pattern::Text
286 | Pattern::Value { .. } => vec![],
287 }
288}
289
290fn get_name(name_class: &NameClass) -> Option<String> {
291 match name_class {
292 NameClass::Name(name) => Some(name.local.component.clone()),
293 NameClass::Choice(choices) => choices.iter().find_map(get_name),
294 NameClass::AnyName | NameClass::Except { .. } | NameClass::NamespaceName(_) => None,
295 }
296}
297
298fn collect_attributes(
299 pattern: &Pattern,
300 definitions: &BTreeMap<Identifier, Pattern>,
301) -> Result<BTreeSet<String>, MacroError> {
302 let mut attributes = Default::default();
303
304 collect_nested_attributes(
305 pattern,
306 definitions,
307 &mut attributes,
308 &mut Default::default(),
309 )?;
310
311 Ok(attributes)
312}
313
314fn collect_nested_attributes<'a>(
315 pattern: &'a Pattern,
316 definitions: &'a BTreeMap<Identifier, Pattern>,
317 attributes: &mut BTreeSet<String>,
318 visited: &mut BTreeSet<&'a Identifier>,
319) -> Result<(), MacroError> {
320 match pattern {
321 Pattern::Attribute { name_class, .. } => {
322 if let Some(name) = get_name(name_class) {
323 attributes.insert(name);
324 }
325 }
326 Pattern::Name(name) => {
327 if !visited.contains(&name.local) {
328 visited.insert(&name.local);
329
330 if let Some(pattern) = definitions.get(&name.local) {
331 collect_nested_attributes(pattern, definitions, attributes, visited)?;
332 }
333 }
334 }
335 Pattern::Choice(patterns) | Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
336 for pattern in patterns {
337 collect_nested_attributes(pattern, definitions, attributes, visited)?;
338 }
339 }
340 Pattern::Many0(pattern) | Pattern::Many1(pattern) | Pattern::Optional(pattern) => {
341 collect_nested_attributes(pattern, definitions, attributes, visited)?;
342 }
343 Pattern::Data { .. } => return Err(MacroError::RncPattern("data")),
344 Pattern::External(_) => return Err(MacroError::RncPattern("external")),
345 Pattern::Grammar(_) => return Err(MacroError::RncPattern("grammar")),
346 Pattern::List { .. } => return Err(MacroError::RncPattern("list")),
347 Pattern::Value { .. } => return Err(MacroError::RncPattern("value")),
348 Pattern::Empty | Pattern::Element { .. } | Pattern::NotAllowed | Pattern::Text => {}
349 }
350
351 Ok(())
352}
353
354fn collect_children(
355 pattern: &Pattern,
356 definitions: &BTreeMap<Identifier, Pattern>,
357) -> Result<BTreeSet<String>, MacroError> {
358 let mut children = Default::default();
359
360 collect_nested_children(pattern, definitions, &mut children, &mut Default::default())?;
361
362 Ok(children)
363}
364
365fn collect_nested_children<'a>(
366 pattern: &'a Pattern,
367 definitions: &'a BTreeMap<Identifier, Pattern>,
368 children: &mut BTreeSet<String>,
369 visited: &mut BTreeSet<&'a Identifier>,
370) -> Result<(), MacroError> {
371 match pattern {
372 Pattern::Element { name_class, .. } => {
373 if let Some(name) = get_name(name_class) {
374 children.insert(name);
375 }
376 }
377 Pattern::Name(name) => {
378 if !visited.contains(&name.local) {
379 visited.insert(&name.local);
380
381 if let Some(pattern) = definitions.get(&name.local) {
382 collect_nested_children(pattern, definitions, children, visited)?;
383 }
384 }
385 }
386 Pattern::Choice(patterns) | Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
387 for pattern in patterns {
388 collect_nested_children(pattern, definitions, children, visited)?;
389 }
390 }
391 Pattern::Many0(pattern) | Pattern::Many1(pattern) | Pattern::Optional(pattern) => {
392 collect_nested_children(pattern, definitions, children, visited)?;
393 }
394 Pattern::Data { .. } => return Err(MacroError::RncPattern("data")),
395 Pattern::External(_) => return Err(MacroError::RncPattern("external")),
396 Pattern::Grammar(_) => return Err(MacroError::RncPattern("grammar")),
397 Pattern::List { .. } => return Err(MacroError::RncPattern("list")),
398 Pattern::Value { .. } => return Err(MacroError::RncPattern("value")),
399 Pattern::Attribute { .. } | Pattern::Empty | Pattern::NotAllowed | Pattern::Text => {}
400 }
401
402 Ok(())
403}