Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Rules/Intent/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,20 @@
name: "system-of-equations"
children:
- x: "*"
-
name: mtable-array-property
tag: mtable
match: "count(*) > 0 and ((@frame='solid' or @frame='dashed') or child::*[@rowspan]) or child::*/child::*[@rowspan or @colspan]"
replace:
- with:
variables:
- TableProperty: "'array'"
replace:
- intent:
name: "array"
children:
- x: "*"


-
name: mtable-lines-property
Expand Down
11 changes: 6 additions & 5 deletions Rules/Languages/en/SharedRules/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -419,22 +419,23 @@
- t: "end scripts" # phrase(At this point 'end scripts' occurs)

- name: default
tag: mtable
tag: [mtable, array]
variables:
- IsColumnSilent: "false()"
- NumColumns: "count(*[1]/*) - IfThenElse(*/self::m:mlabeledtr, 1, 0)"
- NumColumns: "CountTableColumns(.)"
- NumRows: "CountTableRows(.)"
match: "."
replace:
- t: "table with" # phrase(the 'table with' 3 rows)
- x: count(*)
- x: "$NumRows"
- test:
if: count(*)=1
if: "$NumRows=1"
then: [t: "row"] # phrase(the table with 1 'row')
else: [t: "rows"] # phrase(the table with 3 'rows')
- t: "and" # phrase(the table with 3 rows 'and' 4 columns)
- x: "$NumColumns"
- test:
if: "NumColumns=1"
if: "$NumColumns=1"
then: [t: "column"] # phrase(the table with 3 rows and 1 'column')
else: [t: "columns"] # phrase(the table with 3 rows and 4 'columns')
- pause: long
Expand Down
3 changes: 2 additions & 1 deletion src/speech.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::path::PathBuf;
use std::collections::HashMap;
use std::cell::{RefCell, RefMut};
use std::sync::LazyLock;
use std::fmt::Debug;
use sxd_document::dom::{ChildOfElement, Document, Element};
use sxd_document::{Package, QName};
use sxd_xpath::context::Evaluation;
Expand Down Expand Up @@ -311,7 +312,7 @@ pub fn process_include<F>(current_file: &Path, new_file_name: &str, mut read_new

/// As the name says, TreeOrString is either a Tree (Element) or a String
/// It is used to share code during pattern matching
pub trait TreeOrString<'c, 'm:'c, T> {
pub trait TreeOrString<'c, 'm:'c, T: Debug> : Debug {
fn from_element(e: Element<'m>) -> Result<T>;
fn from_string(s: String, doc: Document<'m>) -> Result<T>;
fn replace_tts<'s:'c, 'r>(tts: &TTS, command: &TTSCommandRule, prefs: &PreferenceManager, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T>;
Expand Down
111 changes: 110 additions & 1 deletion src/xpath_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,91 @@ impl Function for ReplaceAll {
}
}

pub struct CountTableDims;
impl CountTableDims {
/// For an `mtable` element, count the number of rows and columns in the table.
///
/// This function is relatively permissive. Non-`mtr` rows are
/// ignored. The number of columns is determined only from the first
/// row, if it exists. Within that row, non-`mtd` elements are ignored.
fn count_table_dims<'d>(e: Element<'_>) -> Result<(Value<'d>, Value<'d>), Error> {
let mut num_cols = 0;
let mut num_rows = 0;
for child in e.children() {
let ChildOfElement::Element(row) = child else {
continue
};

// each child of mtable should be an mtr. Ignore non-mtr rows.
let row_name = name(row);

let labeled_row = if row_name == "mlabeledtr" {
true
} else if row_name == "mtr" {
false
} else {
continue;
};
num_rows += 1;

// count columns based on the number of rows.
if num_rows == 1 {
// count the number of columns, including column spans, in the first row.
let mut first_elem = true;
for row_child in row.children() {
let ChildOfElement::Element(mtd) = row_child else {
continue;
};
if name(mtd) != "mtd" {
continue;
}
// Add the contributing columns, taking colspan into account. Don't contribute if
// this is the first element of a labeled row.
let colspan = mtd.attribute_value("colspan").map_or(1, |e| e.parse::<usize>().unwrap_or(0));
if !(labeled_row && first_elem) {
num_cols += colspan;
}
first_elem = false;
}
}
}

Ok((Value::Number(num_rows as f64), Value::Number(num_cols as f64)))
}

fn evaluate<'d>(fn_name: &str,
args: Vec<Value<'d>>) -> Result<(Value<'d>, Value<'d>), Error> {
let mut args = Args(args);
args.exactly(1)?;
let element = args.pop_nodeset()?;
let node = validate_one_node(element, fn_name)?;
if let Node::Element(e) = node {
return Self::count_table_dims(e);
}

Err( Error::Other("couldn't count table rows".to_string()) )
}
}

pub struct CountTableRows;
impl Function for CountTableRows {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>) -> Result<Value<'d>, Error> {
CountTableDims::evaluate("CountTableRows", args).map(|a| a.0)
}
}

pub struct CountTableColumns;
impl Function for CountTableColumns {
fn evaluate<'c, 'd>(&self,
_context: &context::Evaluation<'c, 'd>,
args: Vec<Value<'d>>) -> Result<Value<'d>, Error> {
CountTableDims::evaluate("CountTableColumns", args).map(|a| a.1)
}
}


/// Add all the functions defined in this module to `context`.
pub fn add_builtin_functions(context: &mut Context) {
context.set_function("NestingChars", crate::braille::NemethNestingChars);
Expand All @@ -1432,6 +1517,8 @@ pub fn add_builtin_functions(context: &mut Context) {
context.set_function("SpeakIntentName", SpeakIntentName);
context.set_function("GetBracketingIntentName", GetBracketingIntentName);
context.set_function("GetNavigationPartName", GetNavigationPartName);
context.set_function("CountTableRows", CountTableRows);
context.set_function("CountTableColumns", CountTableColumns);
context.set_function("DEBUG", Debug);

// Not used: remove??
Expand Down Expand Up @@ -1606,6 +1693,28 @@ mod tests {

}

#[test]
fn table_row_count() {
let mathml = "<math><mtable><mtr><mtd>a</mtd></mtr></mtable></math>";
let package = parser::parse(mathml).expect("failed to parse XML");
let math_elem = get_element(&package);
let child = as_element(math_elem.children()[0]);
assert!(CountTableDims::count_table_dims(child) == Ok((Value::Number(1.0), Value::Number(1.0))));

let mathml = "<math><mtable><mtr><mtd colspan=\"3\">a</mtd><mtd>b</mtd></mtr><mtr><mtd></mtd></mtr></mtable></math>";
let package = parser::parse(mathml).expect("failed to parse XML");
let math_elem = get_element(&package);
let child = as_element(math_elem.children()[0]);
assert!(CountTableDims::count_table_dims(child) == Ok((Value::Number(2.0), Value::Number(4.0))));

let mathml = "<math><mtable><mlabeledtr><mtd>label</mtd><mtd>a</mtd><mtd>b</mtd></mlabeledtr><mtr><mtd>c</mtd><mtd>d</mtd></mtr></mtable></math>";
let package = parser::parse(mathml).expect("failed to parse XML");
let math_elem = get_element(&package);
let child = as_element(math_elem.children()[0]);
let ctd = CountTableDims::count_table_dims(child);
assert!(ctd == Ok((Value::Number(2.0), Value::Number(2.0))));
}

#[test]
fn at_left_edge() {
let mathml = "<math><mfrac><mrow><mn>30</mn><mi>x</mi></mrow><mn>4</mn></mfrac></math>";
Expand Down Expand Up @@ -1636,4 +1745,4 @@ mod tests {
let mn = as_element(as_element(fraction.children()[1]).children()[0]);
assert_eq!(EdgeNode::edge_node(mn, true, "2D"), None);
}
}
}
35 changes: 25 additions & 10 deletions tests/Languages/en/mtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,9 +1028,8 @@ let expr = "<math><mrow><mrow><mo>(</mo><mrow>
test_ClearSpeak("en", "ClearSpeak_Matrix", "EndVector",
expr, "the 2 by 2 matrix; row 1; column 1; b sub 1 1; column 2; b sub 1 2; \
row 2; column 1; b sub 2 1; column 2; b sub 2 2; end matrix")?;
return Ok(());
}

return Ok(());
}


#[test]
Expand All @@ -1041,19 +1040,35 @@ fn matrix_binomial() -> Result<()> {
</mrow><mo>)</mo>
</math>";
test_ClearSpeak("en", "ClearSpeak_Matrix", "Combinatorics", expr, "3 choose 2")?;
return Ok(());
}
return Ok(());
}

#[test]
fn matrix_simple_table() {
let expr = "<math>
<mtable intent=\":array\"><mtr><mtd><mn>3</mn></mtd></mtr><mtr><mtd><mn>2</mn></mtd></mtr></mtable>
</math>";
let _ = test("en", "ClearSpeak", expr, "table with 2 rows and 1 column; row 1; column 1; 3; row 2; column 1; 2");
}

#[test]
fn matrix_span_table() {
let expr = "<math>
<mtable><mtr rowspan=\"1\"><mtd><mn>3</mn></mtd></mtr><mtr><mtd><mn>2</mn></mtd></mtr></mtable>
</math>";
let _ = test("en", "ClearSpeak", expr, "table with 2 rows and 1 column; row 1; column 1; 3; row 2; column 1; 2");
}


#[test]
fn matrix_times() -> Result<()> {
fn matrix_times() {
let expr = "<math>
<mfenced><mtable><mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd></mtr><mtr><mtd><mn>3</mn></mtd><mtd><mn>4</mn></mtd></mtr></mtable></mfenced>
<mfenced><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd><mtd><mi>d</mi></mtd></mtr></mtable></mfenced>
</math>";
test("en", "SimpleSpeak", expr,
"the 2 by 2 matrix; row 1; 1, 2; row 2; 3, 4; times, the 2 by 2 matrix; row 1; eigh, b; row 2; c, d")?;
return Ok(());
}
let _ = test("en", "SimpleSpeak", expr,
"the 2 by 2 matrix; row 1; 1, 2; row 2; 3, 4; times, the 2 by 2 matrix; row 1; eigh, b; row 2; c, d");
}

#[test]
fn unknown_mtable_property() -> Result<()> {
Expand Down
Loading