1use std::{
2 cell::RefCell,
3 fs::File,
4 io::{BufRead, BufReader},
5};
6
7use libxml::tree::Node;
8use once_cell::sync::Lazy;
9use regex::Regex;
10use rustc_hash::FxHashSet as HashSet;
11
12use super::arena::SymHashMap;
13use crate::{
14 common::{
15 arena::{self, SymStr},
16 error::*,
17 relaxng::Relaxng,
18 xml::XML_NS,
19 },
20 document::Document,
21 pin,
22 util::pathname,
23};
24
25pub const LTX_NAMESPACE: &str = "http://dlmf.nist.gov/LaTeXML";
28pub type IndirectModel = SymHashMap<SymHashMap<SymStr>>;
29
30static PREFIXED_LOCALNAME_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([^:]+):(.+)$").unwrap());
31static TAG_MODEL_LINE_RE: Lazy<Regex> =
32 Lazy::new(|| Regex::new(r"^([^\{]+)\{(.*?)\}\((.*?)\)$").unwrap());
33static CLASS_MODEL_LINE_RE: Lazy<Regex> =
37 Lazy::new(|| Regex::new(r"^([^:=]+):=\(?([^)]*?)\)?$").unwrap());
38static NAMESPACE_MODEL_LINE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([^=]+)=(.*?)$").unwrap());
39
40#[derive(Default, Debug)]
41pub struct TagFrame {
42 model: HashSet<SymStr>,
43 attributes: HashSet<SymStr>,
44}
45
46static DEFAULT_TAG_FRAME: Lazy<TagFrame> = Lazy::new(TagFrame::default);
47
48#[derive(Default, Debug)]
49pub struct Model {
50 pub schema: Option<Relaxng>,
51 pub schema_data: Option<Vec<SymStr>>,
52 pub schema_class: SymHashMap<HashSet<SymStr>>,
53 pub code_namespace_prefixes: SymHashMap<SymStr>,
54 pub code_namespaces: SymHashMap<SymStr>,
55 pub document_namespace_prefixes: SymHashMap<SymStr>,
56 pub document_namespaces: SymHashMap<SymStr>,
57 pub permissive: bool,
60 pub no_compiled: bool,
61 pub debug_mode: bool,
62 pub namespace_errors: u8,
63 pub tagprop: SymHashMap<TagFrame>,
64}
65
66#[thread_local]
67pub static MODEL: Lazy<RefCell<Model>> = Lazy::new(|| RefCell::new(Model::new()));
68
69macro_rules! model {
70 () => {
71 (*MODEL).borrow()
72 };
73}
74macro_rules! model_mut {
75 () => {
76 (*MODEL).borrow_mut()
77 };
78}
79
80pub fn initialize_model() {
81 let mut global_model = MODEL.borrow_mut();
82 *global_model = Model::new();
83}
84
85pub(crate) fn force_init() { Lazy::force(&MODEL); }
95
96impl Model {
97 pub fn new() -> Self {
98 let mut model = Model::default();
99 model.register_namespace("xml", Some(XML_NS));
101 model.register_document_namespace("xml", Some(XML_NS));
102 model
103 }
104 pub fn register_namespace(&mut self, codeprefix: &str, namespace_opt: Option<&str>) {
119 self.register_namespace_sym(arena::pin(codeprefix), namespace_opt.map(arena::pin))
120 }
121 pub fn register_namespace_sym(&mut self, codeprefix: SymStr, namespace_opt: Option<SymStr>) {
122 let namespace_opt_checked = namespace_opt.filter(|val| *val != pin!(""));
124 match namespace_opt_checked {
125 Some(namespace) => {
126 self
127 .code_namespace_prefixes
128 .insert_sym(namespace, codeprefix);
129 self.code_namespaces.insert_sym(codeprefix, namespace);
130 },
131 None => {
132 if let Some(prev) = self.code_namespaces.get_sym(codeprefix) {
133 self.code_namespace_prefixes.remove_sym(*prev);
134 };
135 self.code_namespaces.remove_sym(codeprefix);
136 },
137 };
138 }
139
140 pub fn register_document_namespace(&mut self, docprefix: &str, namespace_opt: Option<&str>) {
146 let default_sym = pin!("#default");
147 let docprefix_sym = if docprefix.is_empty() {
148 default_sym
149 } else {
150 arena::pin(docprefix)
151 };
152
153 match namespace_opt {
154 Some(namespace) => {
155 let ns_sym = arena::pin(namespace);
158 let regnamespace = if docprefix_sym == default_sym {
159 arena::pin(s!("DEFAULT#{namespace}"))
160 } else {
161 ns_sym
162 };
163 self
164 .document_namespace_prefixes
165 .insert_sym(regnamespace, docprefix_sym);
166 self.document_namespaces.insert_sym(docprefix_sym, ns_sym);
167 },
168 None => {
169 if let Some(prev) = self.document_namespaces.get_sym(docprefix_sym) {
170 self.document_namespace_prefixes.remove_sym(*prev);
171 };
172 self.document_namespaces.remove_sym(docprefix_sym);
173 },
174 };
175 }
176
177 pub fn set_relaxng_schema(&mut self, schema: &str) {
178 self.schema_data = Some(vec![pin!("RelaxNG"), arena::pin(schema)]);
179 }
180 pub fn load_compiled_schema(&mut self, path: &str) {
184 let compiled_fh = File::open(path).unwrap();
185 let compiled_reader = BufReader::new(&compiled_fh);
186 let content: String = compiled_reader
187 .lines()
188 .map_while(std::result::Result::ok)
189 .collect::<Vec<_>>()
190 .join("\n");
191 self.load_compiled_schema_str(&content, path);
192 }
193
194 pub fn load_compiled_schema_str(&mut self, content: &str, source: &str) {
200 note_begin(&s!("Loading compiled schema {}\n", source));
201 for line in content.lines() {
202 if let Some(caps) = TAG_MODEL_LINE_RE.captures(line) {
203 let tag = caps.get(1).map_or("", |m| m.as_str());
204 let attr = caps.get(2).map_or("", |m| m.as_str());
205 let children = caps.get(3).map_or("", |m| m.as_str());
206 self.add_tag_attribute(tag, attr.split(',').collect());
207 self.add_tag_content(tag, children.split(',').collect());
208 } else if let Some(caps) = CLASS_MODEL_LINE_RE.captures(line) {
209 let classname = caps.get(1).map_or("", |m| m.as_str());
210 let elements = caps.get(2).map_or("", |m| m.as_str());
211 let mut class_set = HashSet::default();
212 for set_element in elements.split(',') {
213 class_set.insert(arena::pin(set_element));
214 }
215 self.set_schema_class(classname, class_set);
216 } else if let Some(caps) = NAMESPACE_MODEL_LINE_RE.captures(line) {
217 let prefix = caps.get(1).map_or("", |m| m.as_str());
218 let namespace = caps.get(2).map_or("", |m| m.as_str());
219 self.register_document_namespace(prefix, Some(namespace));
220 } else {
221 panic!("Fatal:internal:{source} Compiled model '{source}' is malformatted at \"{line}\"");
222 }
223 }
224 note_end(&s!("Loading compiled schema {source}\n"));
225 }
226 pub fn add_tag_content(&mut self, tag: &str, elements: Vec<&str>) {
227 let frame = self.tagprop.entry(tag).or_default();
228
229 for element in elements {
230 frame.model.insert(arena::pin(element));
231 }
232 }
233
234 pub fn add_tag_attribute(&mut self, tag: &str, attributes: Vec<&str>) {
246 let frame = self.tagprop.entry(tag).or_default();
247
248 for attribute in attributes {
249 frame.attributes.insert(arena::pin(attribute));
250 }
251 }
252
253 pub fn set_schema_class(&mut self, classname: &str, content: HashSet<SymStr>) {
254 self.schema_class.insert(classname, content);
255 }
256
257 pub fn dump_compiled_schema(&self) -> String {
272 fn sym_to_string(sym: SymStr) -> String { arena::with(sym, |s| s.to_string()) }
273 fn syms_sorted(set: impl IntoIterator<Item = SymStr>) -> Vec<String> {
274 let mut v: Vec<String> = set.into_iter().map(sym_to_string).collect();
275 v.sort();
276 v
277 }
278 let mut out = String::new();
279 let prefixes = syms_sorted(self.document_namespaces.keys().copied());
280 for prefix in &prefixes {
281 let ns_opt = self
282 .document_namespaces
283 .get_sym(arena::pin(prefix.as_str()));
284 let ns = match ns_opt {
285 Some(v) => sym_to_string(*v),
286 None => continue,
287 };
288 out.push_str(prefix);
289 out.push('=');
290 out.push_str(&ns);
291 out.push('\n');
292 }
293 let classnames = syms_sorted(self.schema_class.keys().copied());
294 for classname in &classnames {
295 let elements = match self.schema_class.get_sym(arena::pin(classname.as_str())) {
296 Some(set) => set,
297 None => continue,
298 };
299 let elt_names = syms_sorted(elements.iter().copied());
300 out.push_str(classname);
301 out.push_str(":=(");
302 out.push_str(&elt_names.join(","));
303 out.push_str(")\n");
304 }
305 let tags = syms_sorted(self.tagprop.keys().copied());
306 for tag in &tags {
307 if tag.starts_with('!') {
308 continue;
309 }
310 let frame = match self.tagprop.get_sym(arena::pin(tag.as_str())) {
311 Some(f) => f,
312 None => continue,
313 };
314 let attrs = syms_sorted(frame.attributes.iter().copied());
315 let children = syms_sorted(frame.model.iter().copied());
316 out.push_str(tag);
317 out.push('{');
318 out.push_str(&attrs.join(","));
319 out.push_str("}(");
320 out.push_str(&children.join(","));
321 out.push_str(")\n");
322 }
323 out
324 }
325 pub fn describe_model(&self) {}
326 fn load_internal_extensions(&mut self) {
327 if !self.tagprop.contains_key("ltx:_CaptureBlock_") {
328 self.synthesize_element("ltx:_CaptureBlock_", &[
335 "ltx:block",
336 "ltx:logical-block",
337 "ltx:sectional-block",
338 "Caption",
339 "FrontMatter",
340 "BackMatter",
341 ]);
342 let cb_entry = self.tagprop.entry("ltx:_CaptureBlock_").or_default();
343 cb_entry.model.insert(pin!("svg:g"));
344 cb_entry.model.insert(pin!("svg:foreignObject"));
345 }
346 }
347
348 fn synthesize_element(&mut self, tag: &str, others: &[&str]) {
350 let mut to_add_in_model = Vec::new();
351 let mut to_add_in_attrs = Vec::new();
352 for other in others {
353 if let Some(content) = self.schema_class.get(other) {
354 for child in content {
355 to_add_in_model.push(*child);
356 }
357 } else if let Some(entry) = self.tagprop.get(other) {
358 for child in &entry.model {
359 to_add_in_model.push(*child);
360 }
361 for attr in &entry.attributes {
362 to_add_in_attrs.push(*attr);
363 }
364 }
365 }
366 let capture = self.tagprop.entry(tag).or_default();
367 for child in to_add_in_model {
368 capture.model.insert(child);
369 }
370 for attr in to_add_in_attrs {
371 capture.attributes.insert(attr);
372 }
373 }
374}
375
376pub fn set_relaxng_schema(schema: &str) { model_mut!().set_relaxng_schema(schema) }
377pub fn add_schema_declaration(document: &mut Document) {
378 if let Some(ref schema) = model!().schema {
379 schema.add_schema_declaration(document);
380 }
381}
382
383pub fn load_schema(search_paths: &[&str]) -> Result<()> {
384 let mut model = model_mut!();
386 if model.schema.is_some() {
387 return Ok(());
388 }
389 let mut name = String::new();
390 if model.schema_data.is_none() {
391 Warn!("expected", "<model>", "TODO");
393 model.register_document_namespace("ltx", Some(LTX_NAMESPACE));
397 model.set_relaxng_schema("LaTeXML");
398 model.register_namespace("ltx", Some(LTX_NAMESPACE));
399 model.register_namespace("svg", Some("http://www.w3.org/2000/svg"));
400 model.register_namespace("xlink", Some("http://www.w3.org/1999/xlink")); model.register_namespace("m", Some("http://www.w3.org/1998/Math/MathML"));
402 model.register_namespace("xhtml", Some("http://www.w3.org/1999/xhtml"));
403 model.permissive = true;
404 } if let Some(ref data) = model.schema_data {
407 if data[0] == pin!("RelaxNG") {
408 name = arena::to_string(data[1]);
409 model.schema = Some(Relaxng {
410 name: name.clone(),
411 ..Relaxng::default()
412 });
413 } else {
414 let message = arena::with(data[0], |schema_type_str| {
415 s!("Can't load a schema of type {schema_type_str:?}")
416 });
417 Error!("unknown", "schematype", message)
418 }
419 }
420
421 if !model.no_compiled && model.schema.is_some() {
422 let paths: Option<Vec<String>> = if search_paths.is_empty() {
423 None
424 } else {
425 Some(search_paths.iter().map(ToString::to_string).collect())
426 };
427 let pathname_opt = pathname::find(&name, pathname::PathnameFindOptions {
428 paths,
429 extensions: Some(vec![s!("model")]),
430 installation_subdir: Some(s!("resources/RelaxNG")),
431 });
432
433 match pathname_opt {
434 Some(compiled_path) => model.load_compiled_schema(&compiled_path),
435 None => {
436 let embed_key = format!("{}.model", name);
442 if let Some(content) = crate::common::relaxng::embedded::lookup(&embed_key) {
443 model.load_compiled_schema_str(content, &format!("<embedded>/{embed_key}"));
444 } else {
445 let paths: Vec<&std::path::Path> =
446 search_paths.iter().map(std::path::Path::new).collect();
447 let schema = model.schema.as_mut().unwrap();
448 let schema_name = schema.name.clone();
449 if let Err(err) = schema.load_schema(&schema_name, &paths) {
450 let msg = format!("load_schema failed for {}: {}", schema_name, err);
451 Warn!("expected", "RelaxNG", msg);
452 }
453 }
454 },
455 };
456 }
457 model.load_internal_extensions();
458 if model.debug_mode {
459 model.describe_model()
460 }
461
462 Ok(())
463}
464
465pub fn get_document_namespace_prefix(
466 namespace: &str,
467 forattribute: bool,
468 probe: bool,
469) -> Option<SymStr> {
470 let mut docprefix = if !forattribute {
474 model!()
475 .document_namespace_prefixes
476 .get(&s!("DEFAULT#{namespace}"))
477 .copied()
478 } else {
479 None
480 };
481 let ns_sym = arena::pin(namespace);
482 if docprefix.is_none() {
483 docprefix = model!()
484 .document_namespace_prefixes
485 .get_sym(ns_sym)
486 .copied();
487 }
488
489 if docprefix.is_none() && !probe {
490 {
491 model_mut!().namespace_errors += 1;
492 }
493 let ns_err = s!("namespace{}", &model!().namespace_errors.to_string());
494 docprefix = Some(arena::pin(&ns_err));
495 {
496 model_mut!().register_document_namespace(&ns_err, Some(namespace));
497 }
498 let message2 = if let Some(dp) = docprefix {
499 arena::with(dp, |dp_str| s!("Using '{dp_str}' instead"))
500 } else {
501 String::from("No prefix to fall back on.")
502 };
503 Warn!(
504 "malformed",
505 namespace,
506 "No prefix has been registered for namespace (in document)",
507 message2
508 );
509 }
510 let default_sym = pin!("#default");
511 docprefix.filter(|p| p != &default_sym)
512}
513
514pub fn get_document_namespace(docprefix: &str, probe: bool) -> Option<String> {
515 let h_default_sym = pin!("#default");
516 let docprefix_sym = if docprefix.is_empty() {
517 h_default_sym
518 } else {
519 arena::pin(docprefix)
520 };
521 let ns_str = match model!().document_namespaces.get_sym(docprefix_sym) {
522 None => String::new(),
523 Some(sym) => arena::with(*sym, |s| {
524 if s.starts_with("DEFAULT#") {
525 s.replacen("DEFAULT#", "", 1)
526 } else {
527 s.to_string()
528 }
529 }),
530 };
531
532 if docprefix_sym != h_default_sym && ns_str.is_empty() && !probe {
533 {
534 model_mut!().namespace_errors += 1;
535 }
536 let ns_error = s!(
537 "http://example.com/namespace{}",
538 &model!().namespace_errors.to_string()
539 );
540 {
541 model_mut!().register_document_namespace(docprefix, Some(&ns_error));
542 }
543 let msg1 = arena::with(docprefix_sym, |dp_str| {
544 s!("No namespace has been registered for prefix '{dp_str}' (in document)")
545 });
546 let msg2 = s!("Using '{ns_str}' instead");
547 let err = || {
548 Error!("malformed", docprefix, msg1, msg2);
549 Ok(())
550 };
551 err().ok();
552 }
553 if ns_str.is_empty() {
554 None
555 } else {
556 Some(ns_str)
557 }
558}
559
560pub fn get_namespace_prefix(namespace: &str, _forattribute: bool, probe: bool) -> Option<SymStr> {
568 let mut codeprefix: Option<SymStr> = None;
569 let ns_sym = arena::pin(namespace);
570 let mut model = model_mut!();
571 if !namespace.is_empty() {
572 codeprefix = model.code_namespace_prefixes.get_sym(ns_sym).copied();
573
574 if codeprefix.is_some() && !probe {
575 {
576 let docprefix = model.document_namespace_prefixes.get_sym(ns_sym);
577 if docprefix.is_some() && !model.code_namespaces.contains_key_sym(docprefix.unwrap()) {
579 codeprefix = docprefix.copied();
580 }
581 }
582 } else {
583 model.namespace_errors += 1;
585 let auto_prefix = arena::pin(s!("namespace{}", &model.namespace_errors.to_string()));
586 codeprefix = Some(auto_prefix);
587 }
588 model.register_namespace_sym(codeprefix.unwrap(), Some(arena::pin(namespace)));
589 }
593
594 codeprefix
595}
596
597pub fn get_namespace(codeprefix: &str, probe: bool) -> Result<Option<SymStr>> {
598 let mut model = model_mut!();
599 let mut ns: Option<SymStr> = model.code_namespaces.get(codeprefix).copied();
600 if ns.is_none() && !probe {
601 model.namespace_errors += 1;
602 let example_namespace = s!(
603 "http://example.com/namespace{}",
604 &model.namespace_errors.to_string()
605 );
606 ns = Some(arena::pin(&example_namespace));
607 model.register_namespace(codeprefix, Some(&example_namespace));
608 Error!(
609 "malformed",
610 codeprefix,
611 s!("No namespace has been registered for prefix '{codeprefix}' (in code)"),
612 s!("Using '{example_namespace}' instead")
613 );
614 }
615 Ok(ns)
616}
617
618#[inline]
625fn prefixed_qname(prefix: &str, local: &str) -> String {
626 let mut q = String::with_capacity(prefix.len() + 1 + local.len());
627 q.push_str(prefix);
628 q.push(':');
629 q.push_str(local);
630 q
631}
632
633pub fn get_node_qname(node: &Node) -> SymStr {
634 use libxml::tree::NodeType::*;
635 let node_type = node.get_type();
636 if node_type.is_none() {
637 return pin!("#BrokenNode");
638 }
639 match node_type.unwrap() {
642 TextNode => pin!("#PCDATA"),
643 DocumentNode => pin!("#Document"),
644 CommentNode => pin!("#Comment"),
645 PiNode => pin!("#ProcessingInstruction"),
646 DTDNode => pin!("#DTD"),
647 NamespaceDecl => {
648 pin!("xmlns")
656 },
657 ElementNode | AttributeNode => {
658 let name_str = node.get_name();
659 if let Some(ns) = node.get_namespace() {
662 let prefix = ns.get_prefix();
663 if prefix.is_empty() {
664 arena::pin(prefixed_qname("ltx", &name_str))
673 } else {
674 arena::pin(prefixed_qname(&prefix, &name_str))
676 }
677 } else {
678 match name_str.as_str() {
680 "song" | "verse" => arena::pin(name_str),
681 regular => arena::pin(prefixed_qname("ltx", regular)),
682 }
683 }
684 },
685 _ => {
687 pin!("#BrokenNode")
694 },
695 }
696}
697
698pub fn get_foreign_node_qname(node: &Node) -> SymStr {
718 if let Some(ns) = node.get_namespace()
719 && ns.get_prefix().is_empty()
720 {
721 let href = ns.get_href();
722 if !href.is_empty() && href != LTX_NAMESPACE {
723 let registered = model!()
726 .code_namespace_prefixes
727 .get_sym(arena::pin(href.as_str()))
728 .copied();
729 if let Some(code_prefix) = registered {
732 let name_str = node.get_name();
733 return arena::pin(arena::with(code_prefix, |p| prefixed_qname(p, &name_str)));
734 }
735 }
736 }
737 get_node_qname(node)
738}
739
740pub fn with_node_qname<R, FnR>(node: &Node, caller: FnR) -> R
747where FnR: FnOnce(&str) -> R {
748 let qsym = get_node_qname(node);
749 arena::with(qsym, |qname_str| caller(qname_str))
750}
751
752pub fn get_node_document_qname(node: &Node) -> SymStr {
754 use libxml::tree::NodeType::*;
755 let node_type = node.get_type();
756 if node_type.is_none() {
757 return pin!("#BrokenNode");
758 }
759
760 match node_type.unwrap() {
761 TextNode => pin!("#PCDATA"),
762 DocumentNode => pin!("#Document"),
763 CommentNode => pin!("#Comment"),
764 PiNode => pin!("#ProcessingInstruction"),
765 DTDNode => pin!("#DTD"),
766
767 NamespaceDecl => pin!("xmlns"),
773
774 ElementNode | AttributeNode => {
775 let empty_sym = pin!("");
776 let mut prefix = empty_sym;
777 if let Some(ns) = node.get_namespace() {
778 let href = ns.get_href();
779 if !href.is_empty() {
780 prefix = get_document_namespace_prefix(&href, false, true).unwrap_or(empty_sym);
781 }
782 }
783 if prefix == empty_sym {
784 arena::pin(node.get_name())
785 } else {
786 arena::pin(arena::with(prefix, |prefix_str| {
787 s!("{}:{}", prefix_str, node.get_name())
788 }))
789 }
790 },
791 t => {
793 panic!("Fatal:misdefined:<caller> should not ask for qualified name for node of type {t:?}")
794 },
795 }
796}
797
798pub fn get_node_attribute(node: &Node, key: &str) -> Option<String> {
808 if let Some(ns_uri) = attribute_namespace(key) {
809 let local = key.split_once(':').map_or(key, |(_, l)| l);
810 if let Some(value) = node.get_attribute_ns(local, &ns_uri) {
811 return Some(value);
812 }
813 }
814 node.get_attribute(key)
815}
816
817pub fn remove_node_attribute(node: &mut Node, key: &str) -> std::result::Result<(), String> {
820 if let Some(ns_uri) = attribute_namespace(key) {
821 let local = key.split_once(':').map_or(key, |(_, l)| l).to_string();
822 if node.get_attribute_ns(&local, &ns_uri).is_some() {
823 return node
824 .remove_attribute_ns(&local, &ns_uri)
825 .map_err(|e| e.to_string());
826 }
827 }
828 node.remove_attribute(key).map_err(|e| e.to_string())
829}
830
831fn attribute_namespace(key: &str) -> Option<String> {
835 let (prefix, _) = key.split_once(':')?;
836 if prefix == "xml" {
837 return Some(XML_NS.to_string());
838 }
839 match decode_qname(key) {
840 Ok((ns_uri, _local)) => ns_uri,
841 Err(_) => None,
842 }
843}
844
845pub fn decode_qname(codetag: &str) -> Result<(Option<String>, String)> {
849 match PREFIXED_LOCALNAME_RE.captures(codetag) {
850 Some(captures) => {
851 let prefix = captures.get(1).map_or("", |m| m.as_str());
852 let localname = captures.get(2).map_or("", |m| m.as_str());
853
854 if prefix == "xml" {
855 Ok((None, codetag.to_string()))
856 } else {
857 Ok((
858 get_namespace(prefix, false)?.map(arena::to_string),
859 localname.to_string(),
860 ))
861 }
862 },
863 None => Ok((None, codetag.to_string())),
864 }
865}
866
867pub fn decode_qname_sym(sym: SymStr) -> Result<(Option<String>, String)> {
870 let codetag = arena::to_string(sym);
871 decode_qname(&codetag)
872}
873
874pub fn can_contain_sym(tag: SymStr, child: SymStr) -> bool {
888 if tag == pin!("#PCDATA") || tag == pin!("#Comment") || tag == pin!("") {
890 return false;
891 } else if tag == pin!("_WildCard_") {
892 return true;
893 };
894 if arena::with(tag, |tag_str| tag_str.ends_with("_Capture_"))
895 || arena::with(child, |child_str| {
896 child_str.ends_with("_Capture_") || child_str.ends_with("_CaptureBlock_")
897 })
898 {
899 return true;
901 }
902
903 if child == pin!("_WildCard_")
904 || child == pin!("#Comment")
905 || child == pin!("#ProcessingInstruction")
906 || child == pin!("#DTD")
907 {
908 return true;
909 }
910
911 let mut model = model_mut!();
912 if model.permissive && tag == pin!("#Document") && child != pin!("#PCDATA") {
913 return true; }
915
916 let model_entry = &mut model.tagprop.entry_sym(tag).or_default().model;
918 model_entry.contains(&pin!("ANY")) || model_entry.contains(&child)
919}
920
921pub fn can_contain(tag: &str, child: &str) -> bool {
923 match tag {
925 "#PCDATA" | "#Comment" | "" => return false,
926 "_WildCard_" => return true,
927 _ => {},
928 };
929 if tag.ends_with("_Capture_") || child.ends_with("_Capture_") || tag.ends_with("_CaptureBlock_") {
930 return true;
932 }
933
934 match child {
935 "_WildCard_" | "#Comment" | "#ProcessingInstruction" | "#DTD" => return true,
936 _ => {},
937 };
938 let state_model = model!();
939 if state_model.permissive && tag == "#Document" && child != "#PCDATA" {
940 return true; }
942
943 let frame = state_model.tagprop.get(tag);
949 let wildcard_frame = if frame.is_none() {
950 namespace_wildcard_tag(tag).and_then(|w| state_model.tagprop.get(&w))
951 } else {
952 None
953 };
954 let content = &frame
955 .or(wildcard_frame)
956 .unwrap_or(&*DEFAULT_TAG_FRAME)
957 .model;
958 content.contains(&pin!("ANY")) || set_allows(content, child)
959}
960
961fn namespace_wildcard_tag(tag: &str) -> Option<String> {
979 tag.split_once(':').map(|(ns, _)| s!("{ns}:*"))
980}
981
982fn set_allows(set: &HashSet<SymStr>, key: &str) -> bool {
991 if set.contains(&arena::pin(key)) {
992 return true;
993 }
994 if set.is_empty() {
995 return false; }
997 if set.contains(&arena::pin(s!("!{key}"))) {
998 return false;
999 }
1000 match key.split_once(':') {
1001 Some((ns, _)) => {
1002 if set.contains(&arena::pin(s!("{ns}:*"))) {
1003 return true;
1004 }
1005 if set.contains(&arena::pin(s!("!{ns}:*"))) {
1006 return false;
1007 }
1008 if set.contains(&pin!("!*:*")) {
1009 return false;
1010 }
1011 set.contains(&pin!("*:*"))
1012 },
1013 None => {
1014 if set.contains(&pin!("!*")) {
1015 return false;
1016 }
1017 set.contains(&pin!("*"))
1018 },
1019 }
1020}
1021
1022pub fn can_have_attribute(tag: SymStr, attrib: SymStr) -> bool {
1023 if let Some(early_choice) = arena::with(tag, |tag_str| match tag_str {
1025 "#PCDATA" | "#Comment" | "#Document" | "#ProcessingInstruction" | "#DTD" => Some(false),
1026 "_WildCard_" => Some(true),
1027 other if other.ends_with("_Capture_") => Some(true),
1028 _ => None,
1029 }) {
1030 return early_choice;
1031 };
1032 if arena::with(attrib, |a| a.starts_with('_')) {
1035 return true;
1036 }
1037 let model = model!();
1038 if model.permissive {
1039 return true;
1040 }
1041
1042 let frame = model.tagprop.get_sym(tag);
1045 let wildcard_frame = if frame.is_none() {
1046 arena::with(tag, namespace_wildcard_tag).and_then(|w| model.tagprop.get(&w))
1047 } else {
1048 None
1049 };
1050 let attributes = &frame
1051 .or(wildcard_frame)
1052 .unwrap_or(&*DEFAULT_TAG_FRAME)
1053 .attributes;
1054 arena::with(attrib, |a| set_allows(attributes, a))
1055}
1056
1057pub fn is_node_in_schema_class(class_name: &str, tag: &Node) -> bool {
1058 let tag = get_node_qname(tag);
1059 is_in_schema_class(arena::pin(class_name), tag)
1060}
1061pub fn is_in_schema_class(class_name: SymStr, tag: SymStr) -> bool {
1062 match model!().schema_class.get_sym(class_name) {
1063 Some(class) => class.contains(&tag),
1064 _ => false,
1065 }
1066}
1067
1068pub fn get_tags() -> Vec<SymStr> { model!().tagprop.keys().copied().collect() }
1073
1074pub fn get_tag_contents(tag: SymStr) -> Vec<SymStr> {
1075 match model!().tagprop.get_sym(tag) {
1076 Some(h) => h.model.iter().copied().collect(),
1077 None => Vec::new(),
1078 }
1079}
1080pub fn set_model(new_model: Model) {
1081 let mut model = model_mut!();
1082 *model = new_model;
1083}
1084pub fn is_permissive() -> bool { model!().permissive }
1085
1086pub fn with_schema_data<FnR, R>(caller: FnR) -> R
1087where FnR: FnOnce(Option<&Vec<SymStr>>) -> R {
1088 caller(model!().schema_data.as_ref())
1089}
1090pub fn set_schema(schema: Relaxng) {
1091 let mut model = model_mut!();
1092 model.schema = Some(schema);
1093}
1094pub fn set_schema_class(classname: &str, content: HashSet<SymStr>) {
1095 model_mut!().set_schema_class(classname, content)
1096}
1097pub fn add_tag_content(tag: &str, elements: Vec<&str>) {
1098 model_mut!().add_tag_content(tag, elements)
1099}
1100pub fn add_tag_attribute(tag: &str, attributes: Vec<&str>) {
1101 model_mut!().add_tag_attribute(tag, attributes)
1102}
1103
1104pub(crate) fn compute_indirect_model_aux(
1105 tag: SymStr,
1106 start_opt: Option<SymStr>,
1107 desirability: usize,
1108 openability: &mut SymHashMap<u32>,
1109 desc: &mut SymHashMap<SymHashMap<usize>>,
1110) {
1111 let start = match start_opt {
1112 Some(s) => s,
1113 None => pin!(""),
1114 };
1115
1116 let tag_contents: Vec<SymStr> = get_tag_contents(tag);
1120
1121 for kid in tag_contents {
1122 let prior = desc.entry_sym(kid).or_default().get_sym(start).copied();
1135 if let Some(prior_d) = prior
1136 && prior_d >= desirability
1137 {
1138 continue;
1139 }
1140
1141 if start != pin!("") {
1142 desc
1143 .entry_sym(kid)
1144 .or_default()
1145 .insert_sym(start, desirability);
1146 }
1147
1148 if kid != pin!("#PCDATA")
1149 && let Some(priority) = openability.get_sym(kid).copied()
1150 {
1151 let inner = if start != pin!("") { start } else { kid };
1152 let next_desirability = desirability * (priority as usize) / 100;
1155 compute_indirect_model_aux(kid, Some(inner), next_desirability, openability, desc);
1156 }
1157 }
1158}
1159pub fn register_document_namespace(docprefix: &str, namespace_opt: Option<&str>) {
1166 model_mut!().register_document_namespace(docprefix, namespace_opt)
1167}
1168
1169pub fn get_document_namespace_prefixes() -> Vec<(String, String)> {
1171 model!()
1172 .document_namespace_prefixes
1173 .iter()
1174 .map(|(ns_sym, prefix_sym)| {
1175 let prefix = arena::with(*prefix_sym, |s| s.to_string());
1176 let ns = arena::with(*ns_sym, |s| s.to_string());
1177 (prefix, ns)
1178 })
1179 .collect()
1180}
1181
1182pub fn register_namespace(codeprefix: &str, namespace_opt: Option<&str>) {
1189 model_mut!().register_namespace(codeprefix, namespace_opt)
1190}
1191
1192pub fn with_code_namespaces<FnR, R>(caller: FnR) -> R
1193where FnR: FnOnce(&SymHashMap<SymStr>) -> R {
1194 caller(&model!().code_namespaces)
1195}
1196
1197#[cfg(test)]
1198mod wildcard_resolution_tests {
1199 use super::*;
1204
1205 fn set_of(keys: &[&str]) -> HashSet<SymStr> { keys.iter().map(|k| arena::pin(*k)).collect() }
1206
1207 #[test]
1208 fn namespace_wildcard_tag_only_applies_to_prefixed_tags() {
1209 assert_eq!(
1210 namespace_wildcard_tag("xhtml:p").as_deref(),
1211 Some("xhtml:*")
1212 );
1213 assert_eq!(namespace_wildcard_tag("ltx:para").as_deref(), Some("ltx:*"));
1214 assert_eq!(namespace_wildcard_tag("para"), None);
1216 assert_eq!(namespace_wildcard_tag("#PCDATA"), None);
1217 }
1218
1219 #[test]
1220 fn exact_membership_wins_and_empty_sets_reject() {
1221 let s = set_of(&["class", "href"]);
1222 assert!(set_allows(&s, "class"));
1223 assert!(!set_allows(&s, "style"));
1224 assert!(!set_allows(&HashSet::default(), "class"));
1225 }
1226
1227 #[test]
1228 fn a_negation_beats_the_wildcard_that_would_otherwise_allow_it() {
1229 let s = set_of(&["!aria:*", "!xml:*", "*:*", "about"]);
1234 assert!(set_allows(&s, "about"), "exact entry allows");
1235 assert!(
1236 set_allows(&s, "data:foo"),
1237 "*:* allows an unexcluded namespace"
1238 );
1239 assert!(
1240 !set_allows(&s, "aria:label"),
1241 "!aria:* excludes its namespace"
1242 );
1243 assert!(!set_allows(&s, "xml:lang"), "!xml:* excludes its namespace");
1244
1245 let s2 = set_of(&["!svg:width", "svg:*"]);
1247 assert!(set_allows(&s2, "svg:height"));
1248 assert!(!set_allows(&s2, "svg:width"));
1249
1250 let s3 = set_of(&["!*:*", "svg:width"]);
1252 assert!(set_allows(&s3, "svg:width"));
1253 assert!(!set_allows(&s3, "svg:height"));
1254 }
1255
1256 #[test]
1257 fn unprefixed_keys_use_the_bare_star_not_the_namespaced_one() {
1258 let namespaced_only = set_of(&["*:*"]);
1260 assert!(!set_allows(&namespaced_only, "class"));
1261 assert!(set_allows(&namespaced_only, "svg:width"));
1262
1263 let html_wildcard = set_of(&["*", "*:*"]);
1266 assert!(set_allows(&html_wildcard, "class"));
1267 assert!(set_allows(&html_wildcard, "xlink:href"));
1268
1269 let denied = set_of(&["!*", "*:*"]);
1271 assert!(!set_allows(&denied, "class"));
1272 assert!(set_allows(&denied, "svg:width"));
1273 }
1274}