From ade6829f146eeb56141c80b5b5db24d17ff3ca02 Mon Sep 17 00:00:00 2001 From: Attila Hammer Date: Fri, 4 Sep 2026 19:29:29 +0200 Subject: [PATCH 1/4] Rules/Languages/hu/definitions.yaml: fix some intent translations Signed-off-by: Attila Hammer --- Rules/Languages/hu/definitions.yaml | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Rules/Languages/hu/definitions.yaml b/Rules/Languages/hu/definitions.yaml index d66738ec4..0616df949 100644 --- a/Rules/Languages/hu/definitions.yaml +++ b/Rules/Languages/hu/definitions.yaml @@ -347,30 +347,30 @@ - NavigationParts: { # These are the parts of a formula that can be navigated to - "large-op": "base; lower limit; upper limit", - "mfrac": "numerator; denominator", + "large-op": "alap; alsó határ; felső határ", + "mfrac": "számláló; nevező", "fraction": "számláló; nevező", "msqrt": "gyök", "square-root": "gyök", - "mroot": "root; root index", + "mroot": "gyök; gyök index", "root": "gyök; gyök index", - "msub": "base; subscript", - "sub": "base; subscript", - "logarithm-with-base": "base", - "indexed-by": "base; subscript", - "msup": "base; superscript", - "say-super": "base; superscript", - "skip-super": "base; superscript", - "power": "base; exponent", - "msubsup": "base; subscript; superscript", - "munder": "base; under limit", - "mover": "base; upper limit", - "munderover": "base; under limit; upper limit", + "msub": "alap; alsó index", + "sub": "alap; alsó index", + "logarithm-with-base": "alap", + "indexed-by": "alap; alsó index", + "msup": "alap; felső index", + "say-super": "alap; felső index", + "skip-super": "alap; felső index", + "power": "alap; kitevő", + "msubsup": "alap; alsó index; felső index", + "munder": "alap; határérték alatt", + "mover": "alap; határérték felett", + "munderover": "alap; határérték alatt; határérték felett", # words for moving into and out of one of the parts (e.g., "move right 'out of' numerator, 'in' denominator") # it's a hack to put them here, but at least they are grouped with the other navigation parts - "in": "in", - "out": "out of", + "in": "ban/ben", + "out": "ki", } - KnownWords: { From 07d49aa61f75af0f30d764d8469305509168653a Mon Sep 17 00:00:00 2001 From: Attila Hammer Date: Fri, 4 Sep 2026 19:33:05 +0200 Subject: [PATCH 2/4] Rules/Languages/hu/navigate.yaml: Fix some navigation translation rules Signed-off-by: Attila Hammer --- Rules/Languages/hu/navigate.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Rules/Languages/hu/navigate.yaml b/Rules/Languages/hu/navigate.yaml index 475e1f625..e98c0e770 100644 --- a/Rules/Languages/hu/navigate.yaml +++ b/Rules/Languages/hu/navigate.yaml @@ -56,30 +56,30 @@ then: [T: "teljesen ráközelített"] # phrase('zoomed in all of the way') - else_if: "$NavNodeOffset = 0" then: [T: "ráközelített az első karakterre"] # phrase('zoomed in to first character') - else: [T: "karakterre nagyítva"] # phrase('zoomed in to character') + else: [T: "ráközelített a karakterre"] # phrase('zoomed in to character') - pause: "medium" else: - test: # CommandOffset is 1 + length of the English NavCommand stem (Zoom/Move/Read/Describe), # not the spoken Prefix length — so translations can use a different Prefix word. - if: "starts-with($NavCommand, 'Zoom')" - then: [set_variables: [Prefix: "'zoom'", CommandOffset: "5"]] # phrase('zoom' in to see more details) + then: [set_variables: [Prefix: "'nagyítás'", CommandOffset: "5"]] # phrase('zoom' in to see more details) - else_if: "starts-with($NavCommand, 'Move')" - then: [set_variables: [Prefix: "'move'", CommandOffset: "5"]] # phrase('move' to next entry in table) + then: [set_variables: [Prefix: "'ugrás'", CommandOffset: "5"]] # phrase('move' to next entry in table) - else_if: "starts-with($NavCommand, 'Read')" - then: [set_variables: [Prefix: "'read'", CommandOffset: "5"]] # phrase('read' to next entry in table) + then: [set_variables: [Prefix: "'olvasás'", CommandOffset: "5"]] # phrase('read' to next entry in table) - else_if: "starts-with($NavCommand, 'Describe')" - then: [set_variables: [Prefix: "'describe'", CommandOffset: "9"]] # phrase('describe' to next entry in table) + then: [set_variables: [Prefix: "'leírás'", CommandOffset: "9"]] # phrase('describe' to next entry in table) - test: if: "$Prefix != ''" then: - x: "$Prefix" - test: - if: "substring($NavCommand, $CommandOffset) = 'In'" - then: [T: "ban"] # phrase(zoom 'in' to see more details) + then: [T: ""] # phrase(zoom 'in' to see more details) - else_if: "substring($NavCommand, $CommandOffset) = 'InAll'" # HACK: '\uF8FE' is used internally for the concatenation char by 'ct' -- this gets "ed" concatenated to "zoom" - then: [T: "Teljesen kinagyítva"] # phrase(zoom 'out all of the way' to see more details) + then: [T: "Teljesen nagyítsa ki"] # phrase(zoom 'out all of the way' to see more details) - else_if: "substring($NavCommand, $CommandOffset) = 'Out'" then: [T: "ki"] # phrase(zoom 'out' to see more details) - else_if: "substring($NavCommand, $CommandOffset) = 'OutAll'" @@ -1322,12 +1322,12 @@ if: "preceding-sibling::* and following-sibling::*[1][name(.)='none']" then: - with: - variables: [Move2D: "'out of'", Child2D: "."] + variables: [Move2D: "'ki'", Child2D: "."] replace: [x: ".."] - x: "following-sibling::*[1]" else: - with: - variables: [Move2D: "'in'", Child2D: "following-sibling::*[1]"] # phrase('in' the denominator) + variables: [Move2D: "'ban/ben'", Child2D: "following-sibling::*[1]"] # phrase('in' the denominator) replace: [x: ".."] - with: variables: [NavCommand: "'MoveNextZoom'"] From b7c781f723bda3fd8685d00122893089c45cba32 Mon Sep 17 00:00:00 2001 From: Attila Hammer Date: Fri, 4 Sep 2026 19:34:20 +0200 Subject: [PATCH 3/4] Added hungarian navigation tests, based with english tests Signed-off-by: Attila Hammer --- tests/Languages/hu.rs | 2 +- tests/Languages/hu/navigate.rs | 274 +++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 tests/Languages/hu/navigate.rs diff --git a/tests/Languages/hu.rs b/tests/Languages/hu.rs index ac24dd22e..b15d27050 100644 --- a/tests/Languages/hu.rs +++ b/tests/Languages/hu.rs @@ -32,4 +32,4 @@ mod alphabets; mod definitions; mod intent; mod mtable; - +mod navigate; diff --git a/tests/Languages/hu/navigate.rs b/tests/Languages/hu/navigate.rs new file mode 100644 index 000000000..3596cd28e --- /dev/null +++ b/tests/Languages/hu/navigate.rs @@ -0,0 +1,274 @@ +//! Navigation ZoomIn speech vs NavigationParts and intent fixity. +//! +//! Uses only real intents / MathML from `definitions.yaml` and `Rules/Intent/*.yaml` +//! (same shapes as other en language tests). Fixities covered are those that actually +//! appear there: prefix, infix, postfix, function, silent, nofix. + +use crate::common::*; +use anyhow::Result; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn init_nav(mathml: &str) -> Result<()> { + set_rules_dir(abs_rules_dir_path())?; + set_preference("Language", "hu")?; + set_preference("SpeechStyle", "SimpleSpeak")?; + set_preference("Verbosity", "Medium")?; + set_preference("NavMode", "Enhanced")?; + set_preference("NavVerbosity", "Verbose")?; + set_preference("AutoZoomOut", "False")?; + set_preference("Overview", "False")?; + set_mathml(mathml)?; + Ok(()) +} + +fn assert_zoom_in(command: &str, mathml: &str, expected: &str) -> Result<()> { + init_panic_handler(); + let result = catch_unwind(AssertUnwindSafe(|| { + init_nav(mathml)?; + let speech = do_navigate_command(command)?; + let trimmed_speech = speech.trim_end_matches([' ', ',', ';']).to_string(); + assert_eq!(expected, trimmed_speech); + Ok(()) + })); + report_any_panic(result) +} + +// --- Intents in NavigationParts (prefix, infix, function, silent; no postfix/nofix) --- + +#[test] +fn parts_prefix_logarithm_with_base() -> Result<()> { + // Intent/general.yaml log-with-base → logarithm-with-base:prefix; parts "base" + let expr = r#" + + + log + b + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in alap; b") +} + +#[test] +fn parts_infix_power() -> Result<()> { + // power:infix; parts "base; exponent" + let expr = r#" + + + x + 2 + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in alap; x") +} + +#[test] +fn parts_infix_indexed_by() -> Result<()> { + // indexed-by:infix; parts "base; subscript" + let expr = r#" + + + x + 1 + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in alap; x") +} + +#[test] +fn parts_function_fraction() -> Result<()> { + // fraction (from mfrac); parts "numerator; denominator" + let expr = r#" + + + 1 + 2 + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in számláló; 1") +} + +#[test] +fn parts_function_square_root() -> Result<()> { + // square-root:function; parts "root" + let expr = r#" + + + x + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in gyök; x") +} + +#[test] +fn parts_silent_skip_super() -> Result<()> { + // skip-super:silent (degree); parts "base; superscript" + let expr = r#" + + + x + ° + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in alap; x") +} + +// --- Intents not in NavigationParts --- + +#[test] +fn no_parts_prefix_unary_minus() -> Result<()> { + // minus:prefix (Intent/general.yaml positive-or-negative) — silent "in" + let expr = r#" + + + - + b + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; b") +} + +#[test] +fn no_parts_prefix_limit() -> Result<()> { + // limit:prefix — silent "in" + let expr = r#" + + + x + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; x") +} + +#[test] +fn no_parts_prefix_vector() -> Result<()> { + // vector:prefix from mover + arrow + let expr = r#" + + + v + + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; v") +} + +#[test] +fn no_parts_infix_binomial() -> Result<()> { + // binomial:infix (choose); no NavigationParts → "in part 1" + let expr = r#" + + + ( + + 7 + 3 + + ) + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in része 1; 7") +} + +#[test] +fn no_parts_postfix_transpose() -> Result<()> { + // transpose:postfix — not prefix, so still announces "in …" + let expr = r#" + + + M + T + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in transzponált; nagy m") +} + +#[test] +fn no_parts_function_absolute_value() -> Result<()> { + // absolute-value:function + let expr = r#" + + + | + x + | + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; in az abszolút érték; x") +} + +#[test] +fn no_parts_silent_modified_variable() -> Result<()> { + // modified-variable:silent (x-hat) — no NavigationParts → silent "in" + let expr = r#" + + + x + ^ + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; x") +} + +#[test] +fn no_parts_nofix_set_of_reals() -> Result<()> { + // set-of-reals:nofix — leaf + let expr = r#" + + R + + "#; + assert_zoom_in("ZoomIn", expr, "teljesen ráközelített; az összes valós szám halmaza") +} + +// --- No intent= attribute (native MathML only; may still be inferred) --- + +#[test] +fn no_intent_sum_mrow() -> Result<()> { + let expr = r#" + + + x + + + y + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; x") +} + +#[test] +fn no_intent_times_mrow() -> Result<()> { + let expr = r#" + + + 2 + + a + + + "#; + assert_zoom_in("ZoomIn", expr, "nagyítás; 2") +} + +#[test] +fn no_intent_mi() -> Result<()> { + let expr = r#" + + x + + "#; + assert_zoom_in("ZoomIn", expr, "teljesen ráközelített; x") +} From 78b273f34717c3222bd793503938088eafde6475 Mon Sep 17 00:00:00 2001 From: Attila Hammer Date: Fri, 4 Sep 2026 20:07:01 +0200 Subject: [PATCH 4/4] src/canonicalize.rs, src/chemistry.rs, src/tts.rs: apply clippy fixes with latest stable Rust release related --- src/canonicalize.rs | 14 ++++++-------- src/chemistry.rs | 15 ++++++--------- src/tts.rs | 14 ++++++-------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/canonicalize.rs b/src/canonicalize.rs index 380c4de0c..d20374d49 100644 --- a/src/canonicalize.rs +++ b/src/canonicalize.rs @@ -2412,23 +2412,21 @@ impl CanonicalizeContext { // This is not yet in canonical form, so the fences may be siblings or siblings of the parent let preceding_siblings = as_element(children[0]).preceding_siblings(); let following_siblings = as_element(children[end-1]).following_siblings(); - let first_child; - let last_child; - if preceding_siblings.is_empty() && following_siblings.is_empty() { + + + let (first_child, last_child) = if preceding_siblings.is_empty() && following_siblings.is_empty() { // number spans all children, look to parent for fences let preceding_children = mrow.preceding_siblings(); let following_children = mrow.following_siblings(); if preceding_children.is_empty() || following_children.is_empty() { return true; // doesn't have left or right fence } - first_child = preceding_children[preceding_children.len()-1]; - last_child = following_children[0]; + (preceding_children[preceding_children.len()-1], following_children[0]) } else if preceding_siblings.is_empty() || following_siblings.is_empty() { return true; // can't be fences around it } else { - first_child = preceding_siblings[preceding_siblings.len()-1]; - last_child = following_siblings[0]; - } + (preceding_siblings[preceding_siblings.len()-1], following_siblings[0]) + }; let first_child = as_element(first_child); let last_child = as_element(last_child); return !(name(first_child) == "mo" && is_fence(first_child) && diff --git a/src/chemistry.rs b/src/chemistry.rs index 1176d8d3a..23de8668a 100644 --- a/src/chemistry.rs +++ b/src/chemistry.rs @@ -1384,17 +1384,14 @@ pub fn likely_adorned_chem_formula(mathml: Element) -> i32 { // prescripts are normally positive integers, chem 2.5.1 allows for a superscript for a Lewis dot // postscript should be a charge - let prescripts; - let postscripts; - if children.len() == 4 && name(as_element(children[1]))=="mprescripts" { // just prescripts - prescripts = &children[2..4]; - postscripts = &children[0..0]; // empty + + + let (prescripts, postscripts) = if children.len() == 4 && name(as_element(children[1]))=="mprescripts" { // just prescripts + (&children[2..4], &children[0..0]) // empty } else if children.len() == 6 && name(as_element(children[3]))=="mprescripts" { // pre and postscripts - prescripts = &children[4..6]; - postscripts = &children[1..3]; // empty + (&children[4..6], &children[1..3]) // empty } else if children.len() == 3 || children.len() == 5 { // just postscripts (simultaneous or offset) - prescripts = &children[0..0]; // empty - postscripts = &children[1..]; + (&children[0..0], &children[1..]) } else { return NOT_CHEMISTRY; }; diff --git a/src/tts.rs b/src/tts.rs index 91b05c0a8..c2f951b39 100644 --- a/src/tts.rs +++ b/src/tts.rs @@ -669,18 +669,16 @@ impl TTS { /// There is a bias towards pausing more _after_ longer strings. pub fn compute_auto_pause(&self, prefs: &PreferenceManager, before: &str, after: &str) -> Result { static REMOVE_XML: LazyLock = LazyLock::new(|| Regex::new(r"<.+?>").unwrap()); // punctuation ending with a '.' - let before_len; - let after_len; - match self { + + + let (before_len, after_len) = match self { TTS::SSML | TTS::SAPI5 => { - before_len = REMOVE_XML.replace_all(before, "").len(); - after_len = REMOVE_XML.replace_all(after, "").len(); + (REMOVE_XML.replace_all(before, "").len(), REMOVE_XML.replace_all(after, "").len()) }, _ => { - before_len = before.len(); - after_len = after.len(); + (before.len(), after.len()) }, - } + }; // pause values are not cut in stone // the calculation bias to 'previous' is based on MathPlayer which used '30 * #-of-descendants-on-left