#![allow(clippy::needless_return)]
use sxd_document::dom::{Element, ChildOfElement};
use sxd_xpath::{Value, Context, context, function::*, nodeset::*};
use crate::definitions::DEFINITIONS;
use regex::Regex;
use crate::pretty_print::mml_to_string;
use std::cell::Ref;
use phf::phf_set;
use crate::canonicalize::{as_element, name};
fn get_text_from_element(e: &Element) -> String {
if e.children().len() == 1 {
if let ChildOfElement::Text(t) = e.children()[0] {
return t.text().to_string();
}
}
return "".to_string();
}
#[allow(non_snake_case)]
fn get_text_from_COE(coe: &ChildOfElement) -> String {
let element = coe.element();
return match element {
Some(e) => get_text_from_element(&e),
None => "".to_string(),
};
}
pub fn validate_one_node<'n>(nodes: Nodeset<'n>, func_name: &str) -> Result<Node<'n>, Error> {
if nodes.size() == 0 {
return Err(Error::Other(format!("Missing argument for {}", func_name)));
} else if nodes.size() > 1 {
return Err( Error::Other(format!("{} arguments for {}; expected 1 argument", nodes.size(), func_name)) );
}
return Ok( nodes.iter().next().unwrap() );
}
fn is_tag(e: &Element, name: &str) -> bool {
return e.name().local_part() == name;
}
#[allow(non_snake_case)]
fn is_COE_tag(coe: &ChildOfElement, name: &str) -> bool {
let element = coe.element();
return element.is_some() && is_tag(&element.unwrap(), name);
}
pub struct IsNode;
impl IsNode {
pub fn is_simple(elem: &Element) -> bool {
if is_trivially_simple(elem) {
return true;
}
if is_negative_of_trivially_simple(elem) {
return true;
}
if !is_tag(elem, "mrow") || elem.children().is_empty() {
return false;
}
#[allow(clippy::if_same_then_else)]
if is_times_mi(elem) {
return true; } else if is_degrees(elem) {
return true; } else if is_function(elem) {
return true;
}
return false;
fn to_str<'a>(e: &'a Element) -> &'a str {
if e.children().len() == 1 {
let text_node = e.children()[0];
if let Some(t) = text_node.text() {
return t.text();
}
}
return "";
}
fn coe_to_str<'a>(coe: &'a ChildOfElement) -> &'a str {
let element_node = coe.element();
if let Some(e) = element_node {
if e.children().len() == 1 {
let text_node = e.children()[0];
if let Some(t) = text_node.text() {
return t.text();
}
}
}
return "";
}
fn is_single_char(str: &str) -> bool {
let mut chars = str.chars();
let first_char = chars.next();
let second_char = chars.next();
return first_char.is_some() && second_char.is_none();
}
fn is_trivially_simple(elem: &Element) -> bool {
if is_tag(elem, "mn") {
return true;
}
if is_tag(elem, "mi") && is_single_char(to_str(elem)) {
return true;
}
if IsNode::is_common_fraction(elem, 10, 19) {
return true;
}
return false;
}
fn is_negative_of_trivially_simple(elem: &Element) -> bool {
if is_tag(elem, "mrow") && elem.children().len() == 2 {
let children = elem.children();
if is_COE_tag(&children[0], "mo") && is_equal(&children[0], '-') &&
children[1].element().is_some() && is_trivially_simple(&children[1].element().unwrap()) {
return true;
}
}
if is_tag(elem, "negative") && elem.children().len() == 1 {
let child = elem.children()[0];
if let Some(e) = child.element() {
return is_trivially_simple(&e);
}
}
return false;
}
fn is_equal(coe: &ChildOfElement, ch: char) -> bool {
return coe_to_str(coe).starts_with(ch);
}
fn is_times_mi(mrow: &Element) -> bool {
assert!( is_tag(mrow, "mrow") );
let children = mrow.children();
if !(children.len() == 3 || children.len() == 5) {
return false;
}
if children[0].element().is_none() {
return false;
}
let first_child = children[0].element().unwrap();
if !is_trivially_simple(&first_child) {
if !is_negative_of_trivially_simple(&first_child) {
return false;
}
if children.len() == 5 && !is_COE_tag(&first_child.children()[1], "mn") {
return false; }
}
if !(is_COE_tag(&children[1], "mo") &&
is_equal(&children[1], '\u{2062}') &&
is_COE_tag(&children[2], "mi") &&
coe_to_str(&children[2]).len()==1 ) {
return false;
}
if children.len() == 3 {
return true;
}
return is_COE_tag(&children[3], "mo") &&
is_equal(&children[3], '\u{2062}') && is_COE_tag(&children[4], "mi") &&
coe_to_str(&children[4]).len()==1 ;
}
fn is_degrees(mrow: &Element) -> bool {
assert!( is_tag(mrow, "mrow") );
let children = mrow.children();
return children.len() == 2 &&
is_equal(&children[1], '°') &&
(is_COE_tag(&children[0], "mi") ||
is_COE_tag(&children[0], "mn") );
}
fn is_function(mrow: &Element) -> bool {
assert!( is_tag(mrow, "mrow") );
let children = mrow.children();
if children.len() != 3 {
return false;
}
if !(is_COE_tag(&children[1], "mo") &&
is_equal(&children[1], '\u{2061}') ) { return false;
}
if !is_COE_tag(&children[0], "mi") {
return false;
}
let function_arg = children[2].element().unwrap();
if IsBracketed::is_bracketed(&function_arg, "(", ")", false, false) {
return IsNode::is_simple(&function_arg.children()[1].element().unwrap());
} else {
return IsNode::is_simple(&function_arg);
}
}
}
fn is_trig_name(e: &Element) -> bool {
if e.name().local_part() != "mi" {
return false;
}
let mi_text = get_text_from_element(e);
return DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
return definitions.get_hashset("TrigFunctionNames").unwrap().contains(&mi_text);
});
}
fn is_common_fraction(frac: &Element, num_limit: usize, denom_limit: usize) -> bool {
lazy_static! {
static ref ALL_DIGITS: Regex = Regex::new(r"\d+").unwrap(); }
if !is_tag(frac, "mfrac") && !is_tag(frac, "fraction"){
return false;
}
let children = frac.children();
if children.len() != 2 {
return false;
}
let num = children[0].element();
let denom = children[1].element();
if num.is_none() || denom.is_none() {
return false;
};
let num = num.unwrap();
let denom = denom.unwrap();
if !is_tag(&num, "mn") || !is_tag(&denom, "mn") {
return false
};
let num = get_text_from_element(&num);
let denom = get_text_from_element(&denom);
if num.is_empty() || denom.is_empty() {
return false;
}
return ALL_DIGITS.is_match(&num) && is_small_enough(&num, num_limit) &&
ALL_DIGITS.is_match(&denom) && is_small_enough(&denom, denom_limit);
fn is_small_enough(val: &str, upper_bound: usize) -> bool {
return if let Ok(value) = val.parse::<usize>() { value <= upper_bound } else { false };
}
}
fn is_punctuation(node: &Element) -> bool {
lazy_static! {
static ref PUNCTUATION: Regex = Regex::new(r"[':,–—―⸺⸻—…!\-.?‘’“”;']").unwrap(); }
let text = get_text_from_element(node);
return PUNCTUATION.is_match(&text);
}
}
static MATHML_LEAF_NODES: phf::Set<&str> = phf_set! {
"mi", "mo", "mn", "mtext", "ms", "mspace", "mglyph",
"annotation", "ci", "cn", "csymbol", };
static MATHML_2D_NODES: phf::Set<&str> = phf_set! {
"mfrac", "msqrt", "mroot", "menclose",
"msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts",
"mtable", "mtr", "mlabeledtr", "mtd",
};
pub fn is_leaf(element: Element) -> bool {
return MATHML_LEAF_NODES.contains(name(&element));
}
impl Function for IsNode {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(2)?;
let kind = args.pop_string()?;
match kind.as_str() {
"simple" | "leaf" | "common_fraction" | "trig_name" | "2D" | "nemeth_punctuation" => (),
_ => return Err( Error::Other(format!("Unknown argument value '{}' for IsNode", kind.as_str())) ),
};
let nodes = args.pop_nodeset()?;
if nodes.size() == 0 {
return Err(Error::Other("Missing argument for IsNode".to_string() ));
};
return Ok(
Value::Boolean(
nodes.iter()
.all(|node|
if let Node::Element(e) = node {
match kind.as_str() {
"simple" => IsNode::is_simple(&e),
"leaf" => MATHML_LEAF_NODES.contains(name(&e)),
"2D" => MATHML_2D_NODES.contains(name(&e)),
"trig_name" => IsNode::is_trig_name(&e),
"common_fraction" => IsNode::is_common_fraction(&e, usize::MAX, usize::MAX),
"nemeth_punctuation" => IsNode::is_punctuation(&e),
_ => true, }
} else {
false
}
)
)
);
}
}
struct ToOrdinal;
impl ToOrdinal {
fn compute_irregular_fractional_speech(number: &str, plural: bool) -> Option<String> {
DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
let words = if plural {
definitions.get_vec("NumbersOrdinalFractionalPluralOnes").unwrap()
} else {
definitions.get_vec("NumbersOrdinalFractionalOnes").unwrap()
};
let number_as_int: usize = number.parse().unwrap(); if number_as_int < words.len() {
return Some( words[number_as_int].clone() );
};
return None;
})
}
fn convert(number: &str, fractional: bool, plural: bool) -> String {
lazy_static! {
static ref NO_DIGIT: Regex = Regex::new(r"[^\d]").unwrap(); }
DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
let numbers_large = definitions.get_vec("NumbersLarge").unwrap();
if number.is_empty() || number.len() > 3*numbers_large.len() || number.contains(".,") {
return String::from(number);
}
if NO_DIGIT.is_match(number) {
return String::from(number);
}
if fractional {
if let Some(string) = ToOrdinal::compute_irregular_fractional_speech(number, plural) {
return string;
}
}
let num_thousands_at_end = match number.rfind(|ch| ch > '0') { Some(n) => (number.len() - 1 - n) / 3 ,
None => 0
};
let (number,_) = number.split_at(number.len() - 3 * num_thousands_at_end);
let number = match number.len() % 3 {
0 => "".to_string() + number,
1 => "00".to_string() + number,
_ => "0".to_string() + number, };
const ASCII_0: usize = 48;
let digits = number.as_bytes()
.iter()
.map(|&byte| byte as usize - ASCII_0)
.collect::<Vec<usize>>();
let mut answer = String::with_capacity(255); let large_words = numbers_large;
if digits.len() > 3 {
let words = [
definitions.get_vec("NumbersHundreds").unwrap(),
definitions.get_vec("NumbersTens").unwrap(),
definitions.get_vec("NumbersOnes").unwrap(),
];
answer = digits[0..digits.len()-3]
.chunks(3)
.enumerate()
.map(|(i, chunk)| {
if chunk[0] != 0 || chunk[1] != 0 || chunk[2] != 0 {
ToOrdinal::hundreds_to_words(chunk, &words) + " " +
&large_words[num_thousands_at_end + digits.len()/3 - 1 - i] + " "
} else {
"".to_string()
}
})
.collect::<Vec<String>>()
.join(""); if num_thousands_at_end > 0 {
let large_words = if plural {
definitions.get_vec("NumbersOrdinalPluralLarge")
} else {
definitions.get_vec("NumbersOrdinalLarge")
};
return answer + &large_words.unwrap()[num_thousands_at_end];
}
};
let words = match (num_thousands_at_end > 0, plural) {
(true, _) => [
definitions.get_vec("NumbersHundreds").unwrap(),
definitions.get_vec("NumbersTens").unwrap(),
definitions.get_vec("NumbersOnes").unwrap(),
],
(false, true) => [
definitions.get_vec("NumbersOrdinalPluralHundreds").unwrap(),
definitions.get_vec("NumbersOrdinalPluralTens").unwrap(),
definitions.get_vec("NumbersOrdinalPluralOnes").unwrap(),
],
(false, false) => [
definitions.get_vec("NumbersOrdinalHundreds").unwrap(),
definitions.get_vec("NumbersOrdinalTens").unwrap(),
definitions.get_vec("NumbersOrdinalOnes").unwrap(),
],
};
answer += &ToOrdinal::hundreds_to_words(&digits[digits.len()-3..], &words);
if num_thousands_at_end > 0 {
let large_words = if plural {
definitions.get_vec("NumbersOrdinalPluralLarge").unwrap()
} else {
definitions.get_vec("NumbersOrdinalLarge").unwrap()
};
answer = answer + " " + &large_words[num_thousands_at_end];
}
return answer;
})
}
fn hundreds_to_words(number: &[usize], words: &[Ref<Vec<String>>; 3]) -> String {
assert!( number.len() == 3 );
return DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
if number[0] != 0 && number[1] == 0 && number[2] == 0 {
return words[0][number[0]].clone();
}
let mut hundreds = definitions.get_vec("NumbersHundreds").unwrap()[number[0]].clone();
if !hundreds.is_empty() {
hundreds += " ";
}
if number[1] != 0 && number[2] == 0 {
return hundreds + &words[1][number[1]];
}
if 10*number[1] < words[2].len() {
return hundreds + &words[2][10*number[1] + number[2]];
} else {
return hundreds + &definitions.get_vec("NumbersTens").unwrap()[number[1]] + " " + &words[2][number[2]];
}
});
}
}
impl Function for ToOrdinal {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(1)?;
let node = validate_one_node(args.pop_nodeset()?, "ToOrdinal")?;
return match node {
Node::Text(t) => Ok( Value::String( ToOrdinal::convert(t.text(), false, false) ) ),
Node::Element(e) => Ok( Value::String( ToOrdinal::convert(&get_text_from_element(&e), false, false) ) ),
_ => Err( Error::ArgumentNotANodeset{actual: ArgumentType::String} ),
}
}
}
struct ToCommonFraction;
impl Function for ToCommonFraction {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(1)?;
let node = validate_one_node(args.pop_nodeset()?, "ToCommonFraction")?;
if let Node::Element(frac) = node {
if !IsNode::is_common_fraction(&frac, usize::MAX, usize::MAX) {
return Err( Error::Other( format!("ToCommonFraction -- argument is not an 'mfrac': {}': ", mml_to_string(&frac))) );
}
let children = frac.children();
let num = children[0].element().unwrap();
let num = get_text_from_element( &num );
let denom = children[1].element().unwrap();
let denom = get_text_from_element( &denom );
let mut answer = num.clone() + " ";
answer += &ToOrdinal::convert(&denom, true, num!="1");
return Ok( Value::String( answer ) );
} else {
return Err( Error::Other( "ToCommonFraction -- argument is not an element".to_string()) );
}
}
}
struct Min;
impl Function for Min {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(2)?;
let num1 = args.pop_number()?;
let num2 = args.pop_number()?;
return Ok( Value::Number( num1.min(num2) ) );
}
}
struct Max;
impl Function for Max {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(2)?;
let num1 = args.pop_number()?;
let num2 = args.pop_number()?;
return Ok( Value::Number( num1.max(num2) ) );
}
}
struct IsLargeOp;
impl Function for IsLargeOp {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(1)?;
let node = validate_one_node(args.pop_nodeset()?, "IsLargeOp")?;
if let Node::Element(e) = node {
if !is_tag(&e, "mo") {
return Ok( Value::Boolean(false) );
}
return DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
let text = get_text_from_element(&e);
return Ok( Value::Boolean(definitions.get_hashset("LargeOperators").unwrap().get(&text).is_some()) );
});
} else {
return Ok( Value::Boolean(false) );
}
}
}
struct BaseNode;
impl BaseNode {
fn base_node(node: Element) -> Element {
let name = name(&node);
if ["msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts"].contains(&name) {
return BaseNode::base_node(as_element(node.children()[0]));
} else {
return node;
}
}
}
impl Function for BaseNode {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(1)?;
let node = validate_one_node(args.pop_nodeset()?, "BaseNode")?;
if let Node::Element(e) = node {
let mut node_set = Nodeset::new();
node_set.add(BaseNode::base_node(e));
return Ok( Value::Nodeset(node_set) );
} else {
return Err( Error::Other("Argument other than a node given to BaseNode".to_string()) );
}
}
}
struct IfThenElse;
impl Function for IfThenElse {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let args = Args(args);
args.exactly(3)?;
let if_val = &args[0];
let then_val = &args[1];
let else_val = &args[2];
let is_true = match if_val {
Value::Nodeset(nodes) => nodes.size() > 0,
Value::Boolean(b) => *b,
Value::Number(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
};
return Ok( if is_true {then_val.clone()} else {else_val.clone()});
}
}
struct Debug;
impl Function for Debug {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(2)?;
let xpath_str = args.pop_string()?;
let eval_result = &args[0];
debug!(" -- Debug: value of '{}' is ", xpath_str);
match eval_result {
Value::Nodeset(nodes) => {
if nodes.size() == 0 {
debug!("0 nodes (false)");
} else {
let singular = nodes.size()==1;
debug!("{} node{}. {}:", nodes.size(),
if singular {""} else {"s"},
if singular {"Node is"} else {"Nodes are"});
nodes.document_order()
.iter()
.enumerate()
.for_each(|(i, node)| {
match node {
Node::Element(mathml) => debug!("#{}:\n{}",
i, mml_to_string(mathml)),
_ => debug!("'{:?}'", node),
}
})
}
},
_ => debug!("'{:?}'", eval_result),
}
return Ok( eval_result.clone() );
}
}
pub struct IsBracketed;
impl IsBracketed {
pub fn is_bracketed(element: &Element, left: &str, right: &str, requires_comma: bool, requires_mrow: bool) -> bool {
use crate::canonicalize::is_fence;
if requires_mrow && !is_tag(element, "mrow") {
return false;
}
let children = element.children();
let n_children = children.len();
if (n_children == 0 ||
!left.is_empty() && !right.is_empty() && n_children < 2) ||
requires_comma && element.children().len() < 3 {
return false;
}
let first_child = as_element(children[0]);
let last_child = as_element(children[children.len()-1]);
if (left.is_empty() && (name(&first_child) != "mo" || !is_fence(first_child))) ||
(right.is_empty() && (name(&last_child) != "mo" || !is_fence(last_child))) {
return false;
}
if !left.is_empty() && get_text_from_COE(&children[0]) != left ||
!right.is_empty() && get_text_from_COE(&children[children.len()-1]) != right {
return false;
}
if requires_comma {
if let ChildOfElement::Element(contents) = children[1] {
let children = contents.children();
if !is_tag(&contents, "mrow") || children.len() <= 1 {
return false;
}
if get_text_from_COE(&children[1]).as_str() == "," {
return true;
}
}
return false;
} else {
return true;
}
}
}
impl Function for IsBracketed {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.at_least(3)?;
args.at_most(5)?;
let mut requires_comma = false;
let mut requires_mrow = true;
if args.len() == 5 {
requires_mrow = args.pop_boolean()?;
}
if args.len() >= 4 {
requires_comma = args.pop_boolean()?;
}
let right = args.pop_string()?;
let left = args.pop_string()?;
let node = validate_one_node(args.pop_nodeset()?, "IsBracketed")?;
if let Node::Element(e) = node {
return Ok( Value::Boolean( IsBracketed::is_bracketed(&e, &left, &right, requires_comma, requires_mrow) ) );
}
return Ok( Value::Boolean(false) );
}
}
pub struct IsInDefinition;
impl IsInDefinition {
fn is_defined_in(element: &Element, set_name: &str) -> Result<bool, Error> {
let text = get_text_from_element(element);
if text.is_empty() {
return Ok(false);
}
return DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
if let Some(set) = definitions.get_hashset(set_name) {
return Ok( set.contains(&text) );
}
return Err( Error::Other( format!("\n IsInDefinition: '{}' is not defined in definitions.yaml", set_name) ) );
});
}
}
impl Function for IsInDefinition {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(2)?;
let set_name = args.pop_string()?;
let node = validate_one_node(args.pop_nodeset()?, "IsInDefinition")?;
if let Node::Element(e) = node {
return match IsInDefinition::is_defined_in(&e, &set_name) {
Ok(result) => Ok( Value::Boolean( result ) ),
Err(e) => Err(e)
};
}
return Ok( Value::Boolean(false) );
}
}
pub struct DistanceFromLeaf;
impl DistanceFromLeaf {
fn distance(element: Element, use_left_side: bool, treat_2d_elements_as_tokens: bool) -> usize {
let mut element = element;
let mut distance = 1;
loop {
if is_leaf(element) {
return distance;
}
if treat_2d_elements_as_tokens && MATHML_2D_NODES.contains(name(&element)) {
return distance;
}
let children = element.children();
assert!(!children.is_empty());
element = as_element( if use_left_side {children[0]} else {children[children.len()-1]} );
distance += 1;
}
}
}
impl Function for DistanceFromLeaf {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(3)?;
let treat_2d_elements_as_tokens = args.pop_boolean()?;
let use_left_side = args.pop_boolean()?;
let node = validate_one_node(args.pop_nodeset()?, "DistanceFromLeaf")?;
if let Node::Element(e) = node {
return Ok( Value::Number( DistanceFromLeaf::distance(e, use_left_side, treat_2d_elements_as_tokens) as f64) );
}
return Err(Error::Other(format!("DistanceFromLeaf: first arg '{:?}' is not a node", node)));
}
}
pub struct EdgeNode;
impl EdgeNode {
fn edge_node<'a, 'b>(element: Element<'a>, use_left_side: bool, stop_node_name: &'b str) -> Option<Element<'a>> {
let element_name = name(&element);
if element_name == "math" {
return Some(element);
};
let parent = element.parent().unwrap().element().unwrap(); let parent_name = name(&parent);
if use_left_side && !element.preceding_siblings().is_empty() { return None;
};
if !use_left_side && !element.following_siblings().is_empty() { let grandparent = parent.parent().unwrap().element().unwrap();
if name(&grandparent) == "math" &&
parent_name == "mrow" && parent.children().len() == 2 { let text = get_text_from_element( &as_element(parent.children()[1]) );
if text == "," || text == "." || text == ";" || text == "?" {
return Some(grandparent);
}
}
return None;
};
if parent_name == stop_node_name ||
(stop_node_name == "2D" && MATHML_2D_NODES.contains(name(&parent))) {
return Some(parent);
};
return EdgeNode::edge_node(parent, use_left_side, stop_node_name)
}
}
impl Function for EdgeNode {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>)
-> Result<Value<'d>, Error>
{
let mut args = Args(args);
args.exactly(3)?;
let stop_node_name = args.pop_string()?;
let use_left_side = args.pop_string()?.to_lowercase() == "left";
let node = validate_one_node(args.pop_nodeset()?, "EdgeNode")?;
if let Node::Element(e) = node {
let result = match EdgeNode::edge_node(e, use_left_side, &stop_node_name) {
Some(found) => found,
None => e,
};
let mut node_set = Nodeset::new();
node_set.add(result);
return Ok( Value::Nodeset(node_set) );
}
return Err(Error::Other(format!("EdgeNode: first arg '{:?}' is not a node", node)));
}
}
pub fn add_builtin_functions(context: &mut Context) {
context.set_function("min", Min); context.set_function("max", Max); context.set_function("NestingChars", crate::braille::NemethNestingChars);
context.set_function("BrailleChars", crate::braille::BrailleChars);
context.set_function("IsNode", IsNode);
context.set_function("ToOrdinal", ToOrdinal);
context.set_function("ToCommonFraction", ToCommonFraction);
context.set_function("IsLargeOp", IsLargeOp);
context.set_function("IsBracketed", IsBracketed);
context.set_function("IsInDefinition", IsInDefinition);
context.set_function("BaseNode", BaseNode);
context.set_function("IfThenElse", IfThenElse);
context.set_function("DistanceFromLeaf", DistanceFromLeaf);
context.set_function("EdgeNode", EdgeNode);
context.set_function("DEBUG", Debug);
}
#[cfg(test)]
mod tests {
use super::*;
use std::{path::PathBuf};
use sxd_document::parser;
use crate::interface::{trim_element, get_element};
fn init_word_list() {
let result = crate::definitions::read_definitions_file(&[
Some(PathBuf::from("Rules/Languages/en/definitions.yaml")),
Some(PathBuf::from("Rules/definitions.yaml")),
None
]);
if let Err(e) = result {
panic!("unable to read 'Rules/Languages/en/definitions.yaml\n{}", e.to_string());
}
}
#[test]
fn ordinal_one_digit() {
init_word_list();
assert_eq!("zeroth", ToOrdinal::convert("0", false, false));
assert_eq!("second", ToOrdinal::convert("2", false, false));
assert_eq!("ninth", ToOrdinal::convert("9", false, false));
assert_eq!("zeroth", ToOrdinal::convert("0", false, true));
assert_eq!("seconds", ToOrdinal::convert("2", false, true));
assert_eq!("ninths", ToOrdinal::convert("9", false, true));
assert_eq!("first", ToOrdinal::convert("1", true, false));
assert_eq!("half", ToOrdinal::convert("2", true, false));
assert_eq!("half", ToOrdinal::convert("02", true, false));
assert_eq!("ninth", ToOrdinal::convert("9", true, false));
assert_eq!("halves", ToOrdinal::convert("2", true, true));
assert_eq!("halves", ToOrdinal::convert("002", true, true));
assert_eq!("ninths", ToOrdinal::convert("9", true, true));
}
#[test]
fn ordinal_two_digit() {
init_word_list();
assert_eq!("tenth", ToOrdinal::convert("10", false, false));
assert_eq!("seventeenth", ToOrdinal::convert("17", false, false));
assert_eq!("thirty second", ToOrdinal::convert("32", false, false));
assert_eq!("fortieth", ToOrdinal::convert("40", false, false));
assert_eq!("tenths", ToOrdinal::convert("10", false, true));
assert_eq!("sixteenths", ToOrdinal::convert("16", false, true));
assert_eq!("eighty eights", ToOrdinal::convert("88", false, true));
assert_eq!("fiftieths", ToOrdinal::convert("50", false, true));
assert_eq!("eleventh", ToOrdinal::convert("11", true, false));
assert_eq!("forty fourth", ToOrdinal::convert("44", true, false));
assert_eq!("ninth", ToOrdinal::convert("9", true, false));
assert_eq!("ninth", ToOrdinal::convert("00000009", true, false));
assert_eq!("sixtieth", ToOrdinal::convert("60", true, false));
assert_eq!("tenths", ToOrdinal::convert("10", true, true));
assert_eq!("tenths", ToOrdinal::convert("0010", true, true));
assert_eq!("elevenths", ToOrdinal::convert("11", true, true));
assert_eq!("nineteenths", ToOrdinal::convert("19", true, true));
assert_eq!("twentieths", ToOrdinal::convert("20", true, true));
}
#[test]
fn ordinal_three_digit() {
init_word_list();
assert_eq!("one hundred first", ToOrdinal::convert("101", false, false));
assert_eq!("two hundred tenth", ToOrdinal::convert("210", false, false));
assert_eq!("four hundred thirty second", ToOrdinal::convert("432", false, false));
assert_eq!("four hundred second", ToOrdinal::convert("402", false, false));
assert_eq!("one hundred first", ToOrdinal::convert("101", true, false));
assert_eq!("two hundred second", ToOrdinal::convert("202", true, false));
assert_eq!("four hundred thirty second", ToOrdinal::convert("432", true, false));
assert_eq!("five hundred third", ToOrdinal::convert("503", true, false));
assert_eq!("three hundred elevenths", ToOrdinal::convert("311", false, true));
assert_eq!("four hundred ninety ninths", ToOrdinal::convert("499", false, true));
assert_eq!("nine hundred ninetieths", ToOrdinal::convert("990", false, true));
assert_eq!("six hundred seconds", ToOrdinal::convert("602", false, true));
assert_eq!("seven hundredths", ToOrdinal::convert("700", true, true));
assert_eq!("one hundredths", ToOrdinal::convert("100", true, true));
assert_eq!("eight hundred seventeenths", ToOrdinal::convert("817", true, true));
}
#[test]
fn ordinal_large() {
init_word_list();
assert_eq!("one thousandth", ToOrdinal::convert("1000", false, false));
assert_eq!("two thousand one hundredth", ToOrdinal::convert("2100", false, false));
assert_eq!("thirty thousandth", ToOrdinal::convert("30000", false, false));
assert_eq!("four hundred thousandth", ToOrdinal::convert("400000", false, false));
assert_eq!("four hundred thousandth", ToOrdinal::convert("400000", true, false));
assert_eq!("five hundred thousand second", ToOrdinal::convert("500002", true, false));
assert_eq!("six millionth", ToOrdinal::convert("6000000", true, false));
assert_eq!("sixty millionth", ToOrdinal::convert("60000000", true, false));
assert_eq!("seven billionths", ToOrdinal::convert("7000000000", false, true));
assert_eq!("eight trillionths", ToOrdinal::convert("8000000000000", false, true));
assert_eq!("nine quadrillionths", ToOrdinal::convert("9000000000000000", false, true));
assert_eq!("one quintillionth", ToOrdinal::convert("1000000000000000000", false, false));
assert_eq!("nine billion eight hundred seventy six million five hundred forty three thousand two hundred tenths", ToOrdinal::convert("9876543210", true, true));
assert_eq!("nine billion five hundred forty three thousand two hundred tenths", ToOrdinal::convert("9000543210", true, true));
assert_eq!("zeroth", ToOrdinal::convert("00000", false, false));
}
fn test_is_simple(message: &'static str, mathml_str: &'static str) {
crate::speech::SPEECH_RULES.with(|_| true);
let package = parser::parse(mathml_str)
.expect("failed to parse XML");
let mathml = get_element(&package);
trim_element(&mathml);
assert!(IsNode::is_simple(&mathml), "{}", message);
}
fn test_is_not_simple(message: &'static str, mathml_str: &'static str) {
crate::speech::SPEECH_RULES.with(|_| true);
let package = parser::parse(mathml_str)
.expect("failed to parse XML");
let mathml = get_element(&package);
trim_element(&mathml);
assert!(!IsNode::is_simple(&mathml), "{}", message);
}
#[test]
fn is_simple() {
test_is_simple("single variable", "<mi>x</mi>");
test_is_simple("single number", "<mn>1.2</mn>");
test_is_simple("negative number", "<mrow><mo>-</mo><mn>10</mn></mrow>");
test_is_simple("negative variable", "<mrow><mo>-</mo><mi>x</mi></mrow>");
test_is_simple("ordinal fraction", "<mfrac><mn>3</mn><mn>4</mn></mfrac>");
test_is_simple("x y", "<mrow><mi>x</mi><mo>⁢</mo><mi>y</mi></mrow>");
test_is_simple("negative two vars",
"<mrow><mrow><mo>-</mo><mi>x</mi></mrow><mo>⁢</mo><mi>y</mi></mrow>");
test_is_simple("-2 x y",
"<mrow><mrow><mo>-</mo><mn>2</mn></mrow>
<mo>⁢</mo><mi>x</mi><mo>⁢</mo><mi>z</mi></mrow>");
test_is_simple("sin x", "<mrow><mi>sin</mi><mo>⁡</mo><mi>x</mi></mrow>");
test_is_simple("f(x)", "<mrow><mi>f</mi><mo>⁡</mo><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></mrow>");
test_is_simple("f(x+y)",
"<mrow><mi>f</mi><mo>⁡</mo>\
<mrow><mo>(</mo><mi>x</mi><mo>+</mo><mi>y</mi><mo>)</mo></mrow></mrow>");
}
#[test]
fn is_not_simple() {
test_is_not_simple("multi-char variable", "<mi>rise</mi>");
test_is_not_simple("large ordinal fraction", "<mfrac><mn>30</mn><mn>4</mn></mfrac>");
test_is_not_simple("fraction with var in numerator", "<mfrac><mi>x</mi><mn>4</mn></mfrac>");
test_is_not_simple("square root", "<msqrt><mi>x</mi></msqrt>");
test_is_not_simple("subscript", "<msub><mi>x</mi><mn>4</mn></msub>");
test_is_not_simple("-x y z",
"<mrow><mrow><mo>-</mo><mi>x</mi></mrow>
<mo>⁢</mo><mi>y</mi><mo>⁢</mo><mi>z</mi></mrow>");
}
#[test]
fn at_left_edge() {
let mathml = "<math><mfrac><mrow><mn>30</mn><mi>x</mi></mrow><mn>4</mn></mfrac></math>";
let package = parser::parse(mathml).expect("failed to parse XML");
let mathml = get_element(&package);
trim_element(&mathml);
let fraction = as_element(mathml.children()[0]);
let mn = as_element(as_element(fraction.children()[0]).children()[0]);
assert_eq!(EdgeNode::edge_node(mn, true, "2D"), Some(fraction));
assert_eq!(EdgeNode::edge_node(mn, false, "2D"), None);
let mi = as_element(as_element(fraction.children()[0]).children()[1]);
assert_eq!(EdgeNode::edge_node(mi, true, "2D"), None);
}
#[test]
fn at_right_edge() {
let mathml = "<math><mrow><mfrac><mn>4</mn><mrow><mn>30</mn><mi>x</mi></mrow></mfrac><mo>.</mo></mrow></math>";
let package = parser::parse(mathml).expect("failed to parse XML");
let mathml = get_element(&package);
trim_element(&mathml);
let fraction = as_element(as_element(mathml.children()[0]).children()[0]);
let mi = as_element(as_element(fraction.children()[1]).children()[1]);
assert_eq!(EdgeNode::edge_node(mi, true, "2D"), None);
assert_eq!(EdgeNode::edge_node(mi, false, "2D"), Some(fraction));
assert_eq!(EdgeNode::edge_node(mi, false, "math"), Some(mathml));
let mn = as_element(as_element(fraction.children()[1]).children()[0]);
assert_eq!(EdgeNode::edge_node(mn, true, "2D"), None);
}
}