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
34 changes: 24 additions & 10 deletions src/speech.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,24 +709,38 @@ impl Intent {


/// "lift" up the children any "TEMP_NAME" child -- could short circuit when only one child
///
/// TEMP_NAME is only ever a transport wrapper created by `replace_nodes_tree`, so wrappers can nest:
/// a rule whose replacement is a bare `x:` (e.g., the mrow 'matrix' rule returns `x: "*[2]"`) yields
/// TEMP_NAME(matrix), and the parent intent's `x: "*[1]"` wraps that again. Lifting only one level left
/// `power(TEMP_NAME(matrix), 2)`, spoken as "TEMP NAME of the 2 by 2 matrix ... squared" (issue #762).
/// Nested wrappers whose children are all elements are flattened; a leaf wrapper (text from a Text or
/// Attribute node) is only unwrapped when it is a direct child, matching the previous behavior.
fn lift_children(result: Element) -> Element {
// debug!("lift_children:\n{}", mml_to_string(result));
// most likely there will be the same number of new children as result has, but there could be more
let mut new_children = Vec::with_capacity(2*result.children().len());
for child_of_element in result.children() {
match child_of_element {
ChildOfElement::Element(child) => {
if name(child) == "TEMP_NAME" {
new_children.append(&mut child.children()); // almost always just one
} else {
new_children.push(child_of_element);
}
},
_ => new_children.push(child_of_element), // text()
}
push_lifted(child_of_element, &mut new_children, true);
}
result.replace_children(new_children);
return result;

fn push_lifted<'a>(child_of_element: ChildOfElement<'a>, new_children: &mut Vec<ChildOfElement<'a>>, is_direct_child: bool) {
if let ChildOfElement::Element(child) = child_of_element {
if name(child) == "TEMP_NAME" {
let grandchildren = child.children();
let is_leaf_wrapper = grandchildren.iter().any(|gc| matches!(gc, ChildOfElement::Text(_)));
if is_direct_child || !is_leaf_wrapper {
for grandchild in grandchildren {
push_lifted(grandchild, new_children, false);
}
return;
}
}
}
new_children.push(child_of_element);
}
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions tests/Languages/en/mtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1390,3 +1390,47 @@ fn single_line_with_label() -> Result<()> {
expr, "1 equation, with label 2; b equals 2")?;
return Ok(());
}

#[test]
fn matrix_raised_to_power() -> Result<()> {
// A parenthesized matrix as the base of msup must speak as "the matrix ... squared".
// Regression for #762: the mrow 'matrix' intent rule returns a bare `x:`, so the intent tree
// carried a nested TEMP_NAME wrapper into `power` and NVDA read "TEMP NAME of the 2 by 2 matrix ...".
let expr = "<math><msup>
<mrow><mo>(</mo>
<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>
<mo>)</mo></mrow>
<mn>2</mn>
</msup></math>";
test("en", "ClearSpeak", expr, "the 2 by 2 matrix; row 1; 1, 2; row 2; 3, 4; squared")
}

#[test]
fn matrix_raised_to_power_lualatex_spacing() -> Result<()> {
// The exact MathML from #762 (LuaLaTeX pmatrix output): negative-width mspace around the
// table and fence attributes on the parens. Canonicalization must fold the spacing away so
// the matrix is still recognized as the base of the power.
let expr = "<math display='block'>
<msup>
<mrow>
<mo fence='true' lspace='0' rspace='0' symmetric='true'>(</mo>
<mspace width='-4.981pt'/>
<mrow>
<mspace width='4.981pt'/>
<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>
<mspace width='4.981pt'/>
</mrow>
<mspace width='-4.981pt'/>
<mo fence='true' lspace='0' rspace='0' symmetric='true'>)</mo>
</mrow>
<mn>2</mn>
</msup>
</math>";
test("en", "ClearSpeak", expr, "the 2 by 2 matrix; row 1; 1, 2; row 2; 3, 4; squared")
}