1use libxml::tree::Node;
11use rustc_hash::FxHashMap as HashMap;
12
13use crate::{
14 document::{NodeData, PostDocument, element_children_iter},
15 math_processor::{MathConversion, MathProcessor},
16 processor::{ProcessResult, Processor},
17};
18
19const XMATH_MIMETYPE: &str = "application/x-latexml";
20
21pub struct XMath {
25 name: String,
26 is_secondary: bool,
27}
28
29impl Default for XMath {
30 fn default() -> Self { Self::new() }
31}
32
33impl XMath {
34 pub fn new() -> Self {
35 XMath {
36 name: "XMath".to_string(),
37 is_secondary: false,
38 }
39 }
40}
41
42impl Processor for XMath {
43 fn get_name(&self) -> &str { &self.name }
44
45 fn to_process(&self, doc: &PostDocument) -> Vec<Node> {
46 doc.findnodes("//ltx:Math[not(ancestor::ltx:Math)]")
47 }
48
49 fn process(&mut self, doc: PostDocument, _nodes: Vec<Node>) -> ProcessResult { Ok(vec![doc]) }
50}
51
52impl MathProcessor for XMath {
53 fn convert_node(&self, _doc: &PostDocument, xmath: &Node) -> Option<MathConversion> {
54 let id_suffix = self.id_suffix();
55 let xml = if !id_suffix.is_empty() {
56 let children: Vec<NodeData> = element_children_iter(xmath)
58 .map(NodeData::XmlNode)
59 .collect();
60 Some(NodeData::Element {
61 tag: "ltx:XMath".to_string(),
62 attributes: Some(HashMap::from_iter([(
63 "_sourced".to_string(),
64 "1".to_string(),
65 )])),
66 children,
67 })
68 } else {
69 Some(NodeData::XmlNode(xmath.clone()))
71 };
72
73 Some(MathConversion {
74 processor_name: self.name.clone(),
75 mimetype: Some(XMATH_MIMETYPE.to_string()),
76 xml,
77 string: None,
78 src: None,
79 width: None,
80 height: None,
81 depth: None,
82 })
83 }
84
85 fn combine_parallel(
86 &self,
87 _doc: &PostDocument,
88 _xmath: &Node,
89 primary: MathConversion,
90 secondaries: Vec<MathConversion>,
91 ) -> MathConversion {
92 let mut alt_children = Vec::new();
93
94 if let Some(ref xml) = primary.xml {
96 alt_children.push(xml.clone());
97 }
98
99 for secondary in &secondaries {
101 let mimetype = secondary.mimetype.as_deref().unwrap_or("unknown");
102 if mimetype == XMATH_MIMETYPE {
103 if let Some(ref xml) = secondary.xml {
104 alt_children.push(xml.clone());
105 }
106 } else if let Some(ref xml) = secondary.xml {
107 alt_children.push(xml.clone());
109 }
110 }
111
112 MathConversion {
113 processor_name: self.name.clone(),
114 mimetype: Some(XMATH_MIMETYPE.to_string()),
115 xml: Some(NodeData::Element {
116 tag: "_Fragment_".to_string(),
117 attributes: None,
118 children: alt_children,
119 }),
120 string: None,
121 src: None,
122 width: None,
123 height: None,
124 depth: None,
125 }
126 }
127
128 fn raw_id_suffix(&self) -> &str { ".xm" }
129
130 fn is_secondary(&self) -> bool { self.is_secondary }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn xmath_new_has_default_name() {
139 let x = XMath::new();
140 assert_eq!(x.get_name(), "XMath");
141 assert!(!x.is_secondary);
142 }
143
144 #[test]
145 fn xmath_default_matches_new() {
146 let a = XMath::default();
147 let b = XMath::new();
148 assert_eq!(a.get_name(), b.get_name());
149 assert_eq!(a.is_secondary, b.is_secondary);
150 }
151
152 #[test]
153 fn xmath_raw_id_suffix() {
154 let x = XMath::new();
155 assert_eq!(x.raw_id_suffix(), ".xm");
156 }
157
158 #[test]
159 fn xmath_is_secondary_false_by_default() {
160 let x = XMath::new();
161 assert!(!x.is_secondary());
162 }
163
164 #[test]
165 fn xmath_mimetype_is_x_latexml() {
166 assert_eq!(XMATH_MIMETYPE, "application/x-latexml");
167 }
168}