1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#[cfg(test)]
use crate::ast::operator::Operator;

/// Functionality for normalizing MathExpression enums.
use crate::ast::{
    Math, MathExpression,
    MathExpression::{Mn, Mo, Msub},
    Mi, Mrow,
};

impl MathExpression {
    /// Collapse subscripts
    fn collapse_subscripts(&mut self) {
        match self {
            Msub(base, subscript) => {
                let mut combined = String::from(&base.get_string_repr());
                combined.push_str("_{");
                combined.push_str(&subscript.get_string_repr());
                combined.push('}');
                *self = MathExpression::Mi(Mi(combined));
            }

            MathExpression::Mrow(Mrow(xs)) => {
                for x in xs {
                    x.collapse_subscripts();
                }
            }
            _ => (),
        }
    }

    /// Get the string representation of a MathExpression
    pub fn get_string_repr(&self) -> String {
        match self {
            MathExpression::Mi(Mi(x)) => x.to_string(),
            Mo(x) => x.to_string(),
            Mn(x) => x.to_string(),
            MathExpression::Mrow(Mrow(xs)) => xs
                .iter()
                .map(|x| x.get_string_repr())
                .collect::<Vec<String>>()
                .join(""),
            _ => {
                panic!("The method 'get_string_repr' for the MathExpression enum only handles the following MathML element types: [mi, mo, mn, mrow]!");
            }
        }
    }
}

impl Math {
    /// Normalize the math expression, performing the following steps:
    /// 1. Collapse subscripts assuming we don't need their substructure.
    pub fn normalize(&mut self) {
        for expr in &mut self.content {
            expr.collapse_subscripts();
        }
    }
}

#[test]
fn test_get_string_repr() {
    assert_eq!(
        MathExpression::Mi(Mi("t".to_string())).get_string_repr(),
        "t".to_string()
    );
    assert_eq!(Mo(Operator::Add).get_string_repr(), "+");
    assert_eq!(
        MathExpression::Mrow(Mrow(vec![
            MathExpression::Mi(Mi("t".into())),
            Mo(Operator::Add),
            MathExpression::Mi(Mi("1".to_string()))
        ]))
        .get_string_repr(),
        "t+1".to_string()
    );
}

#[test]
fn test_subscript_collapsing() {
    let mut expr = Msub(
        Box::new(MathExpression::Mi(Mi("S".to_string()))),
        Box::new(MathExpression::Mrow(Mrow(vec![
            MathExpression::Mi(Mi("t".to_string())),
            Mo(Operator::Add),
            MathExpression::Mi(Mi("1".to_string())),
        ]))),
    );
    expr.collapse_subscripts();
    assert_eq!(expr, MathExpression::Mi(Mi("S_{t+1}".to_string())));
}

#[test]
fn test_normalize() {
    let contents = std::fs::read_to_string("tests/sir.xml").unwrap();
    let mut math = contents.parse::<Math>().unwrap();
    math.normalize();
    assert_eq!(
        &math.content[0],
        &MathExpression::Mrow(Mrow(vec![
            MathExpression::Mi(Mi("S_{t+1}".to_string())),
            Mo(Operator::Equals),
            MathExpression::Mi(Mi("S_{t}".to_string())),
            Mo(Operator::Subtract),
            MathExpression::Mi(Mi("β".to_string())),
            MathExpression::Mi(Mi("S_{t}".to_string())),
            MathExpression::Mi(Mi("I_{t}".to_string())),
        ]))
    );
}