1#![allow(clippy::needless_return)]
6use std::path::PathBuf;
7use std::collections::HashMap;
8use std::cell::{RefCell, RefMut};
9use sxd_document::dom::{ChildOfElement, Document, Element};
10use sxd_document::{Package, QName};
11use sxd_xpath::context::Evaluation;
12use sxd_xpath::{Context, Factory, Value, XPath};
13use sxd_xpath::nodeset::Node;
14use std::fmt;
15use std::time::SystemTime;
16use crate::definitions::read_definitions_file;
17use crate::errors::*;
18use crate::prefs::*;
19use yaml_rust::{YamlLoader, Yaml, yaml::Hash};
20use crate::tts::*;
21use crate::infer_intent::*;
22use crate::pretty_print::{mml_to_string, yaml_to_string};
23use std::path::Path;
24use std::rc::Rc;
25use crate::shim_filesystem::{read_to_string_shim, canonicalize_shim};
26use crate::canonicalize::{as_element, create_mathml_element, set_mathml_name, name, MATHML_FROM_NAME_ATTR};
27use regex::Regex;
28
29
30pub const NAV_NODE_SPEECH_NOT_FOUND: &str = "NAV_NODE_NOT_FOUND";
31
32const NO_EVAL_QUOTE_CHAR: char = '\u{e00A}'; const NO_EVAL_QUOTE_CHAR_AS_BYTES: [u8;3] = [0xee,0x80,0x8a];
38const N_BYTES_NO_EVAL_QUOTE_CHAR: usize = NO_EVAL_QUOTE_CHAR.len_utf8();
39
40pub fn make_quoted_string(mut string: String) -> String {
42 string.push(NO_EVAL_QUOTE_CHAR);
43 return string;
44}
45
46pub fn is_quoted_string(str: &str) -> bool {
48 if str.len() < N_BYTES_NO_EVAL_QUOTE_CHAR {
49 return false;
50 }
51 let bytes = str.as_bytes();
52 return bytes[bytes.len()-N_BYTES_NO_EVAL_QUOTE_CHAR..] == NO_EVAL_QUOTE_CHAR_AS_BYTES;
53}
54
55pub fn unquote_string(str: &str) -> &str {
58 return &str[..str.len()-N_BYTES_NO_EVAL_QUOTE_CHAR];
59}
60
61
62pub fn intent_from_mathml<'m>(mathml: Element, doc: Document<'m>) -> Result<Element<'m>> {
73 let intent_tree = intent_rules(&INTENT_RULES, doc, mathml, "")?;
74 doc.root().append_child(intent_tree);
75 return Ok(intent_tree);
76}
77
78pub fn speak_mathml(mathml: Element, nav_node_id: &str) -> Result<String> {
79 return speak_rules(&SPEECH_RULES, mathml, nav_node_id);
80}
81
82pub fn overview_mathml(mathml: Element, nav_node_id: &str) -> Result<String> {
83 return speak_rules(&OVERVIEW_RULES, mathml, nav_node_id);
84}
85
86
87fn intent_rules<'m>(rules: &'static std::thread::LocalKey<RefCell<SpeechRules>>, doc: Document<'m>, mathml: Element, nav_node_id: &'m str) -> Result<Element<'m>> {
88 rules.with(|rules| {
89 rules.borrow_mut().read_files()?;
90 let rules = rules.borrow();
91 let should_set_literal_intent = rules.pref_manager.borrow().pref_to_string("SpeechStyle").as_str() == "LiteralSpeak";
93 let original_intent = mathml.attribute_value("intent");
94 if should_set_literal_intent {
95 if let Some(intent) = original_intent {
96 let intent = if intent.contains('(') {intent.replace('(', ":literal(")} else {intent.to_string() + ":literal"};
97 mathml.set_attribute_value("intent", &intent);
98 } else {
99 mathml.set_attribute_value("intent", ":literal");
100 };
101 }
102 let mut rules_with_context = SpeechRulesWithContext::new(&rules, doc, nav_node_id);
103 let intent = rules_with_context.match_pattern::<Element<'m>>(mathml)
104 .chain_err(|| "Pattern match/replacement failure!")?;
105 let answer = if name(intent) == "TEMP_NAME" { assert_eq!(intent.children().len(), 1);
107 as_element(intent.children()[0])
108 } else {
109 intent
110 };
111 if should_set_literal_intent {
112 if let Some(original_intent) = original_intent {
113 mathml.set_attribute_value("intent", original_intent);
114 } else {
115 mathml.remove_attribute("intent");
116 }
117 }
118 return Ok(answer);
119 })
120}
121
122fn speak_rules(rules: &'static std::thread::LocalKey<RefCell<SpeechRules>>, mathml: Element, nav_node_id: &str) -> Result<String> {
125 rules.with(|rules| {
126 rules.borrow_mut().read_files()?;
127 let rules = rules.borrow();
128 let new_package = Package::new();
130 let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), nav_node_id);
131 let mut speech_string = rules_with_context.match_pattern::<String>(mathml)
132 .chain_err(|| "Pattern match/replacement failure!")?;
133 if !nav_node_id.is_empty() {
136 if let Some(start) = speech_string.find("[[") {
138 match speech_string[start+2..].find("]]") {
139 None => bail!("Internal error: looking for '[[...]]' during navigation -- only found '[[' in '{}'", speech_string),
140 Some(end) => speech_string = speech_string[start+2..start+2+end].to_string(),
141 }
142 } else {
143 bail!(NAV_NODE_SPEECH_NOT_FOUND); }
145 }
146 return Ok( rules.pref_manager.borrow().get_tts()
147 .merge_pauses(remove_optional_indicators(
148 &speech_string.replace(CONCAT_STRING, "")
149 .replace(CONCAT_INDICATOR, "")
150 )
151 .trim_start().trim_end_matches([' ', ',', ';'])) );
152 })
153}
154
155pub fn yaml_to_type(yaml: &Yaml) -> String {
157 return match yaml {
158 Yaml::Real(v)=> format!("real='{v:#}'"),
159 Yaml::Integer(v)=> format!("integer='{v:#}'"),
160 Yaml::String(v)=> format!("string='{v:#}'"),
161 Yaml::Boolean(v)=> format!("boolean='{v:#}'"),
162 Yaml::Array(v)=> match v.len() {
163 0 => "array with no entries".to_string(),
164 1 => format!("array with the entry: {}", yaml_to_type(&v[0])),
165 _ => format!("array with {} entries. First entry: {}", v.len(), yaml_to_type(&v[0])),
166 }
167 Yaml::Hash(h)=> {
168 let first_pair =
169 if h.is_empty() {
170 "no pairs".to_string()
171 } else {
172 let (key, val) = h.iter().next().unwrap();
173 format!("({}, {})", yaml_to_type(key), yaml_to_type(val))
174 };
175 format!("dictionary with {} pair{}. A pair: {}", h.len(), if h.len()==1 {""} else {"s"}, first_pair)
176 }
177 Yaml::Alias(_)=> "Alias".to_string(),
178 Yaml::Null=> "Null".to_string(),
179 Yaml::BadValue=> "BadValue".to_string(),
180 }
181}
182
183fn yaml_type_err(yaml: &Yaml, str: &str) -> String {
184 return format!("Expected {}, found {}", str, yaml_to_type(yaml));
185}
186
187fn find_str<'a>(dict: &'a Yaml, key: &'a str) -> Option<&'a str> {
200 return dict[key].as_str();
201}
202
203pub fn as_hash_checked(value: &Yaml) -> Result<&Hash> {
205 let result = value.as_hash();
206 let result = result.ok_or_else(|| yaml_type_err(value, "hashmap"))?;
207 return Ok( result );
208}
209
210pub fn as_vec_checked(value: &Yaml) -> Result<&Vec<Yaml>> {
212 let result = value.as_vec();
213 let result = result.ok_or_else(|| yaml_type_err(value, "array"))?;
214 return Ok( result );
215}
216
217pub fn as_str_checked(yaml: &Yaml) -> Result<&str> {
219 return Ok( yaml.as_str().ok_or_else(|| yaml_type_err(yaml, "string"))? );
220}
221
222
223pub const CONCAT_INDICATOR: &str = "\u{F8FE}";
227
228pub const CONCAT_STRING: &str = " \u{F8FE}";
230
231const OPTIONAL_INDICATOR: &str = "\u{F8FD}";
234const OPTIONAL_INDICATOR_LEN: usize = OPTIONAL_INDICATOR.len();
235
236pub fn remove_optional_indicators(str: &str) -> String {
237 return str.replace(OPTIONAL_INDICATOR, "");
238}
239
240pub fn compile_rule<F>(str: &str, mut build_fn: F) -> Result<Vec<PathBuf>> where
244 F: FnMut(&Yaml) -> Result<Vec<PathBuf>> {
245 let docs = YamlLoader::load_from_str(str);
246 match docs {
247 Err(e) => {
248 bail!("Parse error!!: {}", e);
249 },
250 Ok(docs) => {
251 if docs.len() != 1 {
252 bail!("Didn't find rules!");
253 }
254 return build_fn(&docs[0]);
255 }
256 }
257}
258
259pub fn process_include<F>(current_file: &Path, new_file_name: &str, mut read_new_file: F) -> Result<Vec<PathBuf>>
260 where F: FnMut(&Path) -> Result<Vec<PathBuf>> {
261 let parent_path = current_file.parent();
262 if parent_path.is_none() {
263 bail!("Internal error: {:?} is not a valid file name", current_file);
264 }
265 let mut new_file = match canonicalize_shim(parent_path.unwrap()) {
266 Ok(path) => path,
267 Err(e) => bail!("process_include: canonicalize failed for {} with message {}", parent_path.unwrap().display(), e.to_string()),
268 };
269
270 for unzip_dir in new_file.ancestors() {
272 if unzip_dir.ends_with("Rules") {
273 break; }
275 if unzip_dir.ends_with("Languages") || unzip_dir.ends_with("Braille") {
276 if let Some(subdir) = new_file.strip_prefix(unzip_dir).unwrap().iter().next() {
279 let default_lang = if unzip_dir.ends_with("Languages") {"en"} else {"UEB;"};
280 PreferenceManager::unzip_files(unzip_dir, subdir.to_str().unwrap(), Some(default_lang)).unwrap_or_default();
281 }
282 }
283 }
284 new_file.push(new_file_name);
285 info!("...processing include: {new_file_name}...");
286 let new_file = match crate::shim_filesystem::canonicalize_shim(new_file.as_path()) {
287 Ok(buf) => buf,
288 Err(msg) => bail!("-include: constructed file name '{}' causes error '{}'",
289 new_file.to_str().unwrap(), msg),
290 };
291
292 let mut included_files = read_new_file(new_file.as_path())?;
293 let mut files_read = vec![new_file];
294 files_read.append(&mut included_files);
295 return Ok(files_read);
296}
297
298pub trait TreeOrString<'c, 'm:'c, T> {
301 fn from_element(e: Element<'m>) -> Result<T>;
302 fn from_string(s: String, doc: Document<'m>) -> Result<T>;
303 fn replace_tts<'s:'c, 'r>(tts: &TTS, command: &TTSCommandRule, prefs: &PreferenceManager, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T>;
304 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T>;
305 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T>;
306 fn highlight_braille(braille: T, highlight_style: String) -> T;
307 fn mark_nav_speech(speech: T) -> T;
308}
309
310impl<'c, 'm:'c> TreeOrString<'c, 'm, String> for String {
311 fn from_element(_e: Element<'m>) -> Result<String> {
312 bail!("from_element not allowed for strings");
313 }
314
315 fn from_string(s: String, _doc: Document<'m>) -> Result<String> {
316 return Ok(s);
317 }
318
319 fn replace_tts<'s:'c, 'r>(tts: &TTS, command: &TTSCommandRule, prefs: &PreferenceManager, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
320 return tts.replace_string(command, prefs, rules_with_context, mathml);
321 }
322
323 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
324 return ra.replace_array_string(rules_with_context, mathml);
325 }
326
327 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
328 return rules.replace_nodes_string(nodes, mathml);
329 }
330
331 fn highlight_braille(braille: String, highlight_style: String) -> String {
332 return SpeechRulesWithContext::highlight_braille_string(braille, highlight_style);
333 }
334
335 fn mark_nav_speech(speech: String) -> String {
336 return SpeechRulesWithContext::mark_nav_speech(speech);
337 }
338}
339
340impl<'c, 'm:'c> TreeOrString<'c, 'm, Element<'m>> for Element<'m> {
341 fn from_element(e: Element<'m>) -> Result<Element<'m>> {
342 return Ok(e);
343 }
344
345 fn from_string(s: String, doc: Document<'m>) -> Result<Element<'m>> {
346 let leaf = create_mathml_element(&doc, "mi");
348 leaf.set_text(&s);
349 return Ok(leaf);
350}
351
352 fn replace_tts<'s:'c, 'r>(_tts: &TTS, _command: &TTSCommandRule, _prefs: &PreferenceManager, _rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, _mathml: Element<'c>) -> Result<Element<'m>> {
353 bail!("Internal error: applying a TTS rule to a tree");
354 }
355
356 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<Element<'m>> {
357 return ra.replace_array_tree(rules_with_context, mathml);
358 }
359
360 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<Element<'m>> {
361 return rules.replace_nodes_tree(nodes, mathml);
362 }
363
364 fn highlight_braille(_braille: Element<'c>, _highlight_style: String) -> Element<'m> {
365 panic!("Internal error: highlight_braille called on a tree");
366 }
367
368 fn mark_nav_speech(_speech: Element<'c>) -> Element<'m> {
369 panic!("Internal error: mark_nav_speech called on a tree");
370 }
371}
372
373#[derive(Debug, Clone)]
376#[allow(clippy::upper_case_acronyms)]
377enum Replacement {
378 Text(String),
380 XPath(MyXPath),
381 Intent(Box<Intent>),
382 Test(Box<TestArray>),
383 TTS(Box<TTSCommandRule>),
384 With(Box<With>),
385 SetVariables(Box<SetVariables>),
386 Insert(Box<InsertChildren>),
387 Translate(TranslateExpression),
388}
389
390impl fmt::Display for Replacement {
391 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
392 return write!(f, "{}",
393 match self {
394 Replacement::Test(c) => c.to_string(),
395 Replacement::Text(t) => format!("t: \"{t}\""),
396 Replacement::XPath(x) => x.to_string(),
397 Replacement::Intent(i) => i.to_string(),
398 Replacement::TTS(t) => t.to_string(),
399 Replacement::With(w) => w.to_string(),
400 Replacement::SetVariables(v) => v.to_string(),
401 Replacement::Insert(ic) => ic.to_string(),
402 Replacement::Translate(x) => x.to_string(),
403 }
404 );
405 }
406}
407
408impl Replacement {
409 fn build(replacement: &Yaml) -> Result<Replacement> {
410 let dictionary = replacement.as_hash();
412 if dictionary.is_none() {
413 bail!(" expected a key/value pair. Found {}.", yaml_to_string(replacement, 0));
414 };
415 let dictionary = dictionary.unwrap();
416 if dictionary.is_empty() {
417 bail!("No key/value pairs found for key 'replace'.\n\
418 Suggestion: are the following lines indented properly?");
419 }
420 if dictionary.len() > 1 {
421 bail!("Should only be one key/value pair for the replacement.\n \
422 Suggestion: are the following lines indented properly?\n \
423 The key/value pairs found are\n{}", yaml_to_string(replacement, 2));
424 }
425
426 let (key, value) = dictionary.iter().next().unwrap();
428 let key = key.as_str().ok_or("replacement key(e.g, 't') is not a string")?;
429 match key {
430 "t" | "T" => {
431 return Ok( Replacement::Text( as_str_checked(value)?.to_string() ) );
432 },
433 "ct" | "CT" => {
434 return Ok( Replacement::Text( CONCAT_INDICATOR.to_string() + as_str_checked(value)? ) );
435 },
436 "ot" | "OT" => {
437 return Ok( Replacement::Text( OPTIONAL_INDICATOR.to_string() + as_str_checked(value)? + OPTIONAL_INDICATOR ) );
438 },
439 "x" => {
440 return Ok( Replacement::XPath( MyXPath::build(value)
441 .chain_err(|| "while trying to evaluate value of 'x:'")? ) );
442 },
443 "pause" | "rate" | "pitch" | "volume" | "audio" | "gender" | "voice" | "spell" | "SPELL" | "bookmark" | "pronounce" | "PRONOUNCE" => {
444 return Ok( Replacement::TTS( TTS::build(&key.to_ascii_lowercase(), value)? ) );
445 },
446 "intent" => {
447 return Ok( Replacement::Intent( Intent::build(value)? ) );
448 },
449 "test" => {
450 return Ok( Replacement::Test( Box::new( TestArray::build(value)? ) ) );
451 },
452 "with" => {
453 return Ok( Replacement::With( With::build(value)? ) );
454 },
455 "set_variables" => {
456 return Ok( Replacement::SetVariables( SetVariables::build(value)? ) );
457 },
458 "insert" => {
459 return Ok( Replacement::Insert( InsertChildren::build(value)? ) );
460 },
461 "translate" => {
462 return Ok( Replacement::Translate( TranslateExpression::build(value)
463 .chain_err(|| "while trying to evaluate value of 'speak:'")? ) );
464 },
465 _ => {
466 bail!("Unknown 'replace' command ({}) with value: {}", key, yaml_to_string(value, 0));
467 }
468 }
469 }
470}
471
472#[derive(Debug, Clone)]
475struct InsertChildren {
476 xpath: MyXPath, replacements: ReplacementArray, }
479
480impl fmt::Display for InsertChildren {
481 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
482 return write!(f, "InsertChildren:\n nodes {}\n replacements {}", self.xpath, &self.replacements);
483 }
484}
485
486impl InsertChildren {
487 fn build(insert: &Yaml) -> Result<Box<InsertChildren>> {
488 if insert.as_hash().is_none() {
490 bail!("")
491 }
492 let nodes = &insert["nodes"];
493 if nodes.is_badvalue() {
494 bail!("Missing 'nodes' as part of 'insert'.\n \
495 Suggestion: add 'nodes:' or if present, indent so it is contained in 'insert'");
496 }
497 let nodes = as_str_checked(nodes)?;
498 let replace = &insert["replace"];
499 if replace.is_badvalue() {
500 bail!("Missing 'replace' as part of 'insert'.\n \
501 Suggestion: add 'replace:' or if present, indent so it is contained in 'insert'");
502 }
503 return Ok( Box::new( InsertChildren {
504 xpath: MyXPath::new(nodes.to_string())?,
505 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
506 } ) );
507 }
508
509 fn replace<'c, 's:'c, 'm: 'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
518 let result = self.xpath.evaluate(&rules_with_context.context_stack.base, mathml)
519 .chain_err(||format!("in '{}' replacing after pattern match", &self.xpath.rc.string) )?;
520 match result {
521 Value::Nodeset(nodes) => {
522 if nodes.size() == 0 {
523 bail!("During replacement, no matching element found");
524 };
525 let nodes = nodes.document_order();
526 let n_nodes = nodes.len();
527 let mut expanded_result = Vec::with_capacity(n_nodes + (n_nodes+1)*self.replacements.replacements.len());
528 expanded_result.push(
529 Replacement::XPath(
530 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , 1))?
531 )
532 );
533 for i in 2..n_nodes+1 {
534 expanded_result.extend_from_slice(&self.replacements.replacements);
535 expanded_result.push(
536 Replacement::XPath(
537 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , i))?
538 )
539 );
540 }
541 let replacements = ReplacementArray{ replacements: expanded_result };
542 return replacements.replace(rules_with_context, mathml);
543 },
544
545 Value::String(t) => { return T::from_string(rules_with_context.replace_chars(&t, mathml)?, rules_with_context.doc); },
547 Value::Number(num) => { return T::from_string( num.to_string(), rules_with_context.doc ); },
548 Value::Boolean(b) => { return T::from_string( b.to_string(), rules_with_context.doc ); }, }
550
551 }
552}
553
554
555lazy_static! {
556 static ref ATTR_NAME_VALUE: Regex = Regex::new(
557 r#"(?P<name>[^\s\u{0}-\u{40}\[\\\]^`\u{7B}-\u{BF}][^\s\u{0}-\u{2C}/:;<=>?@\[\\\]^`\u{7B}-\u{BF}]*)\s*=\s*('(?P<value>[^']+)'|"(?P<dqvalue>[^"]+)")"#
560 ).unwrap();
561}
562
563#[derive(Debug, Clone)]
566struct Intent {
567 name: Option<String>, xpath: Option<MyXPath>, attrs: String, children: ReplacementArray, }
572
573impl fmt::Display for Intent {
574 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
575 let name = if self.name.is_some() {
576 self.name.as_ref().unwrap().to_string()
577 } else {
578 self.xpath.as_ref().unwrap().to_string()
579 };
580 return write!(f, "intent: {}: {}, attrs='{}'>\n children: {}",
581 if self.name.is_some() {"name"} else {"xpath-name"}, name,
582 self.attrs,
583 &self.children);
584 }
585}
586
587impl Intent {
588 fn build(yaml_dict: &Yaml) -> Result<Box<Intent>> {
589 if yaml_dict.as_hash().is_none() {
591 bail!("Array found for contents of 'intent' -- should be dictionary with keys 'name' and 'children'")
592 }
593 let name = &yaml_dict["name"];
594 let xpath_name = &yaml_dict["xpath-name"];
595 if name.is_badvalue() && xpath_name.is_badvalue(){
596 bail!("Missing 'name' or 'xpath-name' as part of 'intent'.\n \
597 Suggestion: add 'name:' or if present, indent so it is contained in 'intent'");
598 }
599 let attrs = &yaml_dict["attrs"];
600 let replace = &yaml_dict["children"];
601 if replace.is_badvalue() {
602 bail!("Missing 'children' as part of 'intent'.\n \
603 Suggestion: add 'children:' or if present, indent so it is contained in 'intent'");
604 }
605 return Ok( Box::new( Intent {
606 name: if name.is_badvalue() {None} else {Some(as_str_checked(name).chain_err(|| "'name'")?.to_string())},
607 xpath: if xpath_name.is_badvalue() {None} else {Some(MyXPath::build(xpath_name).chain_err(|| "'intent'")?)},
608 attrs: if attrs.is_badvalue() {"".to_string()} else {as_str_checked(attrs).chain_err(|| "'attrs'")?.to_string()},
609 children: ReplacementArray::build(replace).chain_err(|| "'children:'")?,
610 } ) );
611 }
612
613 fn replace<'c, 's:'c, 'm: 'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
614 let result = self.children.replace::<Element<'m>>(rules_with_context, mathml)
615 .chain_err(||"replacing inside 'intent'")?;
616 let mut result = lift_children(result);
617 if name(result) != "TEMP_NAME" && name(result) != "Unknown" {
618 let temp = create_mathml_element(&result.document(), "TEMP_NAME");
620 temp.append_child(result);
621 result = temp;
622 }
623 if let Some(intent_name) = &self.name {
624 result.set_attribute_value(MATHML_FROM_NAME_ATTR, name(mathml));
625 set_mathml_name(result, intent_name.as_str());
626 }
627 if let Some(my_xpath) = &self.xpath{ let xpath_value = my_xpath.evaluate(rules_with_context.get_context(), mathml)?;
629 match xpath_value {
630 Value::String(intent_name) => {
631 result.set_attribute_value(MATHML_FROM_NAME_ATTR, name(mathml));
632 set_mathml_name(result, intent_name.as_str())
633 },
634 _ => bail!("'xpath-name' value '{}' was not a string", &my_xpath),
635 }
636 }
637 if self.name.is_none() && self.xpath.is_none() {
638 panic!("Intent::replace: internal error -- neither 'name' nor 'xpath' is set");
639 };
640
641 for attr in mathml.attributes() {
642 result.set_attribute_value(attr.name(), attr.value());
643 }
644
645 if !self.attrs.is_empty() {
646 for cap in ATTR_NAME_VALUE.captures_iter(&self.attrs) {
650 let matched_value = if cap["value"].is_empty() {&cap["dqvalue"]} else {&cap["value"]};
651 let value_as_xpath = MyXPath::new(matched_value.to_string()).chain_err(||"attr value inside 'intent'")?;
652 let value = value_as_xpath.evaluate(rules_with_context.get_context(), result)
653 .chain_err(||"attr xpath evaluation value inside 'intent'")?;
654 let mut value = value.into_string();
655 if &cap["name"] == INTENT_PROPERTY {
656 value = simplify_fixity_properties(&value);
657 }
658 if &cap["name"] == INTENT_PROPERTY && value == ":" {
660 result.remove_attribute(INTENT_PROPERTY);
662 } else {
663 result.set_attribute_value(&cap["name"], &value);
664 }
665 };
666 }
667
668 return T::from_element(result);
670
671
672 fn lift_children(result: Element) -> Element {
674 let mut new_children = Vec::with_capacity(2*result.children().len());
677 for child_of_element in result.children() {
678 match child_of_element {
679 ChildOfElement::Element(child) => {
680 if name(child) == "TEMP_NAME" {
681 new_children.append(&mut child.children()); } else {
683 new_children.push(child_of_element);
684 }
685 },
686 _ => new_children.push(child_of_element), }
688 }
689 result.replace_children(new_children);
690 return result;
691 }
692 }
693}
694
695#[derive(Debug, Clone)]
698struct With {
699 variables: VariableDefinitions, replacements: ReplacementArray, }
702
703impl fmt::Display for With {
704 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
705 return write!(f, "with:\n variables: {}\n replace: {}", &self.variables, &self.replacements);
706 }
707}
708
709impl With {
710 fn build(vars_replacements: &Yaml) -> Result<Box<With>> {
711 if vars_replacements.as_hash().is_none() {
713 bail!("Array found for contents of 'with' -- should be dictionary with keys 'variables' and 'replace'")
714 }
715 let var_defs = &vars_replacements["variables"];
716 if var_defs.is_badvalue() {
717 bail!("Missing 'variables' as part of 'with'.\n \
718 Suggestion: add 'variables:' or if present, indent so it is contained in 'with'");
719 }
720 let replace = &vars_replacements["replace"];
721 if replace.is_badvalue() {
722 bail!("Missing 'replace' as part of 'with'.\n \
723 Suggestion: add 'replace:' or if present, indent so it is contained in 'with'");
724 }
725 return Ok( Box::new( With {
726 variables: VariableDefinitions::build(var_defs).chain_err(|| "'variables'")?,
727 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
728 } ) );
729 }
730
731 fn replace<'c, 's:'c, 'm: 'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
732 rules_with_context.context_stack.push(self.variables.clone(), mathml)?;
733 let result = self.replacements.replace(rules_with_context, mathml)
734 .chain_err(||"replacing inside 'with'")?;
735 rules_with_context.context_stack.pop();
736 return Ok( result );
737 }
738}
739
740#[derive(Debug, Clone)]
743struct SetVariables {
744 variables: VariableDefinitions, }
746
747impl fmt::Display for SetVariables {
748 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
749 return write!(f, "SetVariables: variables {}", &self.variables);
750 }
751}
752
753impl SetVariables {
754 fn build(vars: &Yaml) -> Result<Box<SetVariables>> {
755 if vars.as_vec().is_none() {
757 bail!("'set_variables' -- should be an array of variable name, xpath value");
758 }
759 return Ok( Box::new( SetVariables {
760 variables: VariableDefinitions::build(vars).chain_err(|| "'set_variables'")?
761 } ) );
762 }
763
764 fn replace<'c, 's:'c, 'm: 'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
765 rules_with_context.context_stack.set_globals(self.variables.clone(), mathml)?;
766 return T::from_string( "".to_string(), rules_with_context.doc );
767 }
768}
769
770
771#[derive(Debug, Clone)]
773struct TranslateExpression {
774 id: MyXPath, }
776
777impl fmt::Display for TranslateExpression {
778 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
779 return write!(f, "speak: {}", &self.id);
780 }
781}
782impl TranslateExpression {
783 fn build(vars: &Yaml) -> Result<TranslateExpression> {
784 return Ok( TranslateExpression { id: MyXPath::build(vars).chain_err(|| "'translate'")? } );
786 }
787
788 fn replace<'c, 's:'c, 'm:'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
789 if self.id.rc.string.contains('@') {
790 let xpath_value = self.id.evaluate(rules_with_context.get_context(), mathml)?;
791 let id = match xpath_value {
792 Value::String(s) => Some(s),
793 Value::Nodeset(nodes) => {
794 if nodes.size() == 1 {
795 nodes.document_order_first().unwrap().attribute().map(|attr| attr.value().to_string())
796 } else {
797 None
798 }
799 },
800 _ => None,
801 };
802 match id {
803 None => bail!("'translate' value '{}' is not a string or an attribute value (correct by using '@id'??):\n", self.id),
804 Some(id) => {
805 let speech = speak_mathml(mathml, &id)?;
806 return T::from_string(speech, rules_with_context.doc);
807 }
808 }
809 } else {
810 return T::from_string(
811 self.id.replace(rules_with_context, mathml).chain_err(||"'translate'")?,
812 rules_with_context.doc
813 );
814 }
815 }
816}
817
818
819#[derive(Debug, Clone)]
821pub struct ReplacementArray {
822 replacements: Vec<Replacement>
823}
824
825impl fmt::Display for ReplacementArray {
826 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
827 return write!(f, "{}", self.pretty_print_replacements());
828 }
829}
830
831impl ReplacementArray {
832 pub fn build_empty() -> ReplacementArray {
834 return ReplacementArray {
835 replacements: vec![]
836 }
837 }
838
839 pub fn build(replacements: &Yaml) -> Result<ReplacementArray> {
842 let result= if replacements.is_array() {
844 let replacements = replacements.as_vec().unwrap();
845 replacements
846 .iter()
847 .enumerate() .map(|(i, r)| Replacement::build(r)
849 .chain_err(|| format!("replacement #{} of {}", i+1, replacements.len())))
850 .collect::<Result<Vec<Replacement>>>()?
851 } else {
852 vec![ Replacement::build(replacements)?]
853 };
854
855 return Ok( ReplacementArray{ replacements: result } );
856 }
857
858 pub fn replace<'c, 's:'c, 'm:'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
860 return T::replace(self, rules_with_context, mathml);
861 }
862
863 pub fn replace_array_string<'c, 's:'c, 'm:'c>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
864 let mut replacement_strings = Vec::with_capacity(self.replacements.len()); for replacement in self.replacements.iter() {
870 let string: String = rules_with_context.replace(replacement, mathml)?;
871 if !string.is_empty() {
872 replacement_strings.push(string);
873 }
874 }
875
876 if replacement_strings.is_empty() {
877 return Ok( "".to_string() );
878 }
879 for i in 1..replacement_strings.len()-1 {
887 if let Some(bytes) = is_repetitive(&replacement_strings[i-1], &replacement_strings[i]) {
888 replacement_strings[i] = bytes.to_string();
889 }
890 }
891
892 for i in 0..replacement_strings.len() {
893 if replacement_strings[i].contains(PAUSE_AUTO_STR) {
894 let before = if i == 0 {""} else {&replacement_strings[i-1]};
895 let after = if i+1 == replacement_strings.len() {""} else {&replacement_strings[i+1]};
896 replacement_strings[i] = replacement_strings[i].replace(
897 PAUSE_AUTO_STR,
898 &rules_with_context.speech_rules.pref_manager.borrow().get_tts().compute_auto_pause(&rules_with_context.speech_rules.pref_manager.borrow(), before, after));
899 }
900 }
901
902 return Ok( replacement_strings.join(" ") );
905
906 fn is_repetitive<'a>(prev: &str, optional: &'a str) -> Option<&'a str> {
907 if optional.len() <= 2 * OPTIONAL_INDICATOR_LEN {
910 return None;
911 }
912
913 match optional.find(OPTIONAL_INDICATOR) {
915 None => return None,
916 Some(start_index) => {
917 let optional_word_start_slice = &optional[start_index + OPTIONAL_INDICATOR_LEN..];
918 match optional_word_start_slice.find(OPTIONAL_INDICATOR) {
920 None => panic!("Internal error: missing end optional char -- text handling is corrupted!"),
921 Some(end_index) => {
922 let optional_word = &optional_word_start_slice[..end_index];
923 let prev = prev.trim_end().as_bytes();
926 if prev.len() > optional_word.len() &&
927 &prev[prev.len()-optional_word.len()..] == optional_word.as_bytes() {
928 return Some( optional_word_start_slice[optional_word.len() + OPTIONAL_INDICATOR_LEN..].trim_start() );
929 } else {
930 return None;
931 }
932 }
933 }
934 }
935 }
936 }
937 }
938
939 pub fn replace_array_tree<'c, 's:'c, 'm:'c>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<Element<'m>> {
940 if self.replacements.len() == 1 {
942 return rules_with_context.replace::<Element<'m>>(&self.replacements[0], mathml);
943 }
944
945 let new_element = create_mathml_element(&rules_with_context.doc, "Unknown"); let mut new_children = Vec::with_capacity(self.replacements.len());
947 for child in self.replacements.iter() {
948 let child = rules_with_context.replace::<Element<'m>>(child, mathml)?;
949 new_children.push(ChildOfElement::Element(child));
950 };
951 new_element.append_children(new_children);
952 return Ok(new_element);
953 }
954
955
956 pub fn is_empty(&self) -> bool {
958 return self.replacements.is_empty();
959 }
960
961 fn pretty_print_replacements(&self) -> String {
962 let mut group_string = String::with_capacity(128);
963 if self.replacements.len() == 1 {
964 group_string += &format!("[{}]", self.replacements[0]);
965 } else {
966 group_string += &self.replacements.iter()
967 .map(|replacement| format!("\n - {replacement}"))
968 .collect::<Vec<String>>()
969 .join("");
970 group_string += "\n";
971 }
972 return group_string;
973 }
974}
975
976
977
978#[derive(Debug)]
982struct RCMyXPath {
983 xpath: XPath,
984 string: String, }
986
987#[derive(Debug, Clone)]
988pub struct MyXPath {
989 rc: Rc<RCMyXPath> }
991
992
993impl fmt::Display for MyXPath {
994 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
995 return write!(f, "\"{}\"", self.rc.string);
996 }
997}
998
999thread_local!{
1003 static XPATH_CACHE: RefCell<HashMap<String, MyXPath>> = RefCell::new( HashMap::with_capacity(2047) );
1004}
1005impl MyXPath {
1008 fn new(xpath: String) -> Result<MyXPath> {
1009 return XPATH_CACHE.with( |cache| {
1010 let mut cache = cache.borrow_mut();
1011 return Ok(
1012 match cache.get(&xpath) {
1013 Some(compiled_xpath) => {
1014 compiled_xpath.clone()
1016 },
1017 None => {
1018 let new_xpath = MyXPath {
1019 rc: Rc::new( RCMyXPath {
1020 xpath: MyXPath::compile_xpath(&xpath)?,
1021 string: xpath.clone()
1022 })};
1023 cache.insert(xpath.clone(), new_xpath.clone());
1024 new_xpath
1025 },
1026 }
1027 )
1028 });
1029 }
1030
1031 pub fn build(xpath: &Yaml) -> Result<MyXPath> {
1032 let xpath = match xpath {
1033 Yaml::String(s) => s.to_string(),
1034 Yaml::Integer(i) => i.to_string(),
1035 Yaml::Real(s) => s.to_string(),
1036 Yaml::Boolean(s) => s.to_string(),
1037 Yaml::Array(v) =>
1038 v.iter()
1040 .map(as_str_checked)
1041 .collect::<Result<Vec<&str>>>()?
1042 .join(" "),
1043 _ => bail!("Bad value when trying to create an xpath: {}", yaml_to_string(xpath, 1)),
1044 };
1045 return MyXPath::new(xpath);
1046 }
1047
1048 fn compile_xpath(xpath: &str) -> Result<XPath> {
1049 let factory = Factory::new();
1050 let xpath_with_debug_info = MyXPath::add_debug_string_arg(xpath)?;
1051 let compiled_xpath = factory.build(&xpath_with_debug_info)
1052 .chain_err(|| format!(
1053 "Could not compile XPath for pattern:\n{}{}",
1054 &xpath, more_details(xpath)))?;
1055 return match compiled_xpath {
1056 Some(xpath) => Ok(xpath),
1057 None => bail!("Problem compiling Xpath for pattern:\n{}{}",
1058 &xpath, more_details(xpath)),
1059 };
1060
1061
1062 fn more_details(xpath: &str) -> String {
1063 if xpath.is_empty() {
1065 return "xpath is empty string".to_string();
1066 }
1067 let as_bytes = xpath.trim().as_bytes();
1068 if as_bytes[0] == b'\'' && as_bytes[as_bytes.len()-1] != b'\'' {
1069 return "\nmissing \"'\"".to_string();
1070 }
1071 if (as_bytes[0] == b'"' && as_bytes[as_bytes.len()-1] != b'"') ||
1072 (as_bytes[0] != b'"' && as_bytes[as_bytes.len()-1] == b'"'){
1073 return "\nmissing '\"'".to_string();
1074 }
1075
1076 let mut i_bytes = 0; let mut paren_count = 0; let mut i_paren = 0; let mut bracket_count = 0;
1080 let mut i_bracket = 0;
1081 for ch in xpath.chars() {
1082 if ch == '(' {
1083 if paren_count == 0 {
1084 i_paren = i_bytes;
1085 }
1086 paren_count += 1;
1087 } else if ch == '[' {
1088 if bracket_count == 0 {
1089 i_bracket = i_bytes;
1090 }
1091 bracket_count += 1;
1092 } else if ch == ')' {
1093 if paren_count == 0 {
1094 return format!("\nExtra ')' found after '{}'", &xpath[i_paren..i_bytes]);
1095 }
1096 paren_count -= 1;
1097 if paren_count == 0 && bracket_count > 0 && i_bracket > i_paren {
1098 return format!("\nUnclosed brackets found at '{}'", &xpath[i_paren..i_bytes]);
1099 }
1100 } else if ch == ']' {
1101 if bracket_count == 0 {
1102 return format!("\nExtra ']' found after '{}'", &xpath[i_bracket..i_bytes]);
1103 }
1104 bracket_count -= 1;
1105 if bracket_count == 0 && paren_count > 0 && i_paren > i_bracket {
1106 return format!("\nUnclosed parens found at '{}'", &xpath[i_bracket..i_bytes]);
1107 }
1108 }
1109 i_bytes += ch.len_utf8();
1110 }
1111 return "".to_string();
1112 }
1113 }
1114
1115 fn add_debug_string_arg(xpath: &str) -> Result<String> {
1117 let debug_start = xpath.find("DEBUG(");
1119 if debug_start.is_none() {
1120 return Ok( xpath.to_string() );
1121 }
1122
1123 let debug_start = debug_start.unwrap();
1124 let mut before_paren = xpath[..debug_start+5].to_string(); let chars = xpath[debug_start+5..].chars().collect::<Vec<char>>(); before_paren.push_str(&chars_add_debug_string_arg(&chars).chain_err(|| format!("In xpath='{xpath}'"))?);
1127 return Ok(before_paren);
1129
1130 fn chars_add_debug_string_arg(chars: &[char]) -> Result<String> {
1131 assert_eq!(chars[0], '(', "{} does not start with ')'", chars.iter().collect::<String>());
1136 let mut count = 1; let mut i = 1;
1138 let mut inside_quote = false;
1139 while i < chars.len() {
1140 let ch = chars[i];
1141 match ch {
1142 '\\' => {
1143 if i+1 == chars.len() {
1144 bail!("Syntax error in DEBUG: last char is escape char\n{}");
1145 }
1146 i += 1;
1147 },
1148 '\'' => inside_quote = !inside_quote,
1149 '(' => {
1150 if !inside_quote {
1151 count += 1;
1152 }
1153 },
1155 ')' => {
1156 if !inside_quote {
1157 count -= 1;
1158 if count == 0 {
1159 let arg = &chars[1..i].iter().collect::<String>();
1160 let escaped_arg = arg.replace('"', "\\\"");
1161 let processed_arg = MyXPath::add_debug_string_arg(arg)?;
1163
1164 let processed_rest = MyXPath::add_debug_string_arg(&chars[i+1..].iter().collect::<String>())?;
1166 return Ok( format!("({processed_arg}, \"{escaped_arg}\"){processed_rest}") );
1167 }
1168 }
1169 },
1170 _ => (),
1171 }
1172 i += 1;
1173 }
1174 bail!("Syntax error in DEBUG: didn't find matching closing paren\nDEBUG{}", chars.iter().collect::<String>());
1175 }
1176 }
1177
1178 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1179 return Ok(
1181 match self.evaluate(context, mathml)? {
1182 Value::Boolean(b) => b,
1183 Value::Nodeset(nodes) => nodes.size() > 0,
1184 _ => false,
1185 }
1186 )
1187 }
1188
1189 pub fn replace<'c, 's:'c, 'm:'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
1190 if self.rc.string == "process-intent(.)" {
1191 return T::from_element( infer_intent(rules_with_context, mathml)? );
1192 }
1193
1194 let result = self.evaluate(&rules_with_context.context_stack.base, mathml)
1195 .chain_err(|| format!("in '{}' replacing after pattern match", &self.rc.string) )?;
1196 let string = match result {
1197 Value::Nodeset(nodes) => {
1198 if nodes.size() == 0 {
1199 bail!("During replacement, no matching element found");
1200 }
1201 return rules_with_context.replace_nodes(nodes.document_order(), mathml);
1202 },
1203 Value::String(s) => s,
1204 Value::Number(num) => num.to_string(),
1205 Value::Boolean(b) => b.to_string(), };
1207 let result = if self.rc.string.starts_with('$') {string} else {rules_with_context.replace_chars(&string, mathml)?};
1210 return T::from_string(result, rules_with_context.doc );
1211 }
1212
1213 pub fn evaluate<'c>(&self, context: &Context<'c>, mathml: Element<'c>) -> Result<Value<'c>> {
1214 let result = self.rc.xpath.evaluate(context, mathml);
1216 return match result {
1217 Ok(val) => Ok( val ),
1218 Err(e) => {
1219 bail!( "{}\n\n",
1221 e.to_string().replace("OwnedPrefixedName { prefix: None, local_part:", "").replace(" }", "") );
1223 }
1224 };
1225 }
1226
1227 pub fn test_input<F>(self, f: F) -> bool where F: Fn(&str) -> bool {
1228 return f(self.rc.string.as_ref());
1229 }
1230}
1231
1232#[derive(Debug)]
1237struct SpeechPattern {
1238 pattern_name: String,
1239 tag_name: String,
1240 file_name: String,
1241 pattern: MyXPath, match_uses_var_defs: bool, var_defs: VariableDefinitions, replacements: ReplacementArray, }
1246
1247impl fmt::Display for SpeechPattern {
1248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1249 return write!(f, "[name: {}, tag: {},\n variables: {:?}, pattern: {},\n replacement: {}]",
1250 self.pattern_name, self.tag_name, self.var_defs, self.pattern,
1251 self.replacements.pretty_print_replacements());
1252 }
1253}
1254
1255impl SpeechPattern {
1256 fn build(dict: &Yaml, file: &Path, rules: &mut SpeechRules) -> Result<Option<Vec<PathBuf>>> {
1257 if let Some(include_file_name) = find_str(dict, "include") {
1263 let do_include_fn = |new_file: &Path| {
1264 rules.read_patterns(new_file)
1265 };
1266
1267 return Ok( Some(process_include(file, include_file_name, do_include_fn)?) );
1268 }
1269
1270 let pattern_name = find_str(dict, "name");
1271
1272 let mut tag_names: Vec<&str> = Vec::new();
1274 match find_str(dict, "tag") {
1275 Some(str) => tag_names.push(str),
1276 None => {
1277 let tag_array = &dict["tag"];
1279 tag_names = vec![];
1280 if tag_array.is_array() {
1281 for (i, name) in tag_array.as_vec().unwrap().iter().enumerate() {
1282 match as_str_checked(name) {
1283 Err(e) => return Err(
1284 e.chain_err(||
1285 format!("tag name '{}' is not a string in:\n{}",
1286 &yaml_to_string(&tag_array.as_vec().unwrap()[i], 0),
1287 &yaml_to_string(dict, 1)))
1288 ),
1289 Ok(str) => tag_names.push(str),
1290 };
1291 }
1292 } else {
1293 bail!("Errors trying to find 'tag' in:\n{}", &yaml_to_string(dict, 1));
1294 }
1295 }
1296 }
1297
1298 if pattern_name.is_none() {
1299 if dict.is_null() {
1300 bail!("Error trying to find 'name': empty value (two consecutive '-'s?");
1301 } else {
1302 bail!("Errors trying to find 'name' in:\n{}", &yaml_to_string(dict, 1));
1303 };
1304 };
1305 let pattern_name = pattern_name.unwrap().to_string();
1306
1307 if dict["match"].is_badvalue() {
1309 bail!("Did not find 'match' in\n{}", yaml_to_string(dict, 1));
1310 }
1311 if dict["replace"].is_badvalue() {
1312 bail!("Did not find 'replace' in\n{}", yaml_to_string(dict, 1));
1313 }
1314
1315 for tag_name in tag_names {
1317 let tag_name = tag_name.to_string();
1318 let pattern_xpath = MyXPath::build(&dict["match"])
1319 .chain_err(|| {
1320 format!("value for 'match' in rule ({}: {}):\n{}",
1321 tag_name, pattern_name, yaml_to_string(dict, 1))
1322 })?;
1323 let speech_pattern =
1324 Box::new( SpeechPattern{
1325 pattern_name: pattern_name.clone(),
1326 tag_name: tag_name.clone(),
1327 file_name: file.to_str().unwrap().to_string(),
1328 match_uses_var_defs: dict["variables"].is_array() && pattern_xpath.rc.string.contains('$'), pattern: pattern_xpath,
1330 var_defs: VariableDefinitions::build(&dict["variables"])
1331 .chain_err(|| {
1332 format!("value for 'variables' in rule ({}: {}):\n{}",
1333 tag_name, pattern_name, yaml_to_string(dict, 1))
1334 })?,
1335 replacements: ReplacementArray::build(&dict["replace"])
1336 .chain_err(|| {
1337 format!("value for 'replace' in rule ({}: {}). Replacements:\n{}",
1338 tag_name, pattern_name, yaml_to_string(&dict["replace"], 1))
1339 })?
1340 } );
1341 let rule_value = rules.rules.entry(tag_name).or_default();
1343
1344 match rule_value.iter().enumerate().find(|&pattern| pattern.1.pattern_name == speech_pattern.pattern_name) {
1346 None => rule_value.push(speech_pattern),
1347 Some((i, _old_pattern)) => {
1348 let old_rule = &rule_value[i];
1349 info!("\n\n***WARNING***: replacing {}/'{}' in {} with rule from {}\n",
1350 old_rule.tag_name, old_rule.pattern_name, old_rule.file_name, speech_pattern.file_name);
1351 rule_value[i] = speech_pattern;
1352 },
1353 }
1354 }
1355
1356 return Ok(None);
1357 }
1358
1359 fn is_match(&self, context: &Context, mathml: Element) -> Result<bool> {
1360 if self.tag_name != mathml.name().local_part() && self.tag_name != "*" && self.tag_name != "!*" {
1361 return Ok( false );
1362 }
1363
1364 return Ok(
1368 match self.pattern.evaluate(context, mathml)? {
1369 Value::Boolean(b) => b,
1370 Value::Nodeset(nodes) => nodes.size() > 0,
1371 _ => false,
1372 }
1373 );
1374 }
1375}
1376
1377
1378#[derive(Debug, Clone)]
1382struct TestArray {
1383 tests: Vec<Test>
1384}
1385
1386impl fmt::Display for TestArray {
1387 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1388 for test in &self.tests {
1389 writeln!(f, "{test}")?;
1390 }
1391 return Ok( () );
1392 }
1393}
1394
1395impl TestArray {
1396 fn build(test: &Yaml) -> Result<TestArray> {
1397 let tests = if test.as_hash().is_some() {
1402 vec![test]
1403 } else if let Some(vec) = test.as_vec() {
1404 vec.iter().collect()
1405 } else {
1406 bail!("Value for 'test:' is neither a dictionary or an array.")
1407 };
1408
1409 let mut test_array = vec![];
1415 for test in tests {
1416 if test.as_hash().is_none() {
1417 bail!("Value for array entry in 'test:' must be a dictionary/contain keys");
1418 }
1419 let if_part = &test[if test_array.is_empty() {"if"} else {"else_if"}];
1420 if !if_part.is_badvalue() {
1421 let condition = Some( MyXPath::build(if_part)? );
1423 let then_part = TestOrReplacements::build(test, "then", "then_test", true)?;
1424 let else_part = TestOrReplacements::build(test, "else", "else_test", false)?;
1425 let n_keys = if else_part.is_none() {2} else {3};
1426 if test.as_hash().unwrap().len() > n_keys {
1427 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found in the 'then' clause of 'test'");
1428 };
1429 test_array.push(
1430 Test { condition, then_part, else_part }
1431 );
1432 } else {
1433 let else_part = TestOrReplacements::build(test, "else", "else_test", true)?;
1435 if test.as_hash().unwrap().len() > 1 {
1436 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found the 'else' clause of 'test'");
1437 };
1438 test_array.push(
1439 Test { condition: None, then_part: None, else_part }
1440 );
1441
1442 if test_array.len() < test.as_hash().unwrap().len() {
1444 bail!("'else'/'else_test' key is not last key in 'test:'");
1445 }
1446 }
1447 };
1448
1449 if test_array.is_empty() {
1450 bail!("No entries for 'test:'");
1451 }
1452
1453 return Ok( TestArray { tests: test_array } );
1454 }
1455
1456 fn replace<'c, 's:'c, 'm:'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
1457 for test in &self.tests {
1458 if test.is_true(&rules_with_context.context_stack.base, mathml)? {
1459 assert!(test.then_part.is_some());
1460 return test.then_part.as_ref().unwrap().replace(rules_with_context, mathml);
1461 } else if let Some(else_part) = test.else_part.as_ref() {
1462 return else_part.replace(rules_with_context, mathml);
1463 }
1464 }
1465 return T::from_string("".to_string(), rules_with_context.doc);
1466 }
1467}
1468
1469#[derive(Debug, Clone)]
1470enum TestOrReplacements {
1472 Replacements(ReplacementArray), Test(TestArray), }
1475
1476impl fmt::Display for TestOrReplacements {
1477 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1478 if let TestOrReplacements::Test(_) = self {
1479 write!(f, " _test")?;
1480 }
1481 write!(f, ":")?;
1482 return match self {
1483 TestOrReplacements::Test(t) => write!(f, "{t}"),
1484 TestOrReplacements::Replacements(r) => write!(f, "{r}"),
1485 };
1486 }
1487}
1488
1489impl TestOrReplacements {
1490 fn build(test: &Yaml, replace_key: &str, test_key: &str, key_required: bool) -> Result<Option<TestOrReplacements>> {
1491 let part = &test[replace_key];
1492 let test_part = &test[test_key];
1493 if !part.is_badvalue() && !test_part.is_badvalue() {
1494 bail!(format!("Only one of '{}' or '{}' is allowed as part of 'test'.\n{}\n \
1495 Suggestion: delete one or adjust indentation",
1496 replace_key, test_key, yaml_to_string(test, 2)));
1497 }
1498 if part.is_badvalue() && test_part.is_badvalue() {
1499 if key_required {
1500 bail!(format!("Missing one of '{}'/'{}:' as part of 'test:'\n{}\n \
1501 Suggestion: add the missing key or indent so it is contained in 'test'",
1502 replace_key, test_key, yaml_to_string(test, 2)))
1503 } else {
1504 return Ok( None );
1505 }
1506 }
1507 if test_part.is_badvalue() {
1509 return Ok( Some( TestOrReplacements::Replacements( ReplacementArray::build(part)? ) ) );
1510 } else {
1511 return Ok( Some( TestOrReplacements::Test( TestArray::build(test_part)? ) ) );
1512 }
1513 }
1514
1515 fn replace<'c, 's:'c, 'm:'c, T:TreeOrString<'c, 'm, T>>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T> {
1516 return match self {
1517 TestOrReplacements::Replacements(r) => r.replace(rules_with_context, mathml),
1518 TestOrReplacements::Test(t) => t.replace(rules_with_context, mathml),
1519 }
1520 }
1521}
1522
1523#[derive(Debug, Clone)]
1524struct Test {
1525 condition: Option<MyXPath>,
1526 then_part: Option<TestOrReplacements>,
1527 else_part: Option<TestOrReplacements>,
1528}
1529impl fmt::Display for Test {
1530 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1531 write!(f, "test: [ ")?;
1532 if let Some(if_part) = &self.condition {
1533 write!(f, " if: '{if_part}'")?;
1534 }
1535 if let Some(then_part) = &self.then_part {
1536 write!(f, " then{then_part}")?;
1537 }
1538 if let Some(else_part) = &self.else_part {
1539 write!(f, " else{else_part}")?;
1540 }
1541 return write!(f, "]");
1542 }
1543}
1544
1545impl Test {
1546 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1547 return match self.condition.as_ref() {
1548 None => Ok( false ), Some(condition) => condition.is_true(context, mathml)
1550 .chain_err(|| "Failure in conditional test"),
1551 }
1552 }
1553}
1554
1555#[derive(Debug, Clone)]
1557struct VariableDefinition {
1558 name: String, value: MyXPath, }
1561
1562impl fmt::Display for VariableDefinition {
1563 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1564 return write!(f, "[name: {}={}]", self.name, self.value);
1565 }
1566}
1567
1568#[derive(Debug)]
1570struct VariableValue<'v> {
1571 name: String, value: Option<Value<'v>>, }
1574
1575impl fmt::Display for VariableValue<'_> {
1576 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1577 let value = match &self.value {
1578 None => "unset".to_string(),
1579 Some(val) => format!("{val:?}")
1580 };
1581 return write!(f, "[name: {}, value: {}]", self.name, value);
1582 }
1583}
1584
1585impl VariableDefinition {
1586 fn build(name_value_def: &Yaml) -> Result<VariableDefinition> {
1587 match name_value_def.as_hash() {
1588 Some(map) => {
1589 if map.len() != 1 {
1590 bail!("definition is not a key/value pair. Found {}",
1591 yaml_to_string(name_value_def, 1) );
1592 }
1593 let (name, value) = map.iter().next().unwrap();
1594 let name = as_str_checked( name)
1595 .chain_err(|| format!( "definition name is not a string: {}",
1596 yaml_to_string(name, 1) ))?.to_string();
1597 match value {
1598 Yaml::Boolean(_) | Yaml::String(_) | Yaml::Integer(_) | Yaml::Real(_) => (),
1599 _ => bail!("definition value is not a string, boolean, or number. Found {}",
1600 yaml_to_string(value, 1) )
1601 };
1602 return Ok(
1603 VariableDefinition{
1604 name,
1605 value: MyXPath::build(value)?
1606 }
1607 );
1608 },
1609 None => bail!("definition is not a key/value pair. Found {}",
1610 yaml_to_string(name_value_def, 1) )
1611 }
1612 }
1613}
1614
1615
1616#[derive(Debug, Clone)]
1617struct VariableDefinitions {
1618 defs: Vec<VariableDefinition>
1619}
1620
1621impl fmt::Display for VariableDefinitions {
1622 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1623 for def in &self.defs {
1624 write!(f, "{def},")?;
1625 }
1626 return Ok( () );
1627 }
1628}
1629
1630struct VariableValues<'v> {
1631 defs: Vec<VariableValue<'v>>
1632}
1633
1634impl fmt::Display for VariableValues<'_> {
1635 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1636 for value in &self.defs {
1637 write!(f, "{value}")?;
1638 }
1639 return writeln!(f);
1640 }
1641}
1642
1643impl VariableDefinitions {
1644 fn new(len: usize) -> VariableDefinitions {
1645 return VariableDefinitions{ defs: Vec::with_capacity(len) };
1646 }
1647
1648 fn build(defs: &Yaml) -> Result<VariableDefinitions> {
1649 if defs.is_badvalue() {
1650 return Ok( VariableDefinitions::new(0) );
1651 };
1652 if defs.is_array() {
1653 let defs = defs.as_vec().unwrap();
1654 let mut definitions = VariableDefinitions::new(defs.len());
1655 for def in defs {
1656 let variable_def = VariableDefinition::build(def)
1657 .chain_err(|| "definition of 'variables'")?;
1658 definitions.push( variable_def);
1659 };
1660 return Ok (definitions );
1661 }
1662 bail!( "'variables' is not an array of {{name: xpath-value}} definitions. Found {}'",
1663 yaml_to_string(defs, 1) );
1664 }
1665
1666 fn push(&mut self, var_def: VariableDefinition) {
1667 self.defs.push(var_def);
1668 }
1669
1670 fn len(&self) -> usize {
1671 return self.defs.len();
1672 }
1673}
1674
1675struct ContextStack<'c> {
1676 old_values: Vec<VariableValues<'c>>, base: Context<'c> }
1680
1681impl fmt::Display for ContextStack<'_> {
1682 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1683 writeln!(f, " {} old_values", self.old_values.len())?;
1684 for values in &self.old_values {
1685 writeln!(f, " {values}")?;
1686 }
1687 return writeln!(f);
1688 }
1689}
1690
1691impl<'c, 'r> ContextStack<'c> {
1692 fn new<'a,>(pref_manager: &'a PreferenceManager) -> ContextStack<'c> {
1693 let prefs = pref_manager.merge_prefs();
1694 let mut context_stack = ContextStack {
1695 base: ContextStack::base_context(prefs),
1696 old_values: Vec::with_capacity(31) };
1698 context_stack.base.set_variable("MatchingPause", Value::Boolean(false));
1701 context_stack.base.set_variable("IsColumnSilent", Value::Boolean(false));
1702
1703
1704 return context_stack;
1705 }
1706
1707 fn base_context(var_defs: PreferenceHashMap) -> Context<'c> {
1708 let mut context = Context::new();
1709 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
1710 crate::xpath_functions::add_builtin_functions(&mut context);
1711 for (key, value) in var_defs {
1712 context.set_variable(key.as_str(), yaml_to_value(&value));
1713 };
1719 return context;
1720 }
1721
1722 fn set_globals(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1723 for def in &new_vars.defs {
1725 let new_value = match def.value.evaluate(&self.base, mathml) {
1727 Ok(val) => val,
1728 Err(_) => bail!(format!("Can't evaluate variable def for {}", def)),
1729 };
1730 let qname = QName::new(def.name.as_str());
1731 self.base.set_variable(qname, new_value);
1732 }
1733 return Ok( () );
1734 }
1735
1736 fn push(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1737 let mut old_values = VariableValues {defs: Vec::with_capacity(new_vars.defs.len()) };
1739 let evaluation = Evaluation::new(&self.base, Node::Element(mathml));
1740 for def in &new_vars.defs {
1741 let qname = QName::new(def.name.as_str());
1743 let old_value = evaluation.value_of(qname).cloned();
1744 old_values.defs.push( VariableValue{ name: def.name.clone(), value: old_value} );
1745 }
1746
1747 for def in &new_vars.defs {
1749 let new_value = match def.value.evaluate(&self.base, mathml) {
1751 Ok(val) => val,
1752 Err(_) => bail!(format!("Can't evaluate variable def for {} with ContextStack {}", def, self)),
1753 };
1754 let qname = QName::new(def.name.as_str());
1755 self.base.set_variable(qname, new_value);
1756 }
1757 self.old_values.push(old_values);
1758 return Ok( () );
1759 }
1760
1761 fn pop(&mut self) {
1762 const MISSING_VALUE: &str = "-- unset value --"; let old_values = self.old_values.pop().unwrap();
1764 for variable in old_values.defs {
1765 let qname = QName::new(&variable.name);
1766 let old_value = match variable.value {
1767 None => Value::String(MISSING_VALUE.to_string()),
1768 Some(val) => val,
1769 };
1770 self.base.set_variable(qname, old_value);
1771 }
1772 }
1773}
1774
1775
1776fn yaml_to_value<'b>(yaml: &Yaml) -> Value<'b> {
1777 return match yaml {
1778 Yaml::String(s) => Value::String(s.clone()),
1779 Yaml::Boolean(b) => Value::Boolean(*b),
1780 Yaml::Integer(i) => Value::Number(*i as f64),
1781 Yaml::Real(s) => Value::Number(s.parse::<f64>().unwrap()),
1782 _ => {
1783 error!("yaml_to_value: illegal type found in Yaml value: {}", yaml_to_string(yaml, 1));
1784 Value::String("".to_string())
1785 },
1786 }
1787}
1788
1789
1790struct UnicodeDef {
1792 ch: u32,
1793 speech: ReplacementArray
1794}
1795
1796impl fmt::Display for UnicodeDef {
1797 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1798 return write!(f, "UnicodeDef{{ch: {}, speech: {:?}}}", self.ch, self.speech);
1799 }
1800}
1801
1802impl UnicodeDef {
1803 fn build(unicode_def: &Yaml, file_name: &Path, speech_rules: &SpeechRules, use_short: bool) -> Result<Option<Vec<PathBuf>>> {
1804 if let Some(include_file_name) = find_str(unicode_def, "include") {
1805 let do_include_fn = |new_file: &Path| {
1806 speech_rules.read_unicode(Some(new_file.to_path_buf()), use_short)
1807 };
1808 return Ok( Some(process_include(file_name, include_file_name, do_include_fn)?) );
1809 }
1810 let dictionary = unicode_def.as_hash();
1812 if dictionary.is_none() {
1813 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1814 }
1815
1816 let dictionary = dictionary.unwrap();
1817 if dictionary.len() != 1 {
1818 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1819 }
1820
1821 let (ch, replacements) = dictionary.iter().next().ok_or_else(|| format!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0)))?;
1822 let mut unicode_table = if use_short {
1823 speech_rules.unicode_short.borrow_mut()
1824 } else {
1825 speech_rules.unicode_full.borrow_mut()
1826 };
1827 if let Some(str) = ch.as_str() {
1828 if str.is_empty() {
1829 bail!("Empty character definition. Replacement is {}", replacements.as_str().unwrap());
1830 }
1831 let mut chars = str.chars();
1832 let first_ch = chars.next().unwrap(); if chars.next().is_some() { if str.contains('-') {
1835 return process_range(str, replacements, unicode_table);
1836 } else if first_ch != '0' { for ch in str.chars() { let ch_as_str = ch.to_string();
1839 if unicode_table.insert(ch as u32, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1840 .chain_err(|| format!("In definition of char: '{str}'"))?.replacements).is_some() {
1841 error!("*** Character '{}' (0x{:X}) is repeated", ch, ch as u32);
1842 }
1843 }
1844 return Ok(None);
1845 }
1846 }
1847 }
1848
1849 let ch = UnicodeDef::get_unicode_char(ch)?;
1850 if unicode_table.insert(ch, ReplacementArray::build(replacements)
1851 .chain_err(|| format!("In definition of char: '{}' (0x{})",
1852 char::from_u32(ch).unwrap(), ch))?.replacements).is_some() {
1853 error!("*** Character '{}' (0x{:X}) is repeated", char::from_u32(ch).unwrap(), ch);
1854 }
1855 return Ok(None);
1856
1857 fn process_range(def_range: &str, replacements: &Yaml, mut unicode_table: RefMut<HashMap<u32,Vec<Replacement>>>) -> Result<Option<Vec<PathBuf>>> {
1858 let mut range = def_range.split('-');
1861 let first = range.next().unwrap().chars().next().unwrap() as u32;
1862 let last = range.next().unwrap().chars().next().unwrap() as u32;
1863 if range.next().is_some() {
1864 bail!("Character range definition has more than one '-': '{}'", def_range);
1865 }
1866
1867 for ch in first..last+1 {
1868 let ch_as_str = char::from_u32(ch).unwrap().to_string();
1869 unicode_table.insert(ch, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1870 .chain_err(|| format!("In definition of char: '{def_range}'"))?.replacements);
1871 };
1872
1873 return Ok(None)
1874 }
1875
1876 fn substitute_ch(yaml: &Yaml, ch: &str) -> Yaml {
1877 return match yaml {
1878 Yaml::Array(ref v) => {
1879 Yaml::Array(
1880 v.iter()
1881 .map(|e| substitute_ch(e, ch))
1882 .collect::<Vec<Yaml>>()
1883 )
1884 },
1885 Yaml::Hash(ref h) => {
1886 Yaml::Hash(
1887 h.iter()
1888 .map(|(key,val)| (key.clone(), substitute_ch(val, ch)) )
1889 .collect::<Hash>()
1890 )
1891 },
1892 Yaml::String(s) => Yaml::String( s.replace('.', ch) ),
1893 _ => yaml.clone(),
1894 }
1895 }
1896 }
1897
1898 fn get_unicode_char(ch: &Yaml) -> Result<u32> {
1899 if let Some(ch) = ch.as_str() {
1901 let mut ch_iter = ch.chars();
1902 let unicode_ch = ch_iter.next();
1903 if unicode_ch.is_none() || ch_iter.next().is_some() {
1904 bail!("Wanted unicode char, found string '{}')", ch);
1905 };
1906 return Ok( unicode_ch.unwrap() as u32 );
1907 }
1908
1909 if let Some(num) = ch.as_i64() {
1910 return Ok( num as u32 );
1911 }
1912 bail!("Unicode character '{}' can't be converted to an code point", yaml_to_string(ch, 0));
1913 }
1914}
1915
1916type RuleTable = HashMap<String, Vec<Box<SpeechPattern>>>;
1924 type UnicodeTable = Rc<RefCell<HashMap<u32,Vec<Replacement>>>>;
1925 type FilesAndTimesShared = Rc<RefCell<FilesAndTimes>>;
1926
1927 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1928 pub enum RulesFor {
1929 Intent,
1930 Speech,
1931 OverView,
1932 Navigation,
1933 Braille,
1934 }
1935
1936 impl fmt::Display for RulesFor {
1937 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1938 let name = match self {
1939 RulesFor::Intent => "Intent",
1940 RulesFor::Speech => "Speech",
1941 RulesFor::OverView => "OverView",
1942 RulesFor::Navigation => "Navigation",
1943 RulesFor::Braille => "Braille",
1944 };
1945 return write!(f, "{name}");
1946 }
1947 }
1948
1949
1950#[derive(Debug, Clone)]
1951pub struct FileAndTime {
1952 file: PathBuf,
1953 time: SystemTime,
1954}
1955
1956impl FileAndTime {
1957 fn new(file: PathBuf) -> FileAndTime {
1958 return FileAndTime {
1959 file,
1960 time: SystemTime::UNIX_EPOCH,
1961 }
1962 }
1963
1964 pub fn debug_get_file(&self) -> Option<&str> {
1966 return self.file.to_str();
1967 }
1968
1969 pub fn new_with_time(file: PathBuf) -> FileAndTime {
1970 return FileAndTime {
1971 time: FileAndTime::get_metadata(&file),
1972 file,
1973 }
1974 }
1975
1976 pub fn is_up_to_date(&self) -> bool {
1977 let file_mod_time = FileAndTime::get_metadata(&self.file);
1978 return self.time >= file_mod_time;
1979 }
1980
1981 fn get_metadata(path: &Path) -> SystemTime {
1982 use std::fs;
1983 if !cfg!(target_family = "wasm") {
1984 let metadata = fs::metadata(path);
1985 if let Ok(metadata) = metadata {
1986 if let Ok(mod_time) = metadata.modified() {
1987 return mod_time;
1988 }
1989 }
1990 }
1991 return SystemTime::UNIX_EPOCH
1992 }
1993
1994}
1995#[derive(Debug, Default)]
1996pub struct FilesAndTimes {
1997 ft: Vec<FileAndTime>
2001}
2002
2003impl FilesAndTimes {
2004 pub fn new(start_path: PathBuf) -> FilesAndTimes {
2005 let mut ft = Vec::with_capacity(8);
2006 ft.push( FileAndTime::new(start_path) );
2007 return FilesAndTimes{ ft };
2008 }
2009
2010 pub fn is_file_up_to_date(&self, pref_path: &Path, should_ignore_file_time: bool) -> bool {
2012
2013 if self.ft.is_empty() || self.as_path() != pref_path {
2015 return false;
2016 }
2017 if should_ignore_file_time || cfg!(target_family = "wasm") {
2018 return true;
2019 }
2020 if self.ft[0].time == SystemTime::UNIX_EPOCH {
2021 return false;
2022 }
2023
2024
2025 for file in &self.ft {
2027 if !file.is_up_to_date() {
2028 return false;
2029 }
2030 }
2031 return true;
2032 }
2033
2034 fn set_files_and_times(&mut self, new_files: Vec<PathBuf>) {
2035 self.ft.clear();
2036 for path in new_files {
2037 let time = FileAndTime::get_metadata(&path); self.ft.push( FileAndTime{ file: path, time })
2039 }
2040 }
2041
2042 pub fn as_path(&self) -> &Path {
2043 assert!(!self.ft.is_empty());
2044 return &self.ft[0].file;
2045 }
2046
2047 pub fn paths(&self) -> Vec<PathBuf> {
2048 return self.ft.iter().map(|ft| ft.file.clone()).collect::<Vec<PathBuf>>();
2049 }
2050
2051}
2052
2053
2054pub struct SpeechRules {
2060 error: String,
2061 name: RulesFor,
2062 pub pref_manager: Rc<RefCell<PreferenceManager>>,
2063 rules: RuleTable, rule_files: FilesAndTimes, translate_single_chars_only: bool, unicode_short: UnicodeTable, unicode_short_files: FilesAndTimesShared, unicode_full: UnicodeTable, unicode_full_files: FilesAndTimesShared, definitions_files: FilesAndTimesShared, }
2072
2073impl fmt::Display for SpeechRules {
2074 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2075 writeln!(f, "SpeechRules '{}'\n{})", self.name, self.pref_manager.borrow())?;
2076 let mut rules_vec: Vec<(&String, &Vec<Box<SpeechPattern>>)> = self.rules.iter().collect();
2077 rules_vec.sort_by(|(tag_name1, _), (tag_name2, _)| tag_name1.cmp(tag_name2));
2078 for (tag_name, rules) in rules_vec {
2079 writeln!(f, " {}: #patterns {}", tag_name, rules.len())?;
2080 };
2081 return writeln!(f, " {}+{} unicode entries", &self.unicode_short.borrow().len(), &self.unicode_full.borrow().len());
2082 }
2083}
2084
2085
2086pub struct SpeechRulesWithContext<'c, 's:'c, 'm:'c> {
2090 speech_rules: &'s SpeechRules,
2091 context_stack: ContextStack<'c>, doc: Document<'m>,
2093 nav_node_id: &'m str,
2094 pub inside_spell: bool, pub translate_count: usize, }
2097
2098impl<'c, 's:'c, 'm:'c> fmt::Display for SpeechRulesWithContext<'c, 's,'m> {
2099 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2100 writeln!(f, "SpeechRulesWithContext \n{})", self.speech_rules)?;
2101 return writeln!(f, " {} context entries, nav node id '{}'", &self.context_stack, self.nav_node_id);
2102 }
2103}
2104
2105thread_local!{
2106 static SPEECH_UNICODE_SHORT: UnicodeTable =
2108 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2109
2110 static SPEECH_UNICODE_FULL: UnicodeTable =
2112 Rc::new( RefCell::new( HashMap::with_capacity(6500) ) );
2113
2114 static BRAILLE_UNICODE_SHORT: UnicodeTable =
2116 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2117
2118 static BRAILLE_UNICODE_FULL: UnicodeTable =
2120 Rc::new( RefCell::new( HashMap::with_capacity(5000) ) );
2121
2122 static SPEECH_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2124 Rc::new( RefCell::new(FilesAndTimes::default()) );
2125
2126 static BRAILLE_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2128 Rc::new( RefCell::new(FilesAndTimes::default()) );
2129
2130 static SPEECH_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2132 Rc::new( RefCell::new(FilesAndTimes::default()) );
2133
2134 static SPEECH_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2136 Rc::new( RefCell::new(FilesAndTimes::default()) );
2137
2138 static BRAILLE_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2140 Rc::new( RefCell::new(FilesAndTimes::default()) );
2141
2142 static BRAILLE_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2144 Rc::new( RefCell::new(FilesAndTimes::default()) );
2145
2146 pub static INTENT_RULES: RefCell<SpeechRules> =
2149 RefCell::new( SpeechRules::new(RulesFor::Intent, true) );
2150
2151 pub static SPEECH_RULES: RefCell<SpeechRules> =
2152 RefCell::new( SpeechRules::new(RulesFor::Speech, true) );
2153
2154 pub static OVERVIEW_RULES: RefCell<SpeechRules> =
2155 RefCell::new( SpeechRules::new(RulesFor::OverView, true) );
2156
2157 pub static NAVIGATION_RULES: RefCell<SpeechRules> =
2158 RefCell::new( SpeechRules::new(RulesFor::Navigation, true) );
2159
2160 pub static BRAILLE_RULES: RefCell<SpeechRules> =
2161 RefCell::new( SpeechRules::new(RulesFor::Braille, false) );
2162}
2163
2164impl SpeechRules {
2165 pub fn new(name: RulesFor, translate_single_chars_only: bool) -> SpeechRules {
2166 let globals = if name == RulesFor::Braille {
2167 (
2168 (BRAILLE_UNICODE_SHORT.with(Rc::clone), BRAILLE_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2169 (BRAILLE_UNICODE_FULL. with(Rc::clone), BRAILLE_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2170 BRAILLE_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2171 )
2172 } else {
2173 (
2174 (SPEECH_UNICODE_SHORT.with(Rc::clone), SPEECH_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2175 (SPEECH_UNICODE_FULL. with(Rc::clone), SPEECH_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2176 SPEECH_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2177 )
2178 };
2179
2180 return SpeechRules {
2181 error: Default::default(),
2182 name,
2183 rules: HashMap::with_capacity(if name == RulesFor::Intent || name == RulesFor::Speech {500} else {50}), rule_files: FilesAndTimes::default(),
2185 unicode_short: globals.0.0, unicode_short_files: globals.0.1,
2187 unicode_full: globals.1.0, unicode_full_files: globals.1.1,
2189 definitions_files: globals.2,
2190 translate_single_chars_only,
2191 pref_manager: PreferenceManager::get(),
2192 };
2193}
2194
2195 pub fn get_error(&self) -> Option<&str> {
2196 return if self.error.is_empty() {
2197 None
2198 } else {
2199 Some(&self.error)
2200 }
2201 }
2202
2203 pub fn read_files(&mut self) -> Result<()> {
2204 let check_rule_files = self.pref_manager.borrow().pref_to_string("CheckRuleFiles");
2205 if check_rule_files != "None" { self.pref_manager.borrow_mut().set_preference_files()?;
2207 }
2208 let should_ignore_file_time = self.pref_manager.borrow().pref_to_string("CheckRuleFiles") != "All"; let rule_file = self.pref_manager.borrow().get_rule_file(&self.name).to_path_buf(); if self.rules.is_empty() || !self.rule_files.is_file_up_to_date(&rule_file, should_ignore_file_time) {
2211 self.rules.clear();
2212 let files_read = self.read_patterns(&rule_file)?;
2213 self.rule_files.set_files_and_times(files_read);
2214 }
2215
2216 let pref_manager = self.pref_manager.borrow();
2217 let unicode_pref_files = if self.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2218
2219 if !self.unicode_short_files.borrow().is_file_up_to_date(unicode_pref_files.0, should_ignore_file_time) {
2220 self.unicode_short.borrow_mut().clear();
2221 self.unicode_short_files.borrow_mut().set_files_and_times(self.read_unicode(None, true)?);
2222 }
2223
2224 if self.definitions_files.borrow().ft.is_empty() || !self.definitions_files.borrow().is_file_up_to_date(
2225 pref_manager.get_definitions_file(self.name != RulesFor::Braille),
2226 should_ignore_file_time
2227 ) {
2228 self.definitions_files.borrow_mut().set_files_and_times(read_definitions_file(self.name != RulesFor::Braille)?);
2229 }
2230 return Ok( () );
2231 }
2232
2233 fn read_patterns(&mut self, path: &Path) -> Result<Vec<PathBuf>> {
2234 let rule_file_contents = read_to_string_shim(path).chain_err(|| format!("cannot read file '{}'", path.to_str().unwrap()))?;
2236 let rules_build_fn = |pattern: &Yaml| {
2237 self.build_speech_patterns(pattern, path)
2238 .chain_err(||format!("in file {:?}", path.to_str().unwrap()))
2239 };
2240 return compile_rule(&rule_file_contents, rules_build_fn)
2241 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2242 }
2243
2244 fn build_speech_patterns(&mut self, patterns: &Yaml, file_name: &Path) -> Result<Vec<PathBuf>> {
2245 let patterns_vec = patterns.as_vec();
2247 if patterns_vec.is_none() {
2248 bail!(yaml_type_err(patterns, "array"));
2249 }
2250 let patterns_vec = patterns.as_vec().unwrap();
2251 let mut files_read = vec![file_name.to_path_buf()];
2252 for entry in patterns_vec.iter() {
2253 if let Some(mut added_files) = SpeechPattern::build(entry, file_name, self)? {
2254 files_read.append(&mut added_files);
2255 }
2256 }
2257 return Ok(files_read)
2258 }
2259
2260 fn read_unicode(&self, path: Option<PathBuf>, use_short: bool) -> Result<Vec<PathBuf>> {
2261 let path = match path {
2262 Some(p) => p,
2263 None => {
2264 let pref_manager = self.pref_manager.borrow();
2266 let unicode_files = if self.name == RulesFor::Braille {
2267 pref_manager.get_braille_unicode_file()
2268 } else {
2269 pref_manager.get_speech_unicode_file()
2270 };
2271 let unicode_files = if use_short {unicode_files.0} else {unicode_files.1};
2272 unicode_files.to_path_buf()
2273 }
2274 };
2275
2276 let unicode_file_contents = read_to_string_shim(&path)?;
2279 let unicode_build_fn = |unicode_def_list: &Yaml| {
2280 let unicode_defs = unicode_def_list.as_vec();
2281 if unicode_defs.is_none() {
2282 bail!("File '{}' does not begin with an array", yaml_to_type(unicode_def_list));
2283 };
2284 let mut files_read = vec![path.to_path_buf()];
2285 for unicode_def in unicode_defs.unwrap() {
2286 if let Some(mut added_files) = UnicodeDef::build(unicode_def, &path, self, use_short)
2287 .chain_err(|| {format!("In file {:?}", path.to_str())})? {
2288 files_read.append(&mut added_files);
2289 }
2290 };
2291 return Ok(files_read)
2292 };
2293
2294 return compile_rule(&unicode_file_contents, unicode_build_fn)
2295 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2296 }
2297
2298 pub fn print_sizes() -> String {
2299 let mut answer = rule_size(&SPEECH_RULES, "SPEECH_RULES");
2307 answer += &rule_size(&INTENT_RULES, "INTENT_RULES");
2308 answer += &rule_size(&BRAILLE_RULES, "BRAILLE_RULES");
2309 answer += &rule_size(&NAVIGATION_RULES, "NAVIGATION_RULES");
2310 answer += &rule_size(&OVERVIEW_RULES, "OVERVIEW_RULES");
2311 SPEECH_RULES.with_borrow(|rule| {
2312 answer += &format!("Speech Unicode tables: short={}/{}, long={}/{}\n",
2313 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2314 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2315 });
2316 BRAILLE_RULES.with_borrow(|rule| {
2317 answer += &format!("Braille Unicode tables: short={}/{}, long={}/{}\n",
2318 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2319 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2320 });
2321 return answer;
2322
2323 fn rule_size(rules: &'static std::thread::LocalKey<RefCell<SpeechRules>>, name: &str) -> String {
2324 rules.with_borrow(|rule| {
2325 let hash_map = &rule.rules;
2326 return format!("{}: {}/{}\n", name, hash_map.len(), hash_map.capacity());
2327 })
2328 }
2329 }
2330}
2331
2332
2333impl<'c, 's:'c, 'r, 'm:'c> SpeechRulesWithContext<'c, 's,'m> {
2338 pub fn new(speech_rules: &'s SpeechRules, doc: Document<'m>, nav_node_id: &'m str) -> SpeechRulesWithContext<'c, 's, 'm> {
2339 return SpeechRulesWithContext {
2340 speech_rules,
2341 context_stack: ContextStack::new(&speech_rules.pref_manager.borrow()),
2342 doc,
2343 nav_node_id,
2344 inside_spell: false,
2345 translate_count: 0,
2346 }
2347 }
2348
2349 pub fn get_rules(&mut self) -> &SpeechRules {
2350 return self.speech_rules;
2351 }
2352
2353 pub fn get_context(&mut self) -> &mut Context<'c> {
2354 return &mut self.context_stack.base;
2355 }
2356
2357 pub fn get_document(&mut self) -> Document<'m> {
2358 return self.doc;
2359 }
2360
2361 pub fn match_pattern<T:TreeOrString<'c, 'm, T>>(&'r mut self, mathml: Element<'c>) -> Result<T> {
2362 let tag_name = mathml.name().local_part();
2364 let rules = &self.speech_rules.rules;
2365
2366 if let Some(rule_vector) = rules.get("!*") {
2368 if let Some(result) = self.find_match(rule_vector, mathml)? {
2369 return Ok(result); }
2371 }
2372
2373 if let Some(rule_vector) = rules.get(tag_name) {
2374 if let Some(result) = self.find_match(rule_vector, mathml)? {
2375 return Ok(result); }
2377 }
2378
2379 if let Some(rule_vector) = rules.get("*") {
2381 if let Some(result) = self.find_match(rule_vector, mathml)? {
2382 return Ok(result); }
2384 }
2385
2386 let speech_manager = self.speech_rules.pref_manager.borrow();
2389 let file_name = speech_manager.get_rule_file(&self.speech_rules.name);
2390 bail!("\nNo match found!\nMissing patterns in {} for MathML.\n{}", file_name.to_string_lossy(), mml_to_string(mathml));
2392 }
2393
2394 fn find_match<T:TreeOrString<'c, 'm, T>>(&'r mut self, rule_vector: &[Box<SpeechPattern>], mathml: Element<'c>) -> Result<Option<T>> {
2395 for pattern in rule_vector {
2396 if pattern.match_uses_var_defs {
2400 self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2401 }
2402 if pattern.is_match(&self.context_stack.base, mathml)
2403 .chain_err(|| error_string(pattern, mathml) )? {
2404 if !pattern.match_uses_var_defs && pattern.var_defs.len() > 0 { self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2407 }
2408 let result: Result<T> = pattern.replacements.replace(self, mathml);
2409 if pattern.var_defs.len() > 0 {
2410 self.context_stack.pop();
2411 }
2412 return match result {
2413 Ok(s) => {
2414 if self.nav_node_id.is_empty() {
2416 Ok( Some(s) )
2417 } else {
2418 Ok ( Some(self.nav_node_adjust(s, mathml)) )
2420 }
2421 },
2422 Err(e) => Err( e.chain_err(||
2423 format!(
2424 "attempting replacement pattern: \"{}\" for \"{}\".\n\
2425 Replacement\n{}\n...due to matching the MathML\n{} with the pattern\n\
2426 {}\n\
2427 The patterns are in {}.\n",
2428 pattern.pattern_name, pattern.tag_name,
2429 pattern.replacements.pretty_print_replacements(),
2430 mml_to_string(mathml), pattern.pattern,
2431 pattern.file_name
2432 )
2433 ))
2434 }
2435 } else if pattern.match_uses_var_defs {
2436 self.context_stack.pop();
2437 }
2438 };
2439 return Ok(None); fn error_string(pattern: &SpeechPattern, mathml: Element) -> String {
2442 return format!(
2443 "error during pattern match using: \"{}\" for \"{}\".\n\
2444 Pattern is \n{}\nMathML for the match:\n\
2445 {}\
2446 The patterns are in {}.\n",
2447 pattern.pattern_name, pattern.tag_name,
2448 pattern.pattern,
2449 mml_to_string(mathml),
2450 pattern.file_name
2451 );
2452 }
2453
2454 }
2455
2456 fn nav_node_adjust<T:TreeOrString<'c, 'm, T>>(&self, speech: T, mathml: Element<'c>) -> T {
2457 if let Some(id) = mathml.attribute_value("id") {
2458 if self.nav_node_id == id {
2459 if self.speech_rules.name == RulesFor::Braille {
2460 let highlight_style = self.speech_rules.pref_manager.borrow().pref_to_string("BrailleNavHighlight");
2461 return T::highlight_braille(speech, highlight_style);
2462 } else {
2463 return T::mark_nav_speech(speech)
2464 }
2465 }
2466 }
2467 return speech;
2468
2469 }
2470
2471 fn highlight_braille_string(braille: String, highlight_style: String) -> String {
2472 if &highlight_style == "Off" || braille.is_empty() {
2474 return braille;
2475 }
2476
2477 let mut chars = braille.chars().collect::<Vec<char>>();
2480
2481 let baseline_indicator_hack = PreferenceManager::get().borrow().pref_to_string("BrailleCode") == "Nemeth";
2483 let mut i_first_modified = 0;
2485 for (i, ch) in chars.iter_mut().enumerate() {
2486 let modified_ch = add_dots_to_braille_char(*ch, baseline_indicator_hack);
2487 if *ch != modified_ch {
2488 *ch = modified_ch;
2489 i_first_modified = i;
2490 break;
2491 };
2492 };
2493
2494 let mut i_last_modified = i_first_modified;
2495 if &highlight_style != "FirstChar" {
2496 for i in (i_first_modified..chars.len()).rev(){
2498 let ch = chars[i];
2499 let modified_ch = add_dots_to_braille_char(ch, baseline_indicator_hack);
2500 chars[i] = modified_ch;
2501 if ch != modified_ch {
2502 i_last_modified = i;
2503 break;
2504 }
2505 }
2506 }
2507
2508 if &highlight_style == "All" {
2509 #[allow(clippy::needless_range_loop)] for i in i_first_modified+1..i_last_modified {
2512 chars[i] = add_dots_to_braille_char(chars[i], baseline_indicator_hack);
2513 };
2514 }
2515
2516 let result = chars.into_iter().collect::<String>();
2517 return result;
2519
2520 fn add_dots_to_braille_char(ch: char, baseline_indicator_hack: bool) -> char {
2521 let as_u32 = ch as u32;
2522 if (0x2800..0x28FF).contains(&as_u32) {
2523 return unsafe {char::from_u32_unchecked(as_u32 | 0xC0)};
2524 } else if baseline_indicator_hack && ch == 'b' {
2525 return '𝑏'
2526 } else {
2527 return ch;
2528 }
2529 }
2530 }
2531
2532 fn mark_nav_speech(speech: String) -> String {
2533 return "[[".to_string() + &speech + "]]";
2536 }
2537
2538 fn replace<T:TreeOrString<'c, 'm, T>>(&'r mut self, replacement: &Replacement, mathml: Element<'c>) -> Result<T> {
2539 return Ok(
2540 match replacement {
2541 Replacement::Text(t) => T::from_string(t.clone(), self.doc)?,
2542 Replacement::XPath(xpath) => xpath.replace(self, mathml)?,
2543 Replacement::TTS(tts) => {
2544 T::from_string(
2545 self.speech_rules.pref_manager.borrow().get_tts().replace(tts, &self.speech_rules.pref_manager.borrow(), self, mathml)?,
2546 self.doc
2547 )?
2548 },
2549 Replacement::Intent(intent) => {
2550 intent.replace(self, mathml)?
2551 },
2552 Replacement::Test(test) => {
2553 test.replace(self, mathml)?
2554 },
2555 Replacement::With(with) => {
2556 with.replace(self, mathml)?
2557 },
2558 Replacement::SetVariables(vars) => {
2559 vars.replace(self, mathml)?
2560 },
2561 Replacement::Insert(ic) => {
2562 ic.replace(self, mathml)?
2563 },
2564 Replacement::Translate(id) => {
2565 id.replace(self, mathml)?
2566 },
2567 }
2568 )
2569 }
2570
2571 fn replace_nodes<T:TreeOrString<'c, 'm, T>>(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T> {
2575 return T::replace_nodes(self, nodes, mathml);
2576 }
2577
2578 fn replace_nodes_tree(&'r mut self, nodes: Vec<Node<'c>>, _mathml: Element<'c>) -> Result<Element<'m>> {
2581 let mut children = Vec::with_capacity(3*nodes.len()); for node in nodes {
2583 let matched = match node {
2584 Node::Element(n) => self.match_pattern::<Element<'m>>(n)?,
2585 Node::Text(t) => {
2586 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2587 leaf.set_text(t.text());
2589 leaf
2590 },
2591 Node::Attribute(attr) => {
2592 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2594 leaf.set_text(attr.value());
2595 leaf
2596 },
2597 _ => {
2598 bail!("replace_nodes: found unexpected node type!!!");
2599 },
2600 };
2601 children.push(matched);
2602 }
2603
2604 let result = create_mathml_element(&self.doc, "TEMP_NAME"); result.append_children(children);
2606 return Ok( result );
2608 }
2609
2610 fn replace_nodes_string(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
2611 let mut result = String::with_capacity(3*nodes.len()); let mut first_time = true;
2614 for node in nodes {
2615 if first_time {
2616 first_time = false;
2617 } else {
2618 result.push(' ');
2619 };
2620 let matched = match node {
2621 Node::Element(n) => self.match_pattern::<String>(n)?,
2622 Node::Text(t) => self.replace_chars(t.text(), mathml)?,
2623 Node::Attribute(attr) => self.replace_chars(attr.value(), mathml)?,
2624 _ => bail!("replace_nodes: found unexpected node type!!!"),
2625 };
2626 result += &matched;
2627 }
2628 return Ok( result );
2629 }
2630
2631 pub fn replace_chars(&'r mut self, str: &str, mathml: Element<'c>) -> Result<String> {
2634 if is_quoted_string(str) {
2635 return Ok(unquote_string(str).to_string());
2636 }
2637 let rules = self.speech_rules;
2638 let mut chars = str.chars();
2639 if rules.translate_single_chars_only {
2641 let ch = chars.next().unwrap_or(' ');
2642 if chars.next().is_none() {
2643 return replace_single_char(self, ch, mathml)
2645 } else {
2646 return Ok(str.replace('\u{00A0}', " ").replace(['\u{2061}', '\u{2062}', '\u{2063}', '\u{2064}'], ""))
2648 }
2649 };
2650
2651 let result = chars
2652 .map(|ch| replace_single_char(self, ch, mathml))
2653 .collect::<Result<Vec<String>>>()?
2654 .join("");
2655 return Ok( result );
2656
2657 fn replace_single_char<'c, 's:'c, 'm, 'r>(rules_with_context: &'r mut SpeechRulesWithContext<'c,'s,'m>, ch: char, mathml: Element<'c>) -> Result<String> {
2658 let ch_as_u32 = ch as u32;
2659 let rules = rules_with_context.speech_rules;
2660 let mut unicode = rules.unicode_short.borrow();
2661 let mut replacements = unicode.get( &ch_as_u32 );
2662 if replacements.is_none() {
2663 let pref_manager = rules.pref_manager.borrow();
2665 let unicode_pref_files = if rules.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2666 let should_ignore_file_time = pref_manager.pref_to_string("CheckRuleFiles") == "All";
2667 if rules.unicode_full.borrow().is_empty() || !rules.unicode_full_files.borrow().is_file_up_to_date(unicode_pref_files.1, should_ignore_file_time) {
2668 info!("*** Loading full unicode {} for char '{}'/{:#06x}", rules.name, ch, ch_as_u32);
2669 rules.unicode_full.borrow_mut().clear();
2670 rules.unicode_full_files.borrow_mut().set_files_and_times(rules.read_unicode(None, false)?);
2671 info!("# Unicode defs = {}/{}", rules.unicode_short.borrow().len(), rules.unicode_full.borrow().len());
2672 }
2673 unicode = rules.unicode_full.borrow();
2674 replacements = unicode.get( &ch_as_u32 );
2675 if replacements.is_none() {
2676 rules_with_context.translate_count = 0; return Ok(String::from(ch)); }
2680 };
2681
2682 let result = replacements.unwrap()
2684 .iter()
2685 .map(|replacement|
2686 rules_with_context.replace(replacement, mathml)
2687 .chain_err(|| format!("Unicode replacement error: {replacement}")) )
2688 .collect::<Result<Vec<String>>>()?
2689 .join(" ");
2690 rules_with_context.translate_count = 0; return Ok(result);
2692 }
2693 }
2694}
2695
2696pub fn braille_replace_chars(str: &str, mathml: Element) -> Result<String> {
2698 return BRAILLE_RULES.with(|rules| {
2699 let rules = rules.borrow();
2700 let new_package = Package::new();
2701 let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), "");
2702 return rules_with_context.replace_chars(str, mathml);
2703 })
2704}
2705
2706
2707
2708#[cfg(test)]
2709mod tests {
2710 #[allow(unused_imports)]
2711 use crate::init_logger;
2712
2713 use super::*;
2714
2715 #[test]
2716 fn test_read_statement() {
2717 let str = r#"---
2718 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2719 let doc = YamlLoader::load_from_str(str).unwrap();
2720 assert_eq!(doc.len(), 1);
2721 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2722
2723 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2724 assert_eq!(rules.rules["math"].len(), 1, "\nshould only be one rule");
2725
2726 let speech_pattern = &rules.rules["math"][0];
2727 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2728 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2729 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2730 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2731 assert_eq!(speech_pattern.replacements.replacements[0].to_string(), r#""./*""#, "\nreplacement failure");
2732 }
2733
2734 #[test]
2735 fn test_read_statements_with_replace() {
2736 let str = r#"---
2737 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2738 let doc = YamlLoader::load_from_str(str).unwrap();
2739 assert_eq!(doc.len(), 1);
2740 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2741 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2742
2743 let str = r#"---
2744 {name: default, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2745 let doc2 = YamlLoader::load_from_str(str).unwrap();
2746 assert_eq!(doc2.len(), 1);
2747 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2748 assert_eq!(rules.rules["math"].len(), 1, "\nfirst rule not replaced");
2749
2750 let speech_pattern = &rules.rules["math"][0];
2751 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2752 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2753 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2754 assert_eq!(speech_pattern.replacements.replacements.len(), 2, "\nreplacement failure");
2755 }
2756
2757 #[test]
2758 fn test_read_statements_with_add() {
2759 let str = r#"---
2760 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2761 let doc = YamlLoader::load_from_str(str).unwrap();
2762 assert_eq!(doc.len(), 1);
2763 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2764 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2765
2766 let str = r#"---
2767 {name: another-rule, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2768 let doc2 = YamlLoader::load_from_str(str).unwrap();
2769 assert_eq!(doc2.len(), 1);
2770 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2771 assert_eq!(rules.rules["math"].len(), 2, "\nsecond rule not added");
2772
2773 let speech_pattern = &rules.rules["math"][0];
2774 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2775 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2776 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2777 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2778 }
2779
2780 #[test]
2781 fn test_debug_no_debug() {
2782 let str = r#"*[2]/*[3][text()='3']"#;
2783 let result = MyXPath::add_debug_string_arg(str);
2784 assert!(result.is_ok());
2785 assert_eq!(result.unwrap(), str);
2786 }
2787
2788 #[test]
2789 fn test_debug_no_debug_with_quote() {
2790 let str = r#"*[2]/*[3][text()='(']"#;
2791 let result = MyXPath::add_debug_string_arg(str);
2792 assert!(result.is_ok());
2793 assert_eq!(result.unwrap(), str);
2794 }
2795
2796 #[test]
2797 fn test_debug_no_quoted_paren() {
2798 let str = r#"DEBUG(*[2]/*[3][text()='3'])"#;
2799 let result = MyXPath::add_debug_string_arg(str);
2800 assert!(result.is_ok());
2801 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='3'], "*[2]/*[3][text()='3']")"#);
2802 }
2803
2804 #[test]
2805 fn test_debug_quoted_paren() {
2806 let str = r#"DEBUG(*[2]/*[3][text()='('])"#;
2807 let result = MyXPath::add_debug_string_arg(str);
2808 assert!(result.is_ok());
2809 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='('], "*[2]/*[3][text()='(']")"#);
2810 }
2811
2812 #[test]
2813 fn test_debug_quoted_paren_before_paren() {
2814 let str = r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics') and IsBracketed(., '(', ')')"#;
2815 let result = MyXPath::add_debug_string_arg(str);
2816 assert!(result.is_ok());
2817 assert_eq!(result.unwrap(), r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics', "ClearSpeak_Matrix = 'Combinatorics'") and IsBracketed(., '(', ')')"#);
2818 }
2819
2820
2821cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] {
2823 #[test]
2824 fn test_up_to_date() {
2825 use crate::interface::*;
2826 set_rules_dir(super::super::abs_rules_dir_path()).unwrap();
2828 set_preference("Language".to_string(), "zz-aa".to_string()).unwrap();
2829 if let Err(e) = set_mathml("<math><mi>x</mi></math>".to_string()) {
2831 error!("{}", crate::errors_to_string(&e));
2832 panic!("Should not be an error in setting MathML")
2833 }
2834
2835 set_preference("CheckRuleFiles".to_string(), "All".to_string()).unwrap();
2836 assert!(!is_file_time_same(), "file's time did not get updated");
2837 set_preference("CheckRuleFiles".to_string(), "None".to_string()).unwrap();
2838 assert!(is_file_time_same(), "file's time was wrongly updated (preference 'CheckRuleFiles' should have prevented updating)");
2839
2840 fn is_file_time_same() -> bool {
2842 use std::time::Duration;
2846 return SPEECH_RULES.with(|rules| {
2847 let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone();
2848
2849 let contents = std::fs::read(&start_main_file.file).expect(&format!("Failed to read file {} during test", &start_main_file.file.to_string_lossy()));
2851 std::fs::write(start_main_file.file, contents).unwrap();
2852 std::thread::sleep(Duration::from_millis(5)); if let Err(e) = get_spoken_text() {
2856 error!("{}", crate::errors_to_string(&e));
2857 panic!("Should not be an error in speech")
2858 }
2859 return rules.borrow().unicode_short_files.borrow().ft[0].time == start_main_file.time;
2860 });
2861 }
2862 }
2863}}
2864
2865 }