Skip to content
3 changes: 3 additions & 0 deletions Rules/Languages/en/SharedRules/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@
- x: "count(preceding-sibling::*)+IfThenElse(parent::m:mlabeledtr, 0, 1)"
- pause: medium
- x: "*"
- test:
if: "HasVisibleColumnLine(../.., count(preceding-sibling::*) + 1)"
then: [t: "separator"]
- test:
# short pause after each element; medium pause if last element in a row; long pause for last element in matrix
- if: count(following-sibling::*) > 0
Expand Down
2 changes: 1 addition & 1 deletion src/speech.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1758,7 +1758,7 @@ impl<'c, 'r> ContextStack<'c> {
fn base_context(var_defs: PreferenceHashMap) -> sxd_xpath_no_unsafe::Context<'c> {
let mut context = sxd_xpath_no_unsafe::Context::new();
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
crate::xpath_functions::add_builtin_functions(&mut context);
crate::xpath_functions::register_mathcat_xpath_functions(&mut context);
for (key, value) in var_defs {
context.set_variable(key.as_str(), yaml_to_value(&value));
// if let Some(str_value) = value.as_str() {
Expand Down
85 changes: 83 additions & 2 deletions src/xpath_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1569,9 +1569,56 @@ impl Function for CountTableColumns {
}
}

/// Return whether a one-based mtable boundary has a visible column line.
///
/// MathML repeats the final `columnlines` value for remaining boundaries.
/// Boundaries after the final column, and values other than `solid` and
/// `dashed`, do not describe a visible separator.
fn has_visible_column_line(table: Element, boundary: usize) -> bool {
if boundary == 0 || !is_tag(table, "mtable") {
return false;
}

let Ok((_, Value::Number(column_count))) = CountTableDims::new().count_table_dims(table) else {
return false;
};
if boundary as f64 >= column_count {
return false;
}

return table
.attribute_value("columnlines")
.map(|values| {
matches!(
values.split_whitespace().take(boundary).last(),
Some("solid" | "dashed")
)
})
.unwrap_or(false);
}

struct HasVisibleColumnLine;
impl Function for HasVisibleColumnLine {
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 boundary = args.pop_number()?;
let table = validate_one_node(args.pop_nodeset()?, "HasVisibleColumnLine")?;
let Node::Element(table) = table else {
return Err(Error::Other { what: "HasVisibleColumnLine requires an mtable element".to_string() });
};
if !boundary.is_finite() || boundary < 1.0 || boundary.fract() != 0.0 {
return Ok(Value::Boolean(false));
}
return Ok(Value::Boolean(has_visible_column_line(table, boundary as usize)));
}
}


/// Add all the functions defined in this module to `context`.
pub fn add_builtin_functions(context: &mut Context) {
pub fn register_mathcat_xpath_functions(context: &mut Context) {
context.set_function("NestingChars", crate::braille::NemethNestingChars);
context.set_function("BrailleChars", crate::braille::BrailleChars);
context.set_function("NeedsToBeGrouped", crate::braille::NeedsToBeGrouped);
Expand All @@ -1591,6 +1638,7 @@ pub fn add_builtin_functions(context: &mut Context) {
context.set_function("GetNavigationPartName", GetNavigationPartName);
context.set_function("CountTableRows", CountTableRows);
context.set_function("CountTableColumns", CountTableColumns);
context.set_function("HasVisibleColumnLine", HasVisibleColumnLine);
context.set_function("DEBUG", Debug);

// Not used: remove??
Expand Down Expand Up @@ -1796,7 +1844,10 @@ mod tests {
let package = parser::parse(mathml).map_err(|e| anyhow::anyhow!("failed to parse XML: {e}"))?;
let math_elem = get_element(&package);
let child = as_element(math_elem.children()[0]);
assert!(CountTableDims::new().count_table_dims(child) == Ok((Value::Number(dims.0 as f64), Value::Number(dims.1 as f64))));
assert_eq!(
CountTableDims::new().count_table_dims(child),
Ok((Value::Number(dims.0 as f64), Value::Number(dims.1 as f64)))
);
return Ok( () );
}

Expand All @@ -1816,6 +1867,36 @@ mod tests {
});
}

fn check_column_line(mathml: &str, boundary: usize, expected: bool) -> Result<()> {
let package = parser::parse(mathml).map_err(|e| anyhow::anyhow!("failed to parse XML: {e}"))?;
let math = get_element(&package);
let table = as_element(math.children()[0]);
assert_eq!(has_visible_column_line(table, boundary), expected);
return Ok(());
}

/// Verifies visible column-line styles, repeated styles, and boundaries outside the table.
#[test]
fn visible_column_lines() -> Result<()> {
return xpath_test(|| {
check_column_line("<math><mtable columnlines=' none solid\n dashed '><mtr><mtd/><mtd/><mtd/><mtd/></mtr></mtable></math>", 1, false)?;
check_column_line("<math><mtable columnlines=' none solid\n dashed '><mtr><mtd/><mtd/><mtd/><mtd/></mtr></mtable></math>", 2, true)?;
check_column_line("<math><mtable columnlines=' none solid\n dashed '><mtr><mtd/><mtd/><mtd/><mtd/></mtr></mtable></math>", 3, true)?;

check_column_line("<math><mtable columnlines='solid dashed'><mtr><mtd/><mtd/><mtd/><mtd/></mtr></mtable></math>", 3, true)?;

// No column-line style is specified.
check_column_line("<math><mtable><mtr><mtd/><mtd/></mtr></mtable></math>", 1, false)?;
// The boundary is explicitly invisible.
check_column_line("<math><mtable columnlines='none'><mtr><mtd/><mtd/></mtr></mtable></math>", 1, false)?;
// Only `solid` and `dashed` describe visible column lines.
check_column_line("<math><mtable columnlines='double'><mtr><mtd/><mtd/></mtr></mtable></math>", 1, false)?;
// Boundary 2 is after the final column, not between two columns.
check_column_line("<math><mtable columnlines='solid'><mtr><mtd/><mtd/></mtr></mtable></math>", 2, false)?;
return Ok(());
});
}

#[test]
fn at_left_edge() -> Result<()> {
return xpath_test(|| {
Expand Down
31 changes: 23 additions & 8 deletions tests/Languages/en/mtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,23 @@ fn augmented_matrix_2x3() -> Result<()> {
<mo>]</mo></mrow></mrow>
</math>
";
test("en", "ClearSpeak", expr, "the 2 by 3 augmented matrix; row 1; 3, 1, 4; row 2; 0, 2, 6")?;
test("en", "SimpleSpeak", expr, "the 2 by 3 augmented matrix; row 1; 3, 1, 4; row 2; 0, 2, 6")?;
test("en", "ClearSpeak", expr, "the 2 by 3 augmented matrix; row 1; 3, 1 separator, 4; row 2; 0, 2 separator, 6")?;
test("en", "SimpleSpeak", expr, "the 2 by 3 augmented matrix; row 1; 3, 1 separator, 4; row 2; 0, 2 separator, 6")?;
Ok(())
}

#[test]
fn dashed_augmented_matrix_separator() -> Result<()> {
let expr = "
<math xmlns='http://www.w3.org/1998/Math/MathML'>
<mrow><mo>[</mo>
<mtable columnlines='dashed'>
<mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd><mtd><mn>3</mn></mtd></mtr>
</mtable>
<mo>]</mo></mrow>
</math>";
test("en", "ClearSpeak", expr, "the 1 by 3 row matrix; 1 separator, 2 separator, 3")?;
test("en", "SimpleSpeak", expr, "the 1 by 3 row matrix; 1 separator, 2 separator, 3")?;
Ok(())
}

Expand Down Expand Up @@ -926,13 +941,13 @@ let expr = "<math display='block' xmlns='http://www.w3.org/1998/Math/MathML'>
</mrow>
</math>";
test_ClearSpeak("en", "ClearSpeak_Matrix", "EndMatrix",
expr, "the 3 by 4 augmented matrix; row 1; column 1; 1, column 2; 2, column 3; negative 1, column 4; 3; \
row 2; column 1; negative 3, column 2; 3, column 3; negative 1, column 4; 2; \
row 3; column 1; 2, column 2; 3, column 3; 2, column 4; negative 1; end matrix")?;
expr, "the 3 by 4 augmented matrix; row 1; column 1; 1, column 2; 2, column 3; negative 1 separator, column 4; 3; \
row 2; column 1; negative 3, column 2; 3, column 3; negative 1 separator, column 4; 2; \
row 3; column 1; 2, column 2; 3, column 3; 2 separator, column 4; negative 1; end matrix")?;
test("en", "SimpleSpeak",
expr, "the 3 by 4 augmented matrix; row 1; column 1; 1, column 2; 2, column 3; negative 1, column 4; 3; \
row 2; column 1; negative 3, column 2; 3, column 3; negative 1, column 4; 2; \
row 3; column 1; 2, column 2; 3, column 3; 2, column 4; negative 1; end matrix")?;
expr, "the 3 by 4 augmented matrix; row 1; column 1; 1, column 2; 2, column 3; negative 1 separator, column 4; 3; \
row 2; column 1; negative 3, column 2; 3, column 3; negative 1 separator, column 4; 2; \
row 3; column 1; 2, column 2; 3, column 3; 2 separator, column 4; negative 1; end matrix")?;
Ok(())
}

Expand Down
Loading