latex2mathml/
ast.rs

1use std::fmt;
2use super::attribute::{Variant, Accent, LineThickness, ColumnAlign};
3use crate::DisplayStyle;
4
5/// AST node
6#[derive(Debug, Clone, PartialEq)]
7pub enum Node {
8    Number(String),
9    Letter(char, Variant),
10    Operator(char),
11    Function(String, Option<Box<Node>>),
12    Space(f32),
13    Subscript(Box<Node>, Box<Node>),
14    Superscript(Box<Node>, Box<Node>),
15    SubSup{ target: Box<Node>, sub: Box<Node>, sup: Box<Node>},
16    OverOp(char, Accent, Box<Node>),
17    UnderOp(char, Accent, Box<Node>),
18    Overset{over: Box<Node>, target: Box<Node>},
19    Underset{under: Box<Node>, target: Box<Node>},
20    Under(Box<Node>, Box<Node>),
21    UnderOver { target: Box<Node>, under: Box<Node>, over: Box<Node>},
22    Sqrt(Option<Box<Node>>, Box<Node>),
23    Frac(Box<Node>, Box<Node>, LineThickness),
24    Row(Vec<Node>),
25    Fenced { open: &'static str, close: &'static str, content: Box<Node> },
26    StrechedOp(bool, String),
27    OtherOperator(&'static str),
28    SizedParen{ size: &'static str, paren: &'static str },
29    Text(String),
30    Matrix(Vec<Node>, ColumnAlign),
31    Ampersand,
32    NewLine,
33    Slashed(Box<Node>),
34    Style(Option<DisplayStyle>, Box<Node>),
35    Undefined(String),
36}
37
38impl fmt::Display for Node {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Node::Number(number)  => write!(f, "<mn>{}</mn>", number),
42            Node::Letter(letter, var) => match var {
43                Variant::Italic => write!(f, "<mi>{}</mi>", letter),
44                var             => write!(f, r#"<mi mathvariant="{}">{}</mi>"#, var, letter),
45            },
46            Node::Operator(op) => if op == &'∂' {
47                write!(f, r#"<mo mathvariant="italic">∂</mo>"#)
48            } else { write!(f, r#"<mo>{}</mo>"#, op) },
49            Node::Function(fun, arg) => match arg {
50                Some(arg) => write!(f, "<mi>{}</mi><mo>&#x2061;</mo>{}", fun, arg),
51                None      => write!(f, "<mi>{}</mi>", fun),
52            },
53            Node::Space(space) => write!(f, r#"<mspace width="{}em"/>"#, space),
54            Node::Subscript(a, b) => write!(f, "<msub>{}{}</msub>", a, b),
55            Node::Superscript(a, b) => write!(f, "<msup>{}{}</msup>", a, b),
56            Node::SubSup{target, sub, sup} => write!(f, "<msubsup>{}{}{}</msubsup>", target, sub, sup),
57            Node::OverOp(op, acc, target) => write!(f, r#"<mover>{}<mo accent="{}">{}</mo></mover>"#, target, acc, op),
58            Node::UnderOp(op, acc, target) => write!(f, r#"<munder>{}<mo accent="{}">{}</mo></munder>"#, target, acc, op),
59            Node::Overset{over, target} => write!(f, r#"<mover>{}{}</mover>"#, target, over),
60            Node::Underset{under, target} => write!(f, r#"<munder>{}{}</munder>"#, target, under),
61            Node::Under(target, under) => write!(f, r#"<munder>{}{}</munder>"#, target, under),
62            Node::UnderOver{target, under, over} => write!(f, r#"<munderover>{}{}{}</munderover>"#, target, under, over),
63            Node::Sqrt(degree, content) => match degree {
64                Some(deg) => write!(f, "<mroot>{}{}</mroot>", content, deg),
65                None      => write!(f, "<msqrt>{}</msqrt>", content),
66            },
67            Node::Frac(num, denom, lt) => write!(f, "<mfrac{}>{}{}</mfrac>", lt, num, denom),
68            Node::Row(vec) => write!(f, "<mrow>{}</mrow>", 
69                vec.iter().map(|node| format!("{}", node)).collect::<String>()
70            ),
71            Node::Fenced{open, close, content} => {
72                write!(f, r#"<mrow><mo stretchy="true" form="prefix">{}</mo>{}<mo stretchy="true" form="postfix">{}</mo></mrow>"#, open, content, close)
73            },
74            Node::StrechedOp(stretchy, op) => write!(f, r#"<mo stretchy="{}">{}</mo>"#, stretchy, op),
75            Node::OtherOperator(op) => write!(f, "<mo>{}</mo>", op),
76            Node::SizedParen{size, paren} => write!(f, r#"<mrow><mo maxsize="{0}" minsize="{0}">{1}</mro></mrow>"#, size, paren),
77            Node::Slashed(node) => match &**node {
78                Node::Letter(x, var) => write!(f, "<mi mathvariant=\"{}\">{}&#x0338;</mi>", var, x),
79                Node::Operator(x) => write!(f, "<mo>{}&#x0338;</mo>", x),
80                n => write!(f, "{}", n),
81            },
82            Node::Matrix(content, columnalign) => {
83                let mut mathml = format!("<mtable{}><mtr><mtd>", columnalign);
84                for (i, node) in content.iter().enumerate() {
85                    match node {
86                        Node::NewLine => {
87                            mathml.push_str("</mtd></mtr>");
88                            if i < content.len() {
89                                mathml.push_str("<mtr><mtd>")
90                            }
91                        },
92                        Node::Ampersand => {
93                            mathml.push_str("</mtd>");
94                            if i < content.len() {
95                                mathml.push_str("<mtd>")
96                            }
97                        },
98                        node => { mathml = format!("{}{}", mathml, node); },
99                    }
100                }
101                mathml.push_str("</mtd></mtr></mtable>");
102                
103                write!(f, "{}", mathml)
104            },
105            Node::Text(text) => write!(f, "<mtext>{}</mtext>", text),
106            Node::Style(display, content) => match display {
107                Some(DisplayStyle::Block)  => write!(f, r#"<mstyle displaystyle="true">{}</mstyle>"#, content),
108                Some(DisplayStyle::Inline) => write!(f, r#"<mstyle displaystyle="false">{}</mstyle>"#, content),
109                None => write!(f, "<mstyle>{}</mstyle>", content),
110            },
111            node => write!(f, "<mtext>[PARSE ERROR: {:?}]</mtext>", node),
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::super::attribute::Variant;
119    use super::Node;
120
121    #[test]
122    fn node_display() {
123        let problems = vec![
124            (Node::Number("3.14".to_owned()), "<mn>3.14</mn>"),
125            (Node::Letter('x', Variant::Italic), "<mi>x</mi>"),
126            (Node::Letter('α', Variant::Italic), "<mi>α</mi>"),
127            (Node::Letter('あ', Variant::Normal), r#"<mi mathvariant="normal">あ</mi>"#),
128            (
129                Node::Row(vec![ Node::Operator('+'), Node::Operator('-') ]), 
130                r"<mrow><mo>+</mo><mo>-</mo></mrow>"
131            ),
132        ];
133        for (problem, answer) in problems.iter() {
134            assert_eq!(&format!("{}", problem), answer);
135        }
136    }
137}