ftml_extraction/
rules.rs

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        //tracing::trace!("{s} == {}? => {}",self.attr,s == self.attr);
35        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                    //tracing::trace!("attribute {s} ({:?})",std::thread::current().id());
99                    true
100                } else {
101                    false
102                }
103            })
104            .collect::<SmallVec<_, 4>>();
105        if stripped.is_empty() {
106            //tracing::trace!("no applicable attributes");
107            return None;
108        }
109        //tracing::trace!("Found {:?} applicable attributes",stripped.len());
110        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                //tracing::debug!("found {:?}",rule.tag);
114                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        //tracing::trace!("Found {:?} applicable rules",rules.len());
125        if rules.is_empty() {
126            //tracing::trace!("returning elements");
127            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            //tracing::trace!("calling rule {:?}",rule.tag);
141            if let Some(r) = (rule.call)(extractor, attrs, &mut rules) {
142                //println!("{{{r:?}");
143                ret.push(r);
144            }
145        }
146        //tracing::trace!("returning elements");
147        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    //type Value<'a,E:FTMLExtractor> = <E::Attr<'a> as Attributes>::Value<'a>;
186    #[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(crate) use rules_impl::*;
220
221    //mod rules_impl {
222    //    use flams_ontology::ftml::FTMLKey;
223    //    use std::str::FromStr;
224    //    use crate::{open::OpenFTMLElement, prelude::{Attributes, FTMLExtractor}};
225
226    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(crate) fn todo<E:FTMLExtractor>(_extractor:&mut E,_attrs:&mut E::Attr<'_>,_nexts:&mut SV<E>,tag:FTMLKey) -> Option<OpenFTMLElement> {
235        todo!("Tag {}",tag.as_str())
236    }*/
237
238    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            //narrative: Vec::new(), content: Vec::new()
357        })
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, //content: Vec::new(), narrative:Vec::new()
377        })
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, //content:Vec::new(),narrative:Vec::new()
400        })
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        // TODO Check if in problem!
617        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        // TODO Check if in problem!
627        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.take_bool(FTMLKey::ProblemChoice);
719        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        //attrs.set(tagstrings::HEAD,&head.to_string());
1241        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 } //, tp: None, df: None }
1258            }
1259            (OpenTermKind::OMA, head) => {
1260                extractor.open_args();
1261                OpenTerm::OMA { head, notation } //, args: SmallVec::new() }
1262            }
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 } //, args: SmallVec::new() }
1271            }
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    //}
1354}