This will override any previous MathML that was set. This returns canonical MathML with 'id's set on any node that doesn't have an id. The ids can be used for sync highlighting if the `Bookmark` API preference is true.
(mathml_str: String)
| 58 | /// This returns canonical MathML with 'id's set on any node that doesn't have an id. |
| 59 | /// The ids can be used for sync highlighting if the `Bookmark` API preference is true. |
| 60 | pub fn set_mathml(mathml_str: String) -> Result<String> { |
| 61 | lazy_static! { |
| 62 | // if these are present when resent to MathJaX, MathJaX crashes (https://github.com/mathjax/MathJax/issues/2822) |
| 63 | static ref MATHJAX_V2: Regex = Regex::new(r#"class *= *['"]MJX-.*?['"]"#).unwrap(); |
| 64 | static ref MATHJAX_V3: Regex = Regex::new(r#"class *= *['"]data-mjx-.*?['"]"#).unwrap(); |
| 65 | static ref NAMESPACE_DECL: Regex = Regex::new(r#"xmlns:[[:alpha:]]+"#).unwrap(); // very limited namespace prefix match |
| 66 | static ref PREFIX: Regex = Regex::new(r#"(</?)[[:alpha:]]+:"#).unwrap(); // very limited namespace prefix match |
| 67 | static ref HTML_ENTITIES: Regex = Regex::new(r#"&([a-zA-Z]+?);"#).unwrap(); |
| 68 | } |
| 69 | |
| 70 | NAVIGATION_STATE.with(|nav_stack| { |
| 71 | nav_stack.borrow_mut().reset(); |
| 72 | }); |
| 73 | return MATHML_INSTANCE.with(|old_package| { |
| 74 | // FIX: convert this to an included file once I get the full entity list |
| 75 | static HTML_ENTITIES_MAPPING: phf::Map<&str, &str> = include!("entities.in"); |
| 76 | |
| 77 | let mut error_message = "".to_string(); // can't return a result inside the replace_all, so we do this hack of setting the message and then returning the error |
| 78 | // need to deal with character data and convert to something the parser knows |
| 79 | let mathml_str = HTML_ENTITIES.replace_all(&mathml_str, |cap: &Captures| { |
| 80 | match HTML_ENTITIES_MAPPING.get(&cap[1]) { |
| 81 | None => { |
| 82 | error_message = format!("No entity named '{}'", &cap[0]); |
| 83 | cap[0].to_string() |
| 84 | }, |
| 85 | Some(&ch) => ch.to_string(), |
| 86 | } |
| 87 | }); |
| 88 | |
| 89 | if !error_message.is_empty() { |
| 90 | bail!(error_message); |
| 91 | } |
| 92 | let mathml_str = MATHJAX_V2.replace_all(&mathml_str, ""); |
| 93 | let mathml_str = MATHJAX_V3.replace_all(&mathml_str, ""); |
| 94 | |
| 95 | // the speech rules use the xpath "name" function and that includes the prefix |
| 96 | // getting rid of the prefix properly probably involves a recursive replacement in the tree |
| 97 | // if the prefix is used, it is almost certainly something like "m" or "mml", so this cheat will work. |
| 98 | let mathml_str = NAMESPACE_DECL.replace(&mathml_str, "xmlns"); // do this before the PREFIX replace! |
| 99 | let mathml_str = PREFIX.replace_all(&mathml_str, "$1"); |
| 100 | |
| 101 | let new_package = parser::parse(&mathml_str); |
| 102 | if let Err(e) = new_package { |
| 103 | bail!("Invalid MathML input:\n{}\nError is: {}", &mathml_str, &e.to_string()); |
| 104 | } |
| 105 | crate::speech::SpeechRules::initialize_all_rules()?; |
| 106 | |
| 107 | let new_package = new_package.unwrap(); |
| 108 | let mathml = get_element(&new_package); |
| 109 | let mathml = cleanup_mathml(mathml)?; |
| 110 | let mathml_string = mml_to_string(&mathml); |
| 111 | old_package.replace(new_package); |
| 112 | |
| 113 | return Ok( mathml_string ); |
| 114 | }) |
| 115 | } |
| 116 | |
| 117 | /// Get the spoken text of the MathML that was set. |