1use crate::extractor::{Attributes, FTMLExtractor};
2use crate::open::OpenFTMLElement;
3use crate::prelude::FTMLNode;
4use flams_ontology::ftml::FTMLKey;
5use smallvec::SmallVec;
6
7#[allow(type_alias_bounds)]
8pub type Call<E: FTMLExtractor> = for<'a> fn(
9 &mut E,
10 &mut E::Attr<'a>,
11 &mut SmallVec<FTMLExtractionRule<E>, 4>,
12) -> Option<OpenFTMLElement>;
13
14#[derive(PartialEq, Eq, Hash)]
15pub struct FTMLExtractionRule<E: FTMLExtractor> {
16 pub(crate) tag: FTMLKey,
17 pub(crate) attr: &'static str,
18 call: Call<E>,
19}
20impl<E: FTMLExtractor> Copy for FTMLExtractionRule<E> {}
21impl<E: FTMLExtractor> Clone for FTMLExtractionRule<E> {
22 #[inline]
23 fn clone(&self) -> Self {
24 *self
25 }
26}
27impl<E: FTMLExtractor> FTMLExtractionRule<E> {
28 #[inline]
29 pub(crate) const fn new(tag: FTMLKey, attr: &'static str, call: Call<E>) -> Self {
30 Self { tag, attr, call }
31 }
32 #[inline]
33 fn applies(&self, s: &str) -> bool {
34 s == self.attr
36 }
37}
38
39#[derive(Debug, Clone)]
40pub struct FTMLElements {
41 pub elems: SmallVec<OpenFTMLElement, 4>,
42}
43impl FTMLElements {
44 #[inline]
45 #[must_use]
46 pub fn is_empty(&self) -> bool {
47 self.elems.is_empty()
48 }
49 #[inline]
50 #[must_use]
51 pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
52 self.into_iter()
53 }
54 pub fn close<E: FTMLExtractor, N: FTMLNode>(&mut self, extractor: &mut E, node: &N) {
55 let mut ret = Self {
56 elems: SmallVec::default(),
57 };
58 while let Some(e) = self.elems.pop() {
59 if let Some(r) = e.close(self, &mut ret, extractor, node) {
60 ret.elems.push(r);
61 }
62 }
63 *self = ret;
64 }
65 #[inline]
66 #[must_use]
67 pub fn take(self) -> SmallVec<OpenFTMLElement, 4> {
68 self.elems
69 }
70}
71impl<'a> IntoIterator for &'a FTMLElements {
72 type Item = &'a OpenFTMLElement;
73 type IntoIter = std::iter::Rev<std::slice::Iter<'a, OpenFTMLElement>>;
74 #[inline]
75 fn into_iter(self) -> Self::IntoIter {
76 self.elems.iter().rev()
77 }
78}
79
80pub trait RuleSet<E: FTMLExtractor> {
81 type I<'i>: Iterator<Item = FTMLExtractionRule<E>>
82 where
83 Self: 'i,
84 E: 'i;
85
86 fn iter_rules(&self) -> Self::I<'_>;
87
88 #[allow(clippy::cognitive_complexity)]
89 fn applicable_rules<'a>(
90 &self,
91 extractor: &mut E,
92 attrs: &'a mut E::Attr<'a>,
93 ) -> Option<FTMLElements> {
94 let mut stripped = attrs
95 .keys()
96 .filter(|s| {
97 if s.starts_with(flams_ontology::ftml::PREFIX) {
98 true
100 } else {
101 false
102 }
103 })
104 .collect::<SmallVec<_, 4>>();
105 if stripped.is_empty() {
106 return None;
108 }
109 let mut rules = SmallVec::<_, 4>::new();
111 for rule in self.iter_rules() {
112 if let Some((i, _)) = stripped.iter().enumerate().find(|(_, s)| rule.applies(s)) {
113 rules.push(rule);
115 stripped.remove(i);
116 }
117 }
118 for s in stripped {
119 tracing::warn!(
120 "Unknown ftml attribute: {s} = {}",
121 attrs.value(s).expect("wut").as_ref()
122 );
123 }
124 if rules.is_empty() {
126 return None;
128 }
129 Self::do_rules(extractor, attrs, rules)
130 }
131
132 fn do_rules<'a>(
133 extractor: &mut E,
134 attrs: &'a mut E::Attr<'a>,
135 mut rules: SmallVec<FTMLExtractionRule<E>, 4>,
136 ) -> Option<FTMLElements> {
137 rules.reverse();
138 let mut ret = SmallVec::new();
139 while let Some(rule) = rules.pop() {
140 if let Some(r) = (rule.call)(extractor, attrs, &mut rules) {
142 ret.push(r);
144 }
145 }
146 if ret.is_empty() {
148 None
149 } else {
150 Some(FTMLElements { elems: ret })
151 }
152 }
153}
154impl<const L: usize, E: FTMLExtractor> RuleSet<E> for [FTMLExtractionRule<E>; L] {
155 type I<'i>
156 = std::iter::Copied<std::slice::Iter<'i, FTMLExtractionRule<E>>>
157 where
158 E: 'i;
159 fn iter_rules(&self) -> Self::I<'_> {
160 self.iter().copied()
161 }
162}
163
164#[allow(clippy::module_inception)]
165#[allow(unused_macros)]
166#[allow(clippy::needless_pass_by_value)]
167#[allow(clippy::unnecessary_wraps)]
168pub mod rules {
169 use crate::errors::FTMLError;
170 use crate::open::terms::{OpenArg, OpenTerm, OpenTermKind, PreVar, VarOrSym};
171 use crate::open::OpenFTMLElement;
172 use crate::prelude::{Attributes, FTMLExtractor};
173 use crate::rules::FTMLExtractionRule;
174 use flams_ontology::content::declarations::symbols::{ArgSpec, AssocType};
175 use flams_ontology::ftml::FTMLKey;
176 use flams_ontology::narration::documents::{DocumentStyle, SectionCounter};
177 use flams_ontology::narration::paragraphs::{ParagraphFormatting, ParagraphKind};
178 use flams_ontology::narration::problems::{AnswerKind, FillInSolOption};
179 use flams_ontology::uris::{DocumentElementURI, DocumentURI, ModuleURI, Name, SymbolURI};
180 use flams_utils::vecmap::VecSet;
181 use smallvec::SmallVec;
182 use std::borrow::Cow;
183 use std::str::FromStr;
184
185 #[allow(type_alias_bounds)]
187 pub type SV<E: FTMLExtractor> = SmallVec<FTMLExtractionRule<E>, 4>;
188
189 lazy_static::lazy_static! {
190 static ref ERROR : Name = "ERROR".parse().unwrap_or_else(|_| unreachable!());
191 }
192
193 macro_rules! err {
194 ($extractor:ident,$f:expr) => {
195 match $f {
196 Ok(r) => r,
197 Err(e) => {
198 $extractor.add_error(e);
199 return None;
200 }
201 }
202 };
203 }
204
205 macro_rules! opt {
206 ($extractor:ident,$f:expr) => {
207 match $f {
208 Ok(r) => Some(r),
209 Err(FTMLError::InvalidKeyFor(_, Some(s))) if s.is_empty() => None,
210 Err(e @ FTMLError::InvalidKeyFor(_, Some(_))) => {
211 $extractor.add_error(e);
212 None
213 }
214 _ => None,
215 }
216 };
217 }
218
219 pub fn no_op<E: FTMLExtractor>(
227 _extractor: &mut E,
228 _attrs: &mut E::Attr<'_>,
229 _nexts: &mut SV<E>,
230 ) -> Option<OpenFTMLElement> {
231 None
232 }
233
234 pub fn invisible<E: FTMLExtractor>(
239 _extractor: &mut E,
240 attrs: &mut E::Attr<'_>,
241 _nexts: &mut SV<E>,
242 ) -> Option<OpenFTMLElement> {
243 if attrs.take_bool(FTMLKey::Invisible) {
244 Some(OpenFTMLElement::Invisible)
245 } else {
246 None
247 }
248 }
249
250 pub fn setsectionlevel<E: FTMLExtractor>(
251 extractor: &mut E,
252 attrs: &mut E::Attr<'_>,
253 _nexts: &mut SV<E>,
254 ) -> Option<OpenFTMLElement> {
255 let lvl = err!(extractor, attrs.get_section_level(FTMLKey::SetSectionLevel));
256 Some(OpenFTMLElement::SetSectionLevel(lvl))
257 }
258
259 pub fn style_rule<E: FTMLExtractor>(
260 extractor: &mut E,
261 attrs: &mut E::Attr<'_>,
262 nexts: &mut SV<E>,
263 ) -> Option<OpenFTMLElement> {
264 let Some(style) = attrs.get(FTMLKey::Style) else {
265 unreachable!()
266 };
267 let Ok(mut style) = DocumentStyle::from_str(style.as_ref()) else {
268 extractor.add_error(FTMLError::InvalidURI(style.into()));
269 return None;
270 };
271 if let Some(count) = attrs.get(FTMLKey::Counter) {
272 nexts.retain(|e| e.tag != FTMLKey::Counter);
273 if !count.as_ref().is_empty() {
274 if let Ok(name) = count.as_ref().parse() {
275 style.counter = Some(name);
276 } else {
277 extractor.add_error(FTMLError::InvalidURI(count.into()));
278 return None;
279 }
280 }
281 }
282 extractor.styles().styles.push(style);
283 None
284 }
285
286 pub fn counter_parent<E: FTMLExtractor>(
287 extractor: &mut E,
288 attrs: &mut E::Attr<'_>,
289 nexts: &mut SV<E>,
290 ) -> Option<OpenFTMLElement> {
291 let name = if let Some(count) = attrs.get(FTMLKey::Counter) {
292 nexts.retain(|e| e.tag != FTMLKey::Counter);
293 if let Ok(name) = count.as_ref().parse() {
294 name
295 } else {
296 extractor.add_error(FTMLError::InvalidURI(count.into()));
297 return None;
298 }
299 } else {
300 extractor.add_error(FTMLError::MissingArguments);
301 return None;
302 };
303 let parent = opt!(extractor, attrs.get_section_level(FTMLKey::CounterParent));
304 extractor
305 .styles()
306 .counters
307 .push(SectionCounter { name, parent });
308 None
309 }
310
311 pub fn importmodule<E: FTMLExtractor>(
312 extractor: &mut E,
313 attrs: &mut E::Attr<'_>,
314 _nexts: &mut SV<E>,
315 ) -> Option<OpenFTMLElement> {
316 let uri = err!(
317 extractor,
318 attrs.take_module_uri(FTMLKey::ImportModule, extractor)
319 );
320 Some(OpenFTMLElement::ImportModule(uri))
321 }
322
323 pub fn usemodule<E: FTMLExtractor>(
324 extractor: &mut E,
325 attrs: &mut E::Attr<'_>,
326 _nexts: &mut SV<E>,
327 ) -> Option<OpenFTMLElement> {
328 let uri = err!(
329 extractor,
330 attrs.take_module_uri(FTMLKey::UseModule, extractor)
331 );
332 Some(OpenFTMLElement::UseModule(uri))
333 }
334
335 pub fn module<E: FTMLExtractor>(
336 extractor: &mut E,
337 attrs: &mut E::Attr<'_>,
338 _nexts: &mut SV<E>,
339 ) -> Option<OpenFTMLElement> {
340 let uri = err!(
341 extractor,
342 attrs.take_new_module_uri(FTMLKey::Module, extractor)
343 );
344 let _ = attrs.take_language(FTMLKey::Language);
345 let meta = opt!(
346 extractor,
347 attrs.take_module_uri(FTMLKey::Metatheory, extractor)
348 );
349 let signature = opt!(extractor, attrs.take_language(FTMLKey::Signature));
350 extractor.open_content(uri.clone());
351 extractor.open_narrative(None);
352 Some(OpenFTMLElement::Module {
353 uri,
354 meta,
355 signature,
356 })
358 }
359
360 pub fn mathstructure<E: FTMLExtractor>(
361 extractor: &mut E,
362 attrs: &mut E::Attr<'_>,
363 _nexts: &mut SV<E>,
364 ) -> Option<OpenFTMLElement> {
365 let uri = err!(
366 extractor,
367 attrs.take_new_symbol_uri(FTMLKey::MathStructure, extractor)
368 );
369 let macroname = attrs
370 .remove(FTMLKey::Macroname)
371 .map(|s| Into::<String>::into(s).into_boxed_str());
372 extractor.open_content(uri.clone().into_module());
373 extractor.open_narrative(None);
374 Some(OpenFTMLElement::MathStructure {
375 uri,
376 macroname, })
378 }
379
380 pub fn morphism<E: FTMLExtractor>(
381 extractor: &mut E,
382 attrs: &mut E::Attr<'_>,
383 _nexts: &mut SV<E>,
384 ) -> Option<OpenFTMLElement> {
385 let uri = err!(
386 extractor,
387 attrs.take_new_symbol_uri(FTMLKey::Morphism, extractor)
388 );
389 let domain = err!(
390 extractor,
391 attrs.take_module_uri(FTMLKey::MorphismDomain, extractor)
392 );
393 let total = attrs.take_bool(FTMLKey::MorphismTotal);
394 extractor.open_content(uri.clone().into_module());
395 extractor.open_narrative(None);
396 Some(OpenFTMLElement::Morphism {
397 uri,
398 domain,
399 total, })
401 }
402
403 pub fn assign<E: FTMLExtractor>(
404 extractor: &mut E,
405 attrs: &mut E::Attr<'_>,
406 _nexts: &mut SV<E>,
407 ) -> Option<OpenFTMLElement> {
408 let symbol = err!(extractor, attrs.get_symbol_uri(FTMLKey::Assign, extractor));
409 extractor.open_complex_term();
410 Some(OpenFTMLElement::Assign(symbol))
411 }
412
413 pub fn section<E: FTMLExtractor>(
414 extractor: &mut E,
415 attrs: &mut E::Attr<'_>,
416 _nexts: &mut SV<E>,
417 ) -> Option<OpenFTMLElement> {
418 let lvl = err!(extractor, attrs.get_section_level(FTMLKey::Section));
419 let id = attrs.get_id(extractor, Cow::Borrowed("section"));
420 let Ok(uri) = extractor.get_narrative_uri() & &*id else {
421 extractor.add_error(FTMLError::InvalidURI(format!("7: {id}")));
422 return None;
423 };
424 extractor.open_section(uri.clone());
425 Some(OpenFTMLElement::Section { lvl, uri })
426 }
427
428 pub fn slide<E: FTMLExtractor>(
429 extractor: &mut E,
430 attrs: &mut E::Attr<'_>,
431 _nexts: &mut SV<E>,
432 ) -> Option<OpenFTMLElement> {
433 let id = attrs.get_id(extractor, Cow::Borrowed("slide"));
434 let Ok(uri) = extractor.get_narrative_uri() & &*id else {
435 extractor.add_error(FTMLError::InvalidURI(format!("7: {id}")));
436 return None;
437 };
438 extractor.open_slide();
439 Some(OpenFTMLElement::Slide(uri))
440 }
441
442 pub fn slide_number<E: FTMLExtractor>(
443 _extractor: &mut E,
444 _attrs: &mut E::Attr<'_>,
445 _nexts: &mut SV<E>,
446 ) -> Option<OpenFTMLElement> {
447 Some(OpenFTMLElement::SlideNumber)
448 }
449
450 pub fn skipsection<E: FTMLExtractor>(
451 extractor: &mut E,
452 _attrs: &mut E::Attr<'_>,
453 _nexts: &mut SV<E>,
454 ) -> Option<OpenFTMLElement> {
455 extractor.open_section((DocumentURI::no_doc() & "skip").unwrap_or_else(|_| unreachable!()));
456 Some(OpenFTMLElement::SkipSection)
457 }
458
459 pub fn definition<E: FTMLExtractor>(
460 extractor: &mut E,
461 attrs: &mut E::Attr<'_>,
462 nexts: &mut SV<E>,
463 ) -> Option<OpenFTMLElement> {
464 do_paragraph(extractor, attrs, nexts, ParagraphKind::Definition)
465 }
466 pub fn paragraph<E: FTMLExtractor>(
467 extractor: &mut E,
468 attrs: &mut E::Attr<'_>,
469 nexts: &mut SV<E>,
470 ) -> Option<OpenFTMLElement> {
471 do_paragraph(extractor, attrs, nexts, ParagraphKind::Paragraph)
472 }
473 pub fn assertion<E: FTMLExtractor>(
474 extractor: &mut E,
475 attrs: &mut E::Attr<'_>,
476 nexts: &mut SV<E>,
477 ) -> Option<OpenFTMLElement> {
478 do_paragraph(extractor, attrs, nexts, ParagraphKind::Assertion)
479 }
480 pub fn example<E: FTMLExtractor>(
481 extractor: &mut E,
482 attrs: &mut E::Attr<'_>,
483 nexts: &mut SV<E>,
484 ) -> Option<OpenFTMLElement> {
485 do_paragraph(extractor, attrs, nexts, ParagraphKind::Example)
486 }
487 pub fn proof<E: FTMLExtractor>(
488 extractor: &mut E,
489 attrs: &mut E::Attr<'_>,
490 nexts: &mut SV<E>,
491 ) -> Option<OpenFTMLElement> {
492 do_paragraph(extractor, attrs, nexts, ParagraphKind::Proof)
493 }
494 pub fn subproof<E: FTMLExtractor>(
495 extractor: &mut E,
496 attrs: &mut E::Attr<'_>,
497 nexts: &mut SV<E>,
498 ) -> Option<OpenFTMLElement> {
499 do_paragraph(extractor, attrs, nexts, ParagraphKind::SubProof)
500 }
501
502 fn do_paragraph<E: FTMLExtractor>(
503 extractor: &mut E,
504 attrs: &mut E::Attr<'_>,
505 _nexts: &mut SV<E>,
506 kind: ParagraphKind,
507 ) -> Option<OpenFTMLElement> {
508 let id = attrs.get_id(extractor, Cow::Borrowed(kind.as_str()));
509 let Ok(uri) = extractor.get_narrative_uri() & &*id else {
510 extractor.add_error(FTMLError::InvalidURI(format!("8: {id}")));
511 return None;
512 };
513 let inline = attrs.get_bool(FTMLKey::Inline);
514 let mut fors = VecSet::new();
515 if let Some(f) = attrs.get(FTMLKey::Fors) {
516 for f in f.as_ref().split(',') {
517 if let Ok(f) = f.trim().parse() {
518 fors.insert(f);
519 } else {
520 extractor.add_error(FTMLError::InvalidKeyFor(
521 FTMLKey::Fors.as_str(),
522 Some(f.trim().into()),
523 ));
524 };
525 }
526 }
527 let styles = opt!(
528 extractor,
529 attrs.get_typed_vec(FTMLKey::Styles, |s| s.trim().parse())
530 )
531 .unwrap_or_default();
532 extractor.open_paragraph(uri.clone(), fors);
533 let formatting = if inline {
534 ParagraphFormatting::Inline
535 } else if matches!(kind, ParagraphKind::Proof | ParagraphKind::SubProof) {
536 let hide = attrs.get_bool(FTMLKey::ProofHide);
537 if hide {
538 ParagraphFormatting::Collapsed
539 } else {
540 ParagraphFormatting::Block
541 }
542 } else {
543 ParagraphFormatting::Block
544 };
545 Some(OpenFTMLElement::Paragraph {
546 kind,
547 formatting,
548 styles: styles.into_boxed_slice(),
549 uri,
550 })
551 }
552
553 pub fn proofbody<E: FTMLExtractor>(
554 _extractor: &mut E,
555 _attrs: &mut E::Attr<'_>,
556 _nexts: &mut SV<E>,
557 ) -> Option<OpenFTMLElement> {
558 Some(OpenFTMLElement::ProofBody)
559 }
560
561 pub fn problem<E: FTMLExtractor>(
562 extractor: &mut E,
563 attrs: &mut E::Attr<'_>,
564 nexts: &mut SV<E>,
565 ) -> Option<OpenFTMLElement> {
566 do_problem(extractor, attrs, nexts, false)
567 }
568
569 pub fn subproblem<E: FTMLExtractor>(
570 extractor: &mut E,
571 attrs: &mut E::Attr<'_>,
572 nexts: &mut SV<E>,
573 ) -> Option<OpenFTMLElement> {
574 do_problem(extractor, attrs, nexts, true)
575 }
576
577 fn do_problem<E: FTMLExtractor>(
578 extractor: &mut E,
579 attrs: &mut E::Attr<'_>,
580 _nexts: &mut SV<E>,
581 sub_problem: bool,
582 ) -> Option<OpenFTMLElement> {
583 let styles = opt!(
584 extractor,
585 attrs.get_typed_vec(FTMLKey::Styles, |s| s.trim().parse())
586 )
587 .unwrap_or_default();
588 let id = attrs.get_id(extractor, Cow::Borrowed("problem"));
589 let Ok(uri) = extractor.get_narrative_uri() & &*id else {
590 extractor.add_error(FTMLError::InvalidURI(format!("9: {id}")));
591 return None;
592 };
593 let _ = attrs.take_language(FTMLKey::Language);
594 let autogradable = attrs.get_bool(FTMLKey::Autogradable);
595 let points = attrs.get(FTMLKey::ProblemPoints).and_then(|s| {
596 s.as_ref()
597 .parse()
598 .ok()
599 .or_else(|| Some(s.as_ref().parse::<i32>().ok()? as f32))
600 });
601 extractor.open_problem(uri.clone());
602 Some(OpenFTMLElement::Problem {
603 sub_problem,
604 styles: styles.into_boxed_slice(),
605 uri,
606 autogradable,
607 points,
608 })
609 }
610
611 pub fn problem_hint<E: FTMLExtractor>(
612 _extractor: &mut E,
613 _attrs: &mut E::Attr<'_>,
614 _nexts: &mut SV<E>,
615 ) -> Option<OpenFTMLElement> {
616 Some(OpenFTMLElement::ProblemHint)
618 }
619
620 #[allow(clippy::borrowed_box)]
621 pub fn solution<E: FTMLExtractor>(
622 _extractor: &mut E,
623 attrs: &mut E::Attr<'_>,
624 nexts: &mut SV<E>,
625 ) -> Option<OpenFTMLElement> {
626 let mut id = attrs.remove(FTMLKey::AnswerClass).map(Into::into);
628 nexts.retain(|r| !matches!(r.tag, FTMLKey::AnswerClass));
629 if id.as_ref().is_some_and(|s: &Box<str>| s.is_empty()) {
630 id = None
631 }
632 Some(OpenFTMLElement::ProblemSolution(id))
633 }
634
635 pub fn gnote<E: FTMLExtractor>(
636 extractor: &mut E,
637 _attrs: &mut E::Attr<'_>,
638 _nexts: &mut SV<E>,
639 ) -> Option<OpenFTMLElement> {
640 extractor.open_gnote();
641 Some(OpenFTMLElement::ProblemGradingNote)
642 }
643
644 pub fn answer_class<E: FTMLExtractor>(
645 extractor: &mut E,
646 attrs: &mut E::Attr<'_>,
647 _nexts: &mut SV<E>,
648 ) -> Option<OpenFTMLElement> {
649 let id = attrs.get_id(extractor, Cow::Borrowed("AC"));
650 let kind = opt!(
651 extractor,
652 attrs.get_typed(FTMLKey::AnswerClassPts, str::parse)
653 )
654 .unwrap_or(AnswerKind::Trait(0.0));
655 extractor.push_answer_class(id, kind);
656 Some(OpenFTMLElement::AnswerClass)
657 }
658
659 pub fn ac_feedback<E: FTMLExtractor>(
660 _extractor: &mut E,
661 _attrs: &mut E::Attr<'_>,
662 _nexts: &mut SV<E>,
663 ) -> Option<OpenFTMLElement> {
664 Some(OpenFTMLElement::AnswerClassFeedback)
665 }
666
667 pub fn multiple_choice_block<E: FTMLExtractor>(
668 extractor: &mut E,
669 attrs: &mut E::Attr<'_>,
670 _nexts: &mut SV<E>,
671 ) -> Option<OpenFTMLElement> {
672 let styles = opt!(
673 extractor,
674 attrs.get_typed(FTMLKey::Styles, |s| Result::<_, ()>::Ok(
675 s.split(',')
676 .map(|s| s.trim().to_string().into_boxed_str())
677 .collect::<Vec<_>>()
678 .into_boxed_slice()
679 ))
680 )
681 .unwrap_or_default();
682 let inline = styles.iter().any(|s| &**s == "inline");
683 extractor.open_choice_block(true, styles);
684 Some(OpenFTMLElement::ChoiceBlock {
685 multiple: true,
686 inline,
687 })
688 }
689
690 pub fn single_choice_block<E: FTMLExtractor>(
691 extractor: &mut E,
692 attrs: &mut E::Attr<'_>,
693 _nexts: &mut SV<E>,
694 ) -> Option<OpenFTMLElement> {
695 let styles = opt!(
696 extractor,
697 attrs.get_typed(FTMLKey::Styles, |s| Result::<_, ()>::Ok(
698 s.split(',')
699 .map(|s| s.trim().to_string().into_boxed_str())
700 .collect::<Vec<_>>()
701 .into_boxed_slice()
702 ))
703 )
704 .unwrap_or_default();
705 let inline = styles.iter().any(|s| &**s == "inline");
706 extractor.open_choice_block(false, styles);
707 Some(OpenFTMLElement::ChoiceBlock {
708 multiple: false,
709 inline,
710 })
711 }
712
713 pub fn problem_choice<E: FTMLExtractor>(
714 extractor: &mut E,
715 attrs: &mut E::Attr<'_>,
716 _nexts: &mut SV<E>,
717 ) -> Option<OpenFTMLElement> {
718 let correct = attrs.get_bool(FTMLKey::ProblemChoice); attrs.set(FTMLKey::ProblemChoice.attr_name(), "");
720 extractor.push_problem_choice(correct);
721 Some(OpenFTMLElement::ProblemChoice)
722 }
723
724 pub fn problem_choice_verdict<E: FTMLExtractor>(
725 _extractor: &mut E,
726 _attrs: &mut E::Attr<'_>,
727 _nexts: &mut SV<E>,
728 ) -> Option<OpenFTMLElement> {
729 Some(OpenFTMLElement::ProblemChoiceVerdict)
730 }
731
732 pub fn problem_choice_feedback<E: FTMLExtractor>(
733 _extractor: &mut E,
734 _attrs: &mut E::Attr<'_>,
735 _nexts: &mut SV<E>,
736 ) -> Option<OpenFTMLElement> {
737 Some(OpenFTMLElement::ProblemChoiceFeedback)
738 }
739
740 #[allow(clippy::cast_precision_loss)]
741 pub fn fillinsol<E: FTMLExtractor>(
742 extractor: &mut E,
743 attrs: &mut E::Attr<'_>,
744 _nexts: &mut SV<E>,
745 ) -> Option<OpenFTMLElement> {
746 let val = attrs
747 .get_typed(FTMLKey::ProblemFillinsolWidth, |s| {
748 if s.contains('.') {
749 s.parse::<f32>().map_err(|_| ())
750 } else {
751 s.parse::<i32>().map(|i| i as f32).map_err(|_| ())
752 }
753 })
754 .ok();
755 extractor.open_fillinsol(val);
756 Some(OpenFTMLElement::Fillinsol(val))
757 }
758
759 pub fn fillinsol_case<E: FTMLExtractor>(
760 extractor: &mut E,
761 attrs: &mut E::Attr<'_>,
762 _nexts: &mut SV<E>,
763 ) -> Option<OpenFTMLElement> {
764 let Some(val) = attrs.remove(FTMLKey::ProblemFillinsolCase) else {
765 unreachable!()
766 };
767 let verdict = attrs.take_bool(FTMLKey::ProblemFillinsolCaseVerdict);
768 let Some(value) = attrs.remove(FTMLKey::ProblemFillinsolCaseValue) else {
769 extractor.add_error(FTMLError::IncompleteArgs(5));
770 return None;
771 };
772 let Some(opt) = FillInSolOption::from_values(&val, &value, verdict) else {
773 extractor.add_error(FTMLError::IncompleteArgs(6));
774 return None;
775 };
776 extractor.push_fillinsol_case(opt);
777 Some(OpenFTMLElement::FillinsolCase)
778 }
779
780 pub fn doctitle<E: FTMLExtractor>(
781 _extractor: &mut E,
782 _attrs: &mut E::Attr<'_>,
783 _nexts: &mut SV<E>,
784 ) -> Option<OpenFTMLElement> {
785 Some(OpenFTMLElement::Doctitle)
786 }
787
788 pub fn title<E: FTMLExtractor>(
789 _extractor: &mut E,
790 _attrs: &mut E::Attr<'_>,
791 _nexts: &mut SV<E>,
792 ) -> Option<OpenFTMLElement> {
793 Some(OpenFTMLElement::Title)
794 }
795
796 pub fn prooftitle<E: FTMLExtractor>(
797 _extractor: &mut E,
798 _attrs: &mut E::Attr<'_>,
799 _nexts: &mut SV<E>,
800 ) -> Option<OpenFTMLElement> {
801 Some(OpenFTMLElement::ProofTitle)
802 }
803
804 pub fn subprooftitle<E: FTMLExtractor>(
805 _extractor: &mut E,
806 _attrs: &mut E::Attr<'_>,
807 _nexts: &mut SV<E>,
808 ) -> Option<OpenFTMLElement> {
809 Some(OpenFTMLElement::SubproofTitle)
810 }
811
812 pub fn precondition<E: FTMLExtractor>(
813 extractor: &mut E,
814 attrs: &mut E::Attr<'_>,
815 _nexts: &mut SV<E>,
816 ) -> Option<OpenFTMLElement> {
817 let uri = err!(
818 extractor,
819 attrs.get_symbol_uri(FTMLKey::PreconditionSymbol, extractor)
820 );
821 let dim = err!(
822 extractor,
823 attrs.get_typed(FTMLKey::PreconditionDimension, str::parse)
824 );
825 extractor.add_precondition(uri, dim);
826 None
827 }
828
829 pub fn objective<E: FTMLExtractor>(
830 extractor: &mut E,
831 attrs: &mut E::Attr<'_>,
832 _nexts: &mut SV<E>,
833 ) -> Option<OpenFTMLElement> {
834 let uri = err!(
835 extractor,
836 attrs.get_symbol_uri(FTMLKey::ObjectiveSymbol, extractor)
837 );
838 let dim = err!(
839 extractor,
840 attrs.get_typed(FTMLKey::ObjectiveDimension, str::parse)
841 );
842 extractor.add_objective(uri, dim);
843 None
844 }
845
846 pub fn symdecl<E: FTMLExtractor>(
847 extractor: &mut E,
848 attrs: &mut E::Attr<'_>,
849 _nexts: &mut SV<E>,
850 ) -> Option<OpenFTMLElement> {
851 let uri = err!(
852 extractor,
853 attrs.get_new_symbol_uri(FTMLKey::Symdecl, extractor)
854 );
855 let role = opt!(
856 extractor,
857 attrs.get_typed(FTMLKey::Role, |s| Result::<_, ()>::Ok(
858 s.split(',')
859 .map(|s| s.trim().to_string().into_boxed_str())
860 .collect::<Vec<_>>()
861 .into_boxed_slice()
862 ))
863 )
864 .unwrap_or_default();
865 let assoctype = opt!(
866 extractor,
867 attrs.get_typed(FTMLKey::AssocType, AssocType::from_str)
868 );
869 let arity =
870 opt!(extractor, attrs.get_typed(FTMLKey::Args, ArgSpec::from_str)).unwrap_or_default();
871 let reordering = attrs
872 .get(FTMLKey::ArgumentReordering)
873 .map(|s| Into::<String>::into(s).into_boxed_str());
874 let macroname = attrs
875 .get(FTMLKey::Macroname)
876 .map(|s| Into::<String>::into(s).into_boxed_str());
877 extractor.open_decl();
878 Some(OpenFTMLElement::Symdecl {
879 uri,
880 arity,
881 macroname,
882 role,
883 assoctype,
884 reordering,
885 })
886 }
887
888 pub fn vardecl<E: FTMLExtractor>(
889 extractor: &mut E,
890 attrs: &mut E::Attr<'_>,
891 nexts: &mut SV<E>,
892 ) -> Option<OpenFTMLElement> {
893 do_vardecl(extractor, attrs, nexts, FTMLKey::Vardef, false)
894 }
895 pub fn varseq<E: FTMLExtractor>(
896 extractor: &mut E,
897 attrs: &mut E::Attr<'_>,
898 nexts: &mut SV<E>,
899 ) -> Option<OpenFTMLElement> {
900 do_vardecl(extractor, attrs, nexts, FTMLKey::Varseq, true)
901 }
902
903 pub fn do_vardecl<E: FTMLExtractor>(
904 extractor: &mut E,
905 attrs: &mut E::Attr<'_>,
906 _nexts: &mut SV<E>,
907 tag: FTMLKey,
908 is_seq: bool,
909 ) -> Option<OpenFTMLElement> {
910 let Some(name) = attrs.get(tag).and_then(|v| Name::from_str(v.as_ref()).ok()) else {
911 extractor.add_error(FTMLError::InvalidKeyFor(tag.as_str(), None));
912 return None;
913 };
914 let role = opt!(
915 extractor,
916 attrs.get_typed(FTMLKey::Role, |s| Result::<_, ()>::Ok(
917 s.split(',')
918 .map(|s| s.trim().to_string().into_boxed_str())
919 .collect::<Vec<_>>()
920 .into_boxed_slice()
921 ))
922 )
923 .unwrap_or_default();
924 let assoctype = opt!(
925 extractor,
926 attrs.get_typed(FTMLKey::AssocType, AssocType::from_str)
927 );
928 let arity =
929 opt!(extractor, attrs.get_typed(FTMLKey::Args, ArgSpec::from_str)).unwrap_or_default();
930 let reordering = attrs
931 .get(FTMLKey::ArgumentReordering)
932 .map(|s| Into::<String>::into(s).into_boxed_str());
933 let macroname = attrs
934 .get(FTMLKey::Macroname)
935 .map(|s| Into::<String>::into(s).into_boxed_str());
936 let bind = attrs.get_bool(FTMLKey::Bind);
937 extractor.open_decl();
938 let uri = extractor.get_narrative_uri() & name;
939 Some(OpenFTMLElement::Vardecl {
940 uri,
941 arity,
942 macroname,
943 role,
944 assoctype,
945 reordering,
946 bind,
947 is_seq,
948 })
949 }
950
951 pub fn notation<E: FTMLExtractor>(
952 extractor: &mut E,
953 attrs: &mut E::Attr<'_>,
954 _nexts: &mut SV<E>,
955 ) -> Option<OpenFTMLElement> {
956 let symbol = if let Ok(s) = attrs.get_symbol_uri(FTMLKey::Notation, extractor) {
957 VarOrSym::S(s.into())
958 } else if let Some(v) = attrs.get(FTMLKey::Notation) {
959 let Ok(n) = v.as_ref().parse() else {
960 extractor.add_error(FTMLError::InvalidURI(format!("10: {}", v.as_ref())));
961 return None;
962 };
963 VarOrSym::V(PreVar::Unresolved(n))
964 } else {
965 extractor.add_error(FTMLError::InvalidKeyFor(FTMLKey::Notation.as_str(), None));
966 return None;
967 };
968 let mut fragment = attrs
969 .get(FTMLKey::NotationFragment)
970 .map(|s| Into::<String>::into(s).into_boxed_str());
971 if fragment.as_ref().is_some_and(|s| s.is_empty()) {
972 fragment = None
973 };
974 let id = fragment.as_ref().map_or("notation", |s| &**s).to_string();
975 let id = extractor.new_id(Cow::Owned(id));
976 let prec = if let Some(v) = attrs.get(FTMLKey::Precedence) {
977 if let Ok(v) = isize::from_str(v.as_ref()) {
978 v
979 } else {
980 extractor.add_error(FTMLError::InvalidKeyFor(FTMLKey::Precedence.as_str(), None));
981 return None;
982 }
983 } else {
984 0
985 };
986 let mut argprecs = SmallVec::default();
987 if let Some(s) = attrs.get(FTMLKey::Argprecs) {
988 for s in s.as_ref().split(',') {
989 if s.is_empty() {
990 continue;
991 }
992 if let Ok(v) = isize::from_str(s.trim()) {
993 argprecs.push(v)
994 } else {
995 extractor.add_error(FTMLError::InvalidKeyFor(FTMLKey::Argprecs.as_str(), None));
996 return None;
997 }
998 }
999 }
1000 extractor.open_notation();
1001 Some(OpenFTMLElement::Notation {
1002 id,
1003 symbol,
1004 precedence: prec,
1005 argprecs,
1006 })
1007 }
1008
1009 pub fn notationcomp<E: FTMLExtractor>(
1010 _extractor: &mut E,
1011 attrs: &mut E::Attr<'_>,
1012 nexts: &mut SV<E>,
1013 ) -> Option<OpenFTMLElement> {
1014 attrs.remove(FTMLKey::NotationComp);
1015 attrs.remove(FTMLKey::Term);
1016 attrs.remove(FTMLKey::Head);
1017 attrs.remove(FTMLKey::NotationId);
1018 attrs.remove(FTMLKey::Invisible);
1019 nexts.retain(|r| {
1020 !matches!(
1021 r.tag,
1022 FTMLKey::Term | FTMLKey::Head | FTMLKey::NotationId | FTMLKey::Invisible
1023 )
1024 });
1025 Some(OpenFTMLElement::NotationComp)
1026 }
1027 pub fn notationopcomp<E: FTMLExtractor>(
1028 _extractor: &mut E,
1029 attrs: &mut E::Attr<'_>,
1030 nexts: &mut SV<E>,
1031 ) -> Option<OpenFTMLElement> {
1032 attrs.remove(FTMLKey::NotationComp);
1033 attrs.remove(FTMLKey::Term);
1034 attrs.remove(FTMLKey::Head);
1035 attrs.remove(FTMLKey::NotationId);
1036 attrs.remove(FTMLKey::Invisible);
1037 nexts.retain(|r| {
1038 !matches!(
1039 r.tag,
1040 FTMLKey::Term | FTMLKey::Head | FTMLKey::NotationId | FTMLKey::Invisible
1041 )
1042 });
1043 Some(OpenFTMLElement::NotationOpComp)
1044 }
1045
1046 pub fn definiendum<E: FTMLExtractor>(
1047 extractor: &mut E,
1048 attrs: &mut E::Attr<'_>,
1049 _nexts: &mut SV<E>,
1050 ) -> Option<OpenFTMLElement> {
1051 let uri = err!(
1052 extractor,
1053 attrs.get_symbol_uri(FTMLKey::Definiendum, extractor)
1054 );
1055 extractor.add_definiendum(uri.clone());
1056 Some(OpenFTMLElement::Definiendum(uri))
1057 }
1058
1059 pub fn r#type<E: FTMLExtractor>(
1060 extractor: &mut E,
1061 _attrs: &mut E::Attr<'_>,
1062 _nexts: &mut SV<E>,
1063 ) -> Option<OpenFTMLElement> {
1064 if extractor.in_term() {
1065 extractor.add_error(FTMLError::InvalidKey);
1066 return None;
1067 }
1068 extractor.set_in_term(true);
1069 Some(OpenFTMLElement::Type)
1070 }
1071
1072 pub fn conclusion<E: FTMLExtractor>(
1073 extractor: &mut E,
1074 attrs: &mut E::Attr<'_>,
1075 _nexts: &mut SV<E>,
1076 ) -> Option<OpenFTMLElement> {
1077 let uri = err!(
1078 extractor,
1079 attrs.get_symbol_uri(FTMLKey::Conclusion, extractor)
1080 );
1081 let in_term = extractor.in_term();
1082 extractor.set_in_term(true);
1083 Some(OpenFTMLElement::Conclusion { uri, in_term })
1084 }
1085
1086 pub fn definiens<E: FTMLExtractor>(
1087 extractor: &mut E,
1088 attrs: &mut E::Attr<'_>,
1089 _nexts: &mut SV<E>,
1090 ) -> Option<OpenFTMLElement> {
1091 let uri = opt!(
1092 extractor,
1093 attrs.get_symbol_uri(FTMLKey::Definiens, extractor)
1094 );
1095 let in_term = extractor.in_term();
1096 extractor.set_in_term(true);
1097 Some(OpenFTMLElement::Definiens { uri, in_term })
1098 }
1099
1100 pub fn mmtrule<E: FTMLExtractor>(
1101 extractor: &mut E,
1102 attrs: &mut E::Attr<'_>,
1103 _nexts: &mut SV<E>,
1104 ) -> Option<OpenFTMLElement> {
1105 let id = attrs
1106 .get(FTMLKey::Rule)
1107 .unwrap_or_else(|| unreachable!())
1108 .as_ref()
1109 .to_string()
1110 .into_boxed_str();
1111 extractor.open_args();
1112 Some(OpenFTMLElement::MMTRule(id))
1113 }
1114
1115 pub fn argsep<E: FTMLExtractor>(
1116 _extractor: &mut E,
1117 attrs: &mut E::Attr<'_>,
1118 nexts: &mut SV<E>,
1119 ) -> Option<OpenFTMLElement> {
1120 attrs.remove(FTMLKey::Term);
1121 attrs.remove(FTMLKey::ArgSep);
1122 attrs.remove(FTMLKey::Head);
1123 attrs.remove(FTMLKey::NotationId);
1124 attrs.remove(FTMLKey::Invisible);
1125 nexts.retain(|r| {
1126 !matches!(
1127 r.tag,
1128 FTMLKey::Term
1129 | FTMLKey::Head
1130 | FTMLKey::NotationId
1131 | FTMLKey::Invisible
1132 | FTMLKey::ArgSep
1133 )
1134 });
1135 Some(OpenFTMLElement::ArgSep)
1136 }
1137
1138 pub fn argmap<E: FTMLExtractor>(
1139 _extractor: &mut E,
1140 attrs: &mut E::Attr<'_>,
1141 nexts: &mut SV<E>,
1142 ) -> Option<OpenFTMLElement> {
1143 attrs.remove(FTMLKey::Term);
1144 attrs.remove(FTMLKey::Head);
1145 attrs.remove(FTMLKey::ArgMap);
1146 attrs.remove(FTMLKey::NotationId);
1147 attrs.remove(FTMLKey::Invisible);
1148 nexts.retain(|r| {
1149 !matches!(
1150 r.tag,
1151 FTMLKey::Term
1152 | FTMLKey::Head
1153 | FTMLKey::NotationId
1154 | FTMLKey::Invisible
1155 | FTMLKey::ArgMap
1156 )
1157 });
1158 Some(OpenFTMLElement::ArgMap)
1159 }
1160
1161 pub fn argmapsep<E: FTMLExtractor>(
1162 _extractor: &mut E,
1163 attrs: &mut E::Attr<'_>,
1164 nexts: &mut SV<E>,
1165 ) -> Option<OpenFTMLElement> {
1166 attrs.remove(FTMLKey::Term);
1167 attrs.remove(FTMLKey::Head);
1168 attrs.remove(FTMLKey::ArgMapSep);
1169 attrs.remove(FTMLKey::NotationId);
1170 attrs.remove(FTMLKey::Invisible);
1171 nexts.retain(|r| {
1172 !matches!(
1173 r.tag,
1174 FTMLKey::Term
1175 | FTMLKey::Head
1176 | FTMLKey::NotationId
1177 | FTMLKey::Invisible
1178 | FTMLKey::ArgMapSep
1179 )
1180 });
1181 Some(OpenFTMLElement::ArgMapSep)
1182 }
1183
1184 pub fn term<E: FTMLExtractor>(
1185 extractor: &mut E,
1186 attrs: &mut E::Attr<'_>,
1187 _nexts: &mut SV<E>,
1188 ) -> Option<OpenFTMLElement> {
1189 if extractor.in_notation() {
1190 return None;
1191 }
1192 let notation = attrs.value(FTMLKey::NotationId.attr_name()).and_then(|n| {
1193 let asr = n.as_ref().trim();
1194 if asr.is_empty() {
1195 return None;
1196 }
1197 Some(asr.parse::<Name>().unwrap_or_else(|_| {
1198 extractor.add_error(FTMLError::InvalidURI(format!("12: {}", n.as_ref())));
1199 ERROR.clone()
1200 }))
1201 });
1202 let head = match attrs.value(FTMLKey::Head.attr_name()) {
1203 None => {
1204 extractor.add_error(FTMLError::MissingHeadForTerm);
1205 VarOrSym::V(PreVar::Unresolved(ERROR.clone()))
1206 }
1207 Some(v) => {
1208 let v = v.as_ref();
1209 v.parse::<SymbolURI>().ok().map_or_else(
1210 || {
1211 v.parse::<ModuleURI>().map_or_else(
1212 |_| {
1213 DocumentElementURI::from_str(v).map_or_else(
1214 |_| {
1215 if v.contains('?') {
1216 tracing::warn!(
1217 "Suspicious variable name containing '?': {v}"
1218 );
1219 }
1220 v.parse().ok().map_or_else(
1221 || {
1222 extractor.add_error(FTMLError::InvalidURI(
1223 format!("13: {v}"),
1224 ));
1225 None
1226 },
1227 |v| Some(VarOrSym::V(PreVar::Unresolved(v))),
1228 )
1229 },
1230 |d| Some(VarOrSym::V(PreVar::Resolved(d))),
1231 )
1232 },
1233 |m| Some(VarOrSym::S(m.into())),
1234 )
1235 },
1236 |s| Some(VarOrSym::S(s.into())),
1237 )?
1238 }
1239 };
1240 let kind = attrs
1242 .value(FTMLKey::Term.attr_name())
1243 .unwrap_or_else(|| unreachable!());
1244 let kind: OpenTermKind = kind.as_ref().parse().unwrap_or_else(|()| {
1245 extractor.add_error(FTMLError::InvalidTermKind(kind.into()));
1246 OpenTermKind::OMA
1247 });
1248 let term = match (kind, head) {
1249 (OpenTermKind::OMID | OpenTermKind::OMV, VarOrSym::S(uri)) => {
1250 OpenTerm::Symref { uri, notation }
1251 }
1252 (OpenTermKind::OMID | OpenTermKind::OMV, VarOrSym::V(name)) => {
1253 OpenTerm::Varref { name, notation }
1254 }
1255 (OpenTermKind::OML, VarOrSym::V(PreVar::Unresolved(name))) => {
1256 extractor.open_decl();
1257 OpenTerm::OML { name } }
1259 (OpenTermKind::OMA, head) => {
1260 extractor.open_args();
1261 OpenTerm::OMA { head, notation } }
1263 (OpenTermKind::Complex, head) => {
1264 extractor.open_complex_term();
1265 OpenTerm::Complex(head)
1266 }
1267 (k, head) => {
1268 extractor.add_error(FTMLError::InvalidHeadForTermKind(k, head.clone()));
1269 extractor.open_args();
1270 OpenTerm::OMA { head, notation } }
1272 };
1273 let is_top = if extractor.in_term() {
1274 false
1275 } else {
1276 extractor.set_in_term(true);
1277 true
1278 };
1279 Some(OpenFTMLElement::OpenTerm { term, is_top })
1280 }
1281
1282 pub fn arg<E: FTMLExtractor>(
1283 extractor: &mut E,
1284 attrs: &mut E::Attr<'_>,
1285 _nexts: &mut SV<E>,
1286 ) -> Option<OpenFTMLElement> {
1287 let Some(value) = attrs.value(FTMLKey::Arg.attr_name()) else {
1288 extractor.add_error(FTMLError::InvalidArgSpec);
1289 return None;
1290 };
1291 let arg = OpenArg::from_strs(value, attrs.value(FTMLKey::ArgMode.attr_name()));
1292 let Some(arg) = arg else {
1293 extractor.add_error(FTMLError::InvalidArgSpec);
1294 return None;
1295 };
1296 Some(OpenFTMLElement::Arg(arg))
1297 }
1298
1299 pub fn headterm<E: FTMLExtractor>(
1300 _extractor: &mut E,
1301 _attrs: &mut E::Attr<'_>,
1302 _nexts: &mut SV<E>,
1303 ) -> Option<OpenFTMLElement> {
1304 Some(OpenFTMLElement::HeadTerm)
1305 }
1306
1307 pub fn inputref<E: FTMLExtractor>(
1308 extractor: &mut E,
1309 attrs: &mut E::Attr<'_>,
1310 _nexts: &mut SV<E>,
1311 ) -> Option<OpenFTMLElement> {
1312 let uri = err!(
1313 extractor,
1314 attrs.get_document_uri(FTMLKey::InputRef, extractor)
1315 );
1316 let id = attrs.get_id(extractor, Cow::Owned(uri.name().last_name().to_string()));
1317 Some(OpenFTMLElement::Inputref { uri, id })
1318 }
1319
1320 pub fn ifinputref<E: FTMLExtractor>(
1321 _extractor: &mut E,
1322 attrs: &mut E::Attr<'_>,
1323 _nexts: &mut SV<E>,
1324 ) -> Option<OpenFTMLElement> {
1325 let value = attrs.get_bool(FTMLKey::IfInputref);
1326 Some(OpenFTMLElement::IfInputref(value))
1327 }
1328
1329 pub fn comp<E: FTMLExtractor>(
1330 _extractor: &mut E,
1331 _attrs: &mut E::Attr<'_>,
1332 _nexts: &mut SV<E>,
1333 ) -> Option<OpenFTMLElement> {
1334 Some(OpenFTMLElement::Comp)
1335 }
1336
1337 pub fn maincomp<E: FTMLExtractor>(
1338 _extractor: &mut E,
1339 _attrs: &mut E::Attr<'_>,
1340 _nexts: &mut SV<E>,
1341 ) -> Option<OpenFTMLElement> {
1342 Some(OpenFTMLElement::MainComp)
1343 }
1344
1345 pub fn defcomp<E: FTMLExtractor>(
1346 _extractor: &mut E,
1347 _attrs: &mut E::Attr<'_>,
1348 _nexts: &mut SV<E>,
1349 ) -> Option<OpenFTMLElement> {
1350 Some(OpenFTMLElement::DefComp)
1351 }
1352
1353 }