Skip to main content

libmathcat/
lib.rs

1#![cfg_attr(coverage, feature(coverage_attribute))]
2#![allow(clippy::needless_return)]
3
4//! A library for generating speech and braille from MathML
5//! 
6//! Typical usage is:
7//! 1. Set the rules directory [`set_rules_dir`]
8//! 2. Set whatever preferences are need with repeated calls to [`set_preference`].
9//! 3. Set MathML via [`set_mathml`]
10//!    A string representing the cleaned up MathML along with `id`s on each node is returned for highlighting if desired
11//! 4. Get the speech [`get_spoken_text`] or (Unicode) braille [`get_braille`].
12//!
13//! The expression can be navigated also.
14//! This is done in one of two ways:
15//! 1. Pass key strokes to allow a user to navigate the MathML by calling [`do_navigate_keypress`]; the speech is returned.
16//! 2. Pass the MathCAT navigation command directory by called [`do_navigate_command`]; the speech is return returned.
17//! 
18//! To get the MathML associated with the current navigation node, call [`get_navigation_mathml`].
19//! To just get the `id` and offset from the id of the current navigation node, call [`get_navigation_mathml_id`].
20///
21/// This module re-exports anyhow types. Use `bail!` for early returns and
22/// `context()`/`with_context()` on Result to add context (replacing old `chain_err()`).
23pub mod errors {
24    pub use anyhow::{anyhow, bail, Error, Result, Context};
25}
26
27pub mod interface;
28#[cfg(feature = "include-zip")]
29pub use shim_filesystem::ZIPPED_RULE_FILES;
30
31mod canonicalize;
32mod infer_intent;
33pub mod speech;
34mod braille;
35mod navigate;
36mod prefs;
37mod tts;
38mod xpath_functions;
39mod definitions;
40pub mod pretty_print;
41mod chemistry;
42
43pub mod shim_filesystem; // really just for override_file_for_debugging_rules, but the config seems to throw it off
44pub use interface::*;
45use crate::errors::{bail, Result};
46
47#[cfg(test)]
48pub fn init_logger() {
49    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
50        .is_test(true)
51        .format_timestamp(None)
52        .format_module_path(false)
53        .format_indent(None)
54        .format_level(false)
55        .init();
56}
57
58/// Build Absolute path to rules dir for testing
59pub fn abs_rules_dir_path() -> String {
60    cfg_if::cfg_if! {
61    if #[cfg(feature = "include-zip")] {
62          return "Rules".to_string();
63    } else {
64        // Package root (see tests/common/mod.rs `abs_rules_dir_path` for rationale).
65        return std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
66            .join("Rules")
67            .to_str()
68            .expect("CARGO_MANIFEST_DIR and Rules path must be UTF-8")
69            .to_string();
70        }
71    }
72}
73
74pub fn are_strs_canonically_equal_with_locale(test: &str, target: &str, ignore_attrs: &[&str], block_separators: &str, decimal_separators: &str) -> Result<()> {
75    use crate::{interface::*, pretty_print::mml_to_string};
76    use sxd_document::parser;
77    use crate::canonicalize::canonicalize;
78    use std::panic::{catch_unwind, AssertUnwindSafe};
79
80    crate::interface::init_panic_handler();
81    let result = catch_unwind(AssertUnwindSafe(|| {
82        // this forces initialization
83        crate::interface::set_rules_dir(abs_rules_dir_path()).unwrap();
84        set_preference("Language", "en").unwrap();
85        set_preference("BlockSeparators", block_separators).unwrap();
86        set_preference("DecimalSeparators", decimal_separators).unwrap();
87        crate::speech::SPEECH_RULES.with(|rules|  rules.borrow_mut().read_files().unwrap());
88
89        let package1 = &parser::parse(test).expect("Failed to parse test input");
90        let mathml = get_element(package1);
91        trim_element(mathml, false);
92        let mathml_test = canonicalize(mathml).unwrap();
93
94        let package2 = &parser::parse(target).expect("Failed to parse target input");
95        let mathml_target = get_element(package2);
96        trim_element(mathml_target, false);
97
98        match is_same_element(mathml_test, mathml_target, ignore_attrs) {
99            Ok(_) => Ok( () ),
100            Err(e) => {
101                bail!("{}\nResult:\n{}\nTarget:\n{}", e, mml_to_string(mathml_test), mml_to_string(mathml_target));
102            },
103        }
104    }));
105    match crate::interface::report_any_panic(result) {
106        Ok(()) => Ok(()),
107        Err(e) => {
108            Err(e)
109        }
110    }
111}
112
113/// sets locale to be US standard
114pub fn are_strs_canonically_equal(test: &str, target: &str, ignore_attrs: &[&str]) -> bool {
115    are_strs_canonically_equal_with_locale(test, target, ignore_attrs, ", \u{00A0}\u{202F}", ".").is_ok()
116}
117
118/// Like `are_strs_canonically_equal` but returns `Result` for use in `#[test]` functions that return `Result<()>`.
119pub fn are_strs_canonically_equal_result(test: &str, target: &str, ignore_attrs: &[&str]) -> Result<()> {
120    are_strs_canonically_equal_with_locale(test, target, ignore_attrs, ", \u{00A0}\u{202F}", ".")
121}