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
110
111
112
113
114
115
116
use crate::sbml::import::MATHML;
use roxmltree::{ExpandedName, Node};
use std::option::Option::Some;

const APPLY_TAG: (&str, &str) = (MATHML, "apply");
const NUMBER_TAG: (&str, &str) = (MATHML, "cn");
const IDENTIFIER_TAG: (&str, &str) = (MATHML, "ci");
const SYMBOL_TAG: (&str, &str) = (MATHML, "csymbol");
const TRUE_TAG: (&str, &str) = (MATHML, "true");
const FALSE_TAG: (&str, &str) = (MATHML, "false");

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MathMl {
    Boolean(bool),
    Integer(i64),
    Identifier(String),
    Apply(String, Vec<MathMl>),
    SymbolApply(String, Vec<MathMl>),
}

pub fn read_mathml(math: Node) -> Result<MathMl, String> {
    let child_count = math.children().filter(|c| c.is_element()).count();
    if child_count == 0 {
        return Err("Tag <math> has no children.".to_string());
    }
    if child_count > 1 {
        return Err("More than one child in a <math> tag.".to_string());
    }

    read_expression(math.first_element_child().unwrap())
}

fn read_expression(math: Node) -> Result<MathMl, String> {
    if math.tag_name() == ExpandedName::from(TRUE_TAG) {
        return Ok(MathMl::Boolean(true));
    }

    if math.tag_name() == ExpandedName::from(FALSE_TAG) {
        return Ok(MathMl::Boolean(false));
    }

    if math.tag_name() == ExpandedName::from(IDENTIFIER_TAG) {
        let id = math
            .text()
            .map(|s| s.trim().to_string())
            .unwrap_or_default();
        if id.is_empty() {
            return Err("Empty math identifier.".to_string());
        }
        return Ok(MathMl::Identifier(id));
    }

    if math.tag_name() == ExpandedName::from(NUMBER_TAG) {
        let value = math
            .text()
            .map(|s| s.trim().to_string())
            .unwrap_or_default();
        let num_type = math.attribute((MATHML, "type"));
        if num_type.is_some() && num_type.unwrap() != "integer" {
            return Err(format!(
                "Non-integer numeric types ({}) are not supported.",
                num_type.unwrap()
            ));
        }
        return if let Ok(parsed) = value.parse::<i64>() {
            Ok(MathMl::Integer(parsed))
        } else {
            Err(format!("Invalid integer constant: `{}`.", value))
        };
    }
    if math.tag_name() == ExpandedName::from(APPLY_TAG) {
        let op_tag = math.first_element_child();
        return if let Some(op_tag) = op_tag {
            let mut args = Vec::new();
            let mut arg = op_tag.next_sibling_element();
            while let Some(inner) = arg {
                args.push(read_expression(inner)?);
                arg = inner.next_sibling_element();
            }
            if op_tag.tag_name() == ExpandedName::from(SYMBOL_TAG) {
                let symbol = op_tag
                    .text()
                    .map(|s| s.trim().to_string())
                    .unwrap_or_default();
                if symbol.is_empty() {
                    Err("Empty <csymbol> in MathML.".to_string())
                } else {
                    Ok(MathMl::SymbolApply(symbol, args))
                }
            } else {
                Ok(MathMl::Apply(op_tag.tag_name().name().to_string(), args))
            }
        } else {
            Err("MathML <apply> with no child elements.".to_string())
        };
    }

    Err(format!(
        "Unexpected MathML tag `{}`.",
        math.tag_name().name()
    ))
}

/// Some utility methods for working with MathML trees.
impl MathMl {
    /// Returns true if the function contains given identifier (function symbols do not count).
    pub fn contains_identifier(&self, id: &str) -> bool {
        match self {
            MathMl::Boolean(_) => false,
            MathMl::Integer(_) => false,
            MathMl::Identifier(value) => value == id,
            MathMl::SymbolApply(_, args) => args.iter().any(|a| a.contains_identifier(id)),
            MathMl::Apply(_, args) => args.iter().any(|a| a.contains_identifier(id)),
        }
    }
}