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 return 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 speech_string = nestable_speak_rules(& mut rules_with_context, mathml, nav_node_id)?;
132 return Ok( rules.pref_manager.borrow().get_tts()
133 .merge_pauses(remove_optional_indicators(
134 &speech_string.replace(CONCAT_STRING, "")
135 .replace(CONCAT_INDICATOR, "")
136 )
137 .trim_start().trim_end_matches([' ', ',', ';'])) );
138 });
139
140 fn nestable_speak_rules<'c, 's:'c, 'm:'c>(rules_with_context: &mut SpeechRulesWithContext<'c, 's, 'm>, mathml: Element<'c>, nav_node_id: &str) -> Result<String> {
141 let mut speech_string = rules_with_context.match_pattern::<String>(mathml)
142 .chain_err(|| "Pattern match/replacement failure!")?;
143 if !nav_node_id.is_empty() {
146 let intent_attr = mathml.attribute_value("data-intent-property").unwrap_or_default();
148 if let Some(start) = speech_string.find("[[") {
149 match speech_string[start+2..].find("]]") {
150 None => bail!("Internal error: looking for '[[...]]' during navigation -- only found '[[' in '{}'", speech_string),
151 Some(end) => speech_string = speech_string[start+2..start+2+end].to_string(),
152 }
153 } else if !intent_attr.contains(":literal:") {
154 mathml.set_attribute_value("data-intent-property", (":literal:".to_string() + intent_attr).as_str());
156 let speech = nestable_speak_rules(rules_with_context, mathml, nav_node_id);
157 mathml.set_attribute_value("data-intent-property", intent_attr);
158 return speech;
159 } else {
160 bail!(NAV_NODE_SPEECH_NOT_FOUND); }
162 }
163 return Ok(speech_string);
164 }
165}
166
167pub fn yaml_to_type(yaml: &Yaml) -> String {
169 return match yaml {
170 Yaml::Real(v)=> format!("real='{v:#}'"),
171 Yaml::Integer(v)=> format!("integer='{v:#}'"),
172 Yaml::String(v)=> format!("string='{v:#}'"),
173 Yaml::Boolean(v)=> format!("boolean='{v:#}'"),
174 Yaml::Array(v)=> match v.len() {
175 0 => "array with no entries".to_string(),
176 1 => format!("array with the entry: {}", yaml_to_type(&v[0])),
177 _ => format!("array with {} entries. First entry: {}", v.len(), yaml_to_type(&v[0])),
178 }
179 Yaml::Hash(h)=> {
180 let first_pair =
181 if h.is_empty() {
182 "no pairs".to_string()
183 } else {
184 let (key, val) = h.iter().next().unwrap();
185 format!("({}, {})", yaml_to_type(key), yaml_to_type(val))
186 };
187 format!("dictionary with {} pair{}. A pair: {}", h.len(), if h.len()==1 {""} else {"s"}, first_pair)
188 }
189 Yaml::Alias(_)=> "Alias".to_string(),
190 Yaml::Null=> "Null".to_string(),
191 Yaml::BadValue=> "BadValue".to_string(),
192 }
193}
194
195fn yaml_type_err(yaml: &Yaml, str: &str) -> String {
196 return format!("Expected {}, found {}", str, yaml_to_type(yaml));
197}
198
199fn find_str<'a>(dict: &'a Yaml, key: &'a str) -> Option<&'a str> {
212 return dict[key].as_str();
213}
214
215pub fn as_hash_checked(value: &Yaml) -> Result<&Hash> {
217 let result = value.as_hash();
218 let result = result.ok_or_else(|| yaml_type_err(value, "hashmap"))?;
219 return Ok( result );
220}
221
222pub fn as_vec_checked(value: &Yaml) -> Result<&Vec<Yaml>> {
224 let result = value.as_vec();
225 let result = result.ok_or_else(|| yaml_type_err(value, "array"))?;
226 return Ok( result );
227}
228
229pub fn as_str_checked(yaml: &Yaml) -> Result<&str> {
231 return Ok( yaml.as_str().ok_or_else(|| yaml_type_err(yaml, "string"))? );
232}
233
234
235pub const CONCAT_INDICATOR: &str = "\u{F8FE}";
239
240pub const CONCAT_STRING: &str = " \u{F8FE}";
242
243const OPTIONAL_INDICATOR: &str = "\u{F8FD}";
246const OPTIONAL_INDICATOR_LEN: usize = OPTIONAL_INDICATOR.len();
247
248pub fn remove_optional_indicators(str: &str) -> String {
249 return str.replace(OPTIONAL_INDICATOR, "");
250}
251
252pub fn compile_rule<F>(str: &str, mut build_fn: F) -> Result<Vec<PathBuf>> where
256 F: FnMut(&Yaml) -> Result<Vec<PathBuf>> {
257 let docs = YamlLoader::load_from_str(str);
258 match docs {
259 Err(e) => {
260 bail!("Parse error!!: {}", e);
261 },
262 Ok(docs) => {
263 if docs.len() != 1 {
264 bail!("Didn't find rules!");
265 }
266 return build_fn(&docs[0]);
267 }
268 }
269}
270
271pub fn process_include<F>(current_file: &Path, new_file_name: &str, mut read_new_file: F) -> Result<Vec<PathBuf>>
272 where F: FnMut(&Path) -> Result<Vec<PathBuf>> {
273 let parent_path = current_file.parent();
274 if parent_path.is_none() {
275 bail!("Internal error: {:?} is not a valid file name", current_file);
276 }
277 let mut new_file = match canonicalize_shim(parent_path.unwrap()) {
278 Ok(path) => path,
279 Err(e) => bail!("process_include: canonicalize failed for {} with message {}", parent_path.unwrap().display(), e.to_string()),
280 };
281
282 for unzip_dir in new_file.ancestors() {
284 if unzip_dir.ends_with("Rules") {
285 break; }
287 if unzip_dir.ends_with("Languages") || unzip_dir.ends_with("Braille") {
288 if let Some(subdir) = new_file.strip_prefix(unzip_dir).unwrap().iter().next() {
291 let default_lang = if unzip_dir.ends_with("Languages") {"en"} else {"UEB;"};
292 PreferenceManager::unzip_files(unzip_dir, subdir.to_str().unwrap(), Some(default_lang)).unwrap_or_default();
293 }
294 }
295 }
296 new_file.push(new_file_name);
297 info!("...processing include: {new_file_name}...");
298 let new_file = match crate::shim_filesystem::canonicalize_shim(new_file.as_path()) {
299 Ok(buf) => buf,
300 Err(msg) => bail!("-include: constructed file name '{}' causes error '{}'",
301 new_file.to_str().unwrap(), msg),
302 };
303
304 let mut included_files = read_new_file(new_file.as_path())?;
305 let mut files_read = vec![new_file];
306 files_read.append(&mut included_files);
307 return Ok(files_read);
308}
309
310pub trait TreeOrString<'c, 'm:'c, T> {
313 fn from_element(e: Element<'m>) -> Result<T>;
314 fn from_string(s: String, doc: Document<'m>) -> Result<T>;
315 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>;
316 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T>;
317 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T>;
318 fn highlight_braille(braille: T, highlight_style: String) -> T;
319 fn mark_nav_speech(speech: T) -> T;
320}
321
322impl<'c, 'm:'c> TreeOrString<'c, 'm, String> for String {
323 fn from_element(_e: Element<'m>) -> Result<String> {
324 bail!("from_element not allowed for strings");
325 }
326
327 fn from_string(s: String, _doc: Document<'m>) -> Result<String> {
328 return Ok(s);
329 }
330
331 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> {
332 return tts.replace_string(command, prefs, rules_with_context, mathml);
333 }
334
335 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
336 return ra.replace_array_string(rules_with_context, mathml);
337 }
338
339 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
340 return rules.replace_nodes_string(nodes, mathml);
341 }
342
343 fn highlight_braille(braille: String, highlight_style: String) -> String {
344 return SpeechRulesWithContext::highlight_braille_string(braille, highlight_style);
345 }
346
347 fn mark_nav_speech(speech: String) -> String {
348 return SpeechRulesWithContext::mark_nav_speech(speech);
349 }
350}
351
352impl<'c, 'm:'c> TreeOrString<'c, 'm, Element<'m>> for Element<'m> {
353 fn from_element(e: Element<'m>) -> Result<Element<'m>> {
354 return Ok(e);
355 }
356
357 fn from_string(s: String, doc: Document<'m>) -> Result<Element<'m>> {
358 let leaf = create_mathml_element(&doc, "mi");
360 leaf.set_text(&s);
361 return Ok(leaf);
362}
363
364 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>> {
365 bail!("Internal error: applying a TTS rule to a tree");
366 }
367
368 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<Element<'m>> {
369 return ra.replace_array_tree(rules_with_context, mathml);
370 }
371
372 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<Element<'m>> {
373 return rules.replace_nodes_tree(nodes, mathml);
374 }
375
376 fn highlight_braille(_braille: Element<'c>, _highlight_style: String) -> Element<'m> {
377 panic!("Internal error: highlight_braille called on a tree");
378 }
379
380 fn mark_nav_speech(_speech: Element<'c>) -> Element<'m> {
381 panic!("Internal error: mark_nav_speech called on a tree");
382 }
383}
384
385#[derive(Debug, Clone)]
388#[allow(clippy::upper_case_acronyms)]
389enum Replacement {
390 Text(String),
392 XPath(MyXPath),
393 Intent(Box<Intent>),
394 Test(Box<TestArray>),
395 TTS(Box<TTSCommandRule>),
396 With(Box<With>),
397 SetVariables(Box<SetVariables>),
398 Insert(Box<InsertChildren>),
399 Translate(TranslateExpression),
400}
401
402impl fmt::Display for Replacement {
403 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
404 return write!(f, "{}",
405 match self {
406 Replacement::Test(c) => c.to_string(),
407 Replacement::Text(t) => format!("t: \"{t}\""),
408 Replacement::XPath(x) => x.to_string(),
409 Replacement::Intent(i) => i.to_string(),
410 Replacement::TTS(t) => t.to_string(),
411 Replacement::With(w) => w.to_string(),
412 Replacement::SetVariables(v) => v.to_string(),
413 Replacement::Insert(ic) => ic.to_string(),
414 Replacement::Translate(x) => x.to_string(),
415 }
416 );
417 }
418}
419
420impl Replacement {
421 fn build(replacement: &Yaml) -> Result<Replacement> {
422 let dictionary = replacement.as_hash();
424 if dictionary.is_none() {
425 bail!(" expected a key/value pair. Found {}.", yaml_to_string(replacement, 0));
426 };
427 let dictionary = dictionary.unwrap();
428 if dictionary.is_empty() {
429 bail!("No key/value pairs found for key 'replace'.\n\
430 Suggestion: are the following lines indented properly?");
431 }
432 if dictionary.len() > 1 {
433 bail!("Should only be one key/value pair for the replacement.\n \
434 Suggestion: are the following lines indented properly?\n \
435 The key/value pairs found are\n{}", yaml_to_string(replacement, 2));
436 }
437
438 let (key, value) = dictionary.iter().next().unwrap();
440 let key = key.as_str().ok_or("replacement key(e.g, 't') is not a string")?;
441 match key {
442 "t" | "T" => {
443 return Ok( Replacement::Text( as_str_checked(value)?.to_string() ) );
444 },
445 "ct" | "CT" => {
446 return Ok( Replacement::Text( CONCAT_INDICATOR.to_string() + as_str_checked(value)? ) );
447 },
448 "ot" | "OT" => {
449 return Ok( Replacement::Text( OPTIONAL_INDICATOR.to_string() + as_str_checked(value)? + OPTIONAL_INDICATOR ) );
450 },
451 "x" => {
452 return Ok( Replacement::XPath( MyXPath::build(value)
453 .chain_err(|| "while trying to evaluate value of 'x:'")? ) );
454 },
455 "pause" | "rate" | "pitch" | "volume" | "audio" | "gender" | "voice" | "spell" | "SPELL" | "bookmark" | "pronounce" | "PRONOUNCE" => {
456 return Ok( Replacement::TTS( TTS::build(&key.to_ascii_lowercase(), value)? ) );
457 },
458 "intent" => {
459 return Ok( Replacement::Intent( Intent::build(value)? ) );
460 },
461 "test" => {
462 return Ok( Replacement::Test( Box::new( TestArray::build(value)? ) ) );
463 },
464 "with" => {
465 return Ok( Replacement::With( With::build(value)? ) );
466 },
467 "set_variables" => {
468 return Ok( Replacement::SetVariables( SetVariables::build(value)? ) );
469 },
470 "insert" => {
471 return Ok( Replacement::Insert( InsertChildren::build(value)? ) );
472 },
473 "translate" => {
474 return Ok( Replacement::Translate( TranslateExpression::build(value)
475 .chain_err(|| "while trying to evaluate value of 'speak:'")? ) );
476 },
477 _ => {
478 bail!("Unknown 'replace' command ({}) with value: {}", key, yaml_to_string(value, 0));
479 }
480 }
481 }
482}
483
484#[derive(Debug, Clone)]
487struct InsertChildren {
488 xpath: MyXPath, replacements: ReplacementArray, }
491
492impl fmt::Display for InsertChildren {
493 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494 return write!(f, "InsertChildren:\n nodes {}\n replacements {}", self.xpath, &self.replacements);
495 }
496}
497
498impl InsertChildren {
499 fn build(insert: &Yaml) -> Result<Box<InsertChildren>> {
500 if insert.as_hash().is_none() {
502 bail!("")
503 }
504 let nodes = &insert["nodes"];
505 if nodes.is_badvalue() {
506 bail!("Missing 'nodes' as part of 'insert'.\n \
507 Suggestion: add 'nodes:' or if present, indent so it is contained in 'insert'");
508 }
509 let nodes = as_str_checked(nodes)?;
510 let replace = &insert["replace"];
511 if replace.is_badvalue() {
512 bail!("Missing 'replace' as part of 'insert'.\n \
513 Suggestion: add 'replace:' or if present, indent so it is contained in 'insert'");
514 }
515 return Ok( Box::new( InsertChildren {
516 xpath: MyXPath::new(nodes.to_string())?,
517 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
518 } ) );
519 }
520
521 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> {
530 let result = self.xpath.evaluate(&rules_with_context.context_stack.base, mathml)
531 .chain_err(||format!("in '{}' replacing after pattern match", &self.xpath.rc.string) )?;
532 match result {
533 Value::Nodeset(nodes) => {
534 if nodes.size() == 0 {
535 bail!("During replacement, no matching element found");
536 };
537 let nodes = nodes.document_order();
538 let n_nodes = nodes.len();
539 let mut expanded_result = Vec::with_capacity(n_nodes + (n_nodes+1)*self.replacements.replacements.len());
540 expanded_result.push(
541 Replacement::XPath(
542 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , 1))?
543 )
544 );
545 for i in 2..n_nodes+1 {
546 expanded_result.extend_from_slice(&self.replacements.replacements);
547 expanded_result.push(
548 Replacement::XPath(
549 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , i))?
550 )
551 );
552 }
553 let replacements = ReplacementArray{ replacements: expanded_result };
554 return replacements.replace(rules_with_context, mathml);
555 },
556
557 Value::String(t) => { return T::from_string(rules_with_context.replace_chars(&t, mathml)?, rules_with_context.doc); },
559 Value::Number(num) => { return T::from_string( num.to_string(), rules_with_context.doc ); },
560 Value::Boolean(b) => { return T::from_string( b.to_string(), rules_with_context.doc ); }, }
562
563 }
564}
565
566
567lazy_static! {
568 static ref ATTR_NAME_VALUE: Regex = Regex::new(
569 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>[^"]+)")"#
572 ).unwrap();
573}
574
575#[derive(Debug, Clone)]
578struct Intent {
579 name: Option<String>, xpath: Option<MyXPath>, attrs: String, children: ReplacementArray, }
584
585impl fmt::Display for Intent {
586 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
587 let name = if self.name.is_some() {
588 self.name.as_ref().unwrap().to_string()
589 } else {
590 self.xpath.as_ref().unwrap().to_string()
591 };
592 return write!(f, "intent: {}: {}, attrs='{}'>\n children: {}",
593 if self.name.is_some() {"name"} else {"xpath-name"}, name,
594 self.attrs,
595 &self.children);
596 }
597}
598
599impl Intent {
600 fn build(yaml_dict: &Yaml) -> Result<Box<Intent>> {
601 if yaml_dict.as_hash().is_none() {
603 bail!("Array found for contents of 'intent' -- should be dictionary with keys 'name' and 'children'")
604 }
605 let name = &yaml_dict["name"];
606 let xpath_name = &yaml_dict["xpath-name"];
607 if name.is_badvalue() && xpath_name.is_badvalue(){
608 bail!("Missing 'name' or 'xpath-name' as part of 'intent'.\n \
609 Suggestion: add 'name:' or if present, indent so it is contained in 'intent'");
610 }
611 let attrs = &yaml_dict["attrs"];
612 let replace = &yaml_dict["children"];
613 if replace.is_badvalue() {
614 bail!("Missing 'children' as part of 'intent'.\n \
615 Suggestion: add 'children:' or if present, indent so it is contained in 'intent'");
616 }
617 return Ok( Box::new( Intent {
618 name: if name.is_badvalue() {None} else {Some(as_str_checked(name).chain_err(|| "'name'")?.to_string())},
619 xpath: if xpath_name.is_badvalue() {None} else {Some(MyXPath::build(xpath_name).chain_err(|| "'intent'")?)},
620 attrs: if attrs.is_badvalue() {"".to_string()} else {as_str_checked(attrs).chain_err(|| "'attrs'")?.to_string()},
621 children: ReplacementArray::build(replace).chain_err(|| "'children:'")?,
622 } ) );
623 }
624
625 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> {
626 let result = self.children.replace::<Element<'m>>(rules_with_context, mathml)
627 .chain_err(||"replacing inside 'intent'")?;
628 let mut result = lift_children(result);
629 if name(result) != "TEMP_NAME" && name(result) != "Unknown" {
630 let temp = create_mathml_element(&result.document(), "TEMP_NAME");
632 temp.append_child(result);
633 result = temp;
634 }
635 if let Some(intent_name) = &self.name {
636 result.set_attribute_value(MATHML_FROM_NAME_ATTR, name(mathml));
637 set_mathml_name(result, intent_name.as_str());
638 }
639 if let Some(my_xpath) = &self.xpath{ let xpath_value = my_xpath.evaluate(rules_with_context.get_context(), mathml)?;
641 match xpath_value {
642 Value::String(intent_name) => {
643 result.set_attribute_value(MATHML_FROM_NAME_ATTR, name(mathml));
644 set_mathml_name(result, intent_name.as_str())
645 },
646 _ => bail!("'xpath-name' value '{}' was not a string", &my_xpath),
647 }
648 }
649 if self.name.is_none() && self.xpath.is_none() {
650 panic!("Intent::replace: internal error -- neither 'name' nor 'xpath' is set");
651 };
652
653 for attr in mathml.attributes() {
654 result.set_attribute_value(attr.name(), attr.value());
655 }
656
657 if !self.attrs.is_empty() {
658 for cap in ATTR_NAME_VALUE.captures_iter(&self.attrs) {
662 let matched_value = if cap["value"].is_empty() {&cap["dqvalue"]} else {&cap["value"]};
663 let value_as_xpath = MyXPath::new(matched_value.to_string()).chain_err(||"attr value inside 'intent'")?;
664 let value = value_as_xpath.evaluate(rules_with_context.get_context(), result)
665 .chain_err(||"attr xpath evaluation value inside 'intent'")?;
666 let mut value = value.into_string();
667 if &cap["name"] == INTENT_PROPERTY {
668 value = simplify_fixity_properties(&value);
669 }
670 if &cap["name"] == INTENT_PROPERTY && value == ":" {
672 result.remove_attribute(INTENT_PROPERTY);
674 } else {
675 result.set_attribute_value(&cap["name"], &value);
676 }
677 };
678 }
679
680 return T::from_element(result);
682
683
684 fn lift_children(result: Element) -> Element {
686 let mut new_children = Vec::with_capacity(2*result.children().len());
689 for child_of_element in result.children() {
690 match child_of_element {
691 ChildOfElement::Element(child) => {
692 if name(child) == "TEMP_NAME" {
693 new_children.append(&mut child.children()); } else {
695 new_children.push(child_of_element);
696 }
697 },
698 _ => new_children.push(child_of_element), }
700 }
701 result.replace_children(new_children);
702 return result;
703 }
704 }
705}
706
707#[derive(Debug, Clone)]
710struct With {
711 variables: VariableDefinitions, replacements: ReplacementArray, }
714
715impl fmt::Display for With {
716 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
717 return write!(f, "with:\n variables: {}\n replace: {}", &self.variables, &self.replacements);
718 }
719}
720
721impl With {
722 fn build(vars_replacements: &Yaml) -> Result<Box<With>> {
723 if vars_replacements.as_hash().is_none() {
725 bail!("Array found for contents of 'with' -- should be dictionary with keys 'variables' and 'replace'")
726 }
727 let var_defs = &vars_replacements["variables"];
728 if var_defs.is_badvalue() {
729 bail!("Missing 'variables' as part of 'with'.\n \
730 Suggestion: add 'variables:' or if present, indent so it is contained in 'with'");
731 }
732 let replace = &vars_replacements["replace"];
733 if replace.is_badvalue() {
734 bail!("Missing 'replace' as part of 'with'.\n \
735 Suggestion: add 'replace:' or if present, indent so it is contained in 'with'");
736 }
737 return Ok( Box::new( With {
738 variables: VariableDefinitions::build(var_defs).chain_err(|| "'variables'")?,
739 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
740 } ) );
741 }
742
743 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> {
744 rules_with_context.context_stack.push(self.variables.clone(), mathml)?;
745 let result = self.replacements.replace(rules_with_context, mathml)
746 .chain_err(||"replacing inside 'with'")?;
747 rules_with_context.context_stack.pop();
748 return Ok( result );
749 }
750}
751
752#[derive(Debug, Clone)]
755struct SetVariables {
756 variables: VariableDefinitions, }
758
759impl fmt::Display for SetVariables {
760 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
761 return write!(f, "SetVariables: variables {}", &self.variables);
762 }
763}
764
765impl SetVariables {
766 fn build(vars: &Yaml) -> Result<Box<SetVariables>> {
767 if vars.as_vec().is_none() {
769 bail!("'set_variables' -- should be an array of variable name, xpath value");
770 }
771 return Ok( Box::new( SetVariables {
772 variables: VariableDefinitions::build(vars).chain_err(|| "'set_variables'")?
773 } ) );
774 }
775
776 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> {
777 rules_with_context.context_stack.set_globals(self.variables.clone(), mathml)?;
778 return T::from_string( "".to_string(), rules_with_context.doc );
779 }
780}
781
782
783#[derive(Debug, Clone)]
785struct TranslateExpression {
786 id: MyXPath, }
788
789impl fmt::Display for TranslateExpression {
790 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
791 return write!(f, "speak: {}", &self.id);
792 }
793}
794impl TranslateExpression {
795 fn build(vars: &Yaml) -> Result<TranslateExpression> {
796 return Ok( TranslateExpression { id: MyXPath::build(vars).chain_err(|| "'translate'")? } );
798 }
799
800 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> {
801 if self.id.rc.string.contains('@') {
802 let xpath_value = self.id.evaluate(rules_with_context.get_context(), mathml)?;
803 let id = match xpath_value {
804 Value::String(s) => Some(s),
805 Value::Nodeset(nodes) => {
806 if nodes.size() == 1 {
807 nodes.document_order_first().unwrap().attribute().map(|attr| attr.value().to_string())
808 } else {
809 None
810 }
811 },
812 _ => None,
813 };
814 match id {
815 None => bail!("'translate' value '{}' is not a string or an attribute value (correct by using '@id'??):\n", self.id),
816 Some(id) => {
817 let speech = speak_mathml(mathml, &id)?;
818 return T::from_string(speech, rules_with_context.doc);
819 }
820 }
821 } else {
822 return T::from_string(
823 self.id.replace(rules_with_context, mathml).chain_err(||"'translate'")?,
824 rules_with_context.doc
825 );
826 }
827 }
828}
829
830
831#[derive(Debug, Clone)]
833pub struct ReplacementArray {
834 replacements: Vec<Replacement>
835}
836
837impl fmt::Display for ReplacementArray {
838 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
839 return write!(f, "{}", self.pretty_print_replacements());
840 }
841}
842
843impl ReplacementArray {
844 pub fn build_empty() -> ReplacementArray {
846 return ReplacementArray {
847 replacements: vec![]
848 }
849 }
850
851 pub fn build(replacements: &Yaml) -> Result<ReplacementArray> {
854 let result= if replacements.is_array() {
856 let replacements = replacements.as_vec().unwrap();
857 replacements
858 .iter()
859 .enumerate() .map(|(i, r)| Replacement::build(r)
861 .chain_err(|| format!("replacement #{} of {}", i+1, replacements.len())))
862 .collect::<Result<Vec<Replacement>>>()?
863 } else {
864 vec![ Replacement::build(replacements)?]
865 };
866
867 return Ok( ReplacementArray{ replacements: result } );
868 }
869
870 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> {
872 return T::replace(self, rules_with_context, mathml);
873 }
874
875 pub fn replace_array_string<'c, 's:'c, 'm:'c>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
876 let mut replacement_strings = Vec::with_capacity(self.replacements.len()); for replacement in self.replacements.iter() {
882 let string: String = rules_with_context.replace(replacement, mathml)?;
883 if !string.is_empty() {
884 replacement_strings.push(string);
885 }
886 }
887
888 if replacement_strings.is_empty() {
889 return Ok( "".to_string() );
890 }
891 for i in 1..replacement_strings.len()-1 {
899 if let Some(bytes) = is_repetitive(&replacement_strings[i-1], &replacement_strings[i]) {
900 replacement_strings[i] = bytes.to_string();
901 }
902 }
903
904 for i in 0..replacement_strings.len() {
905 if replacement_strings[i].contains(PAUSE_AUTO_STR) {
906 let before = if i == 0 {""} else {&replacement_strings[i-1]};
907 let after = if i+1 == replacement_strings.len() {""} else {&replacement_strings[i+1]};
908 replacement_strings[i] = replacement_strings[i].replace(
909 PAUSE_AUTO_STR,
910 &rules_with_context.speech_rules.pref_manager.borrow().get_tts().compute_auto_pause(&rules_with_context.speech_rules.pref_manager.borrow(), before, after));
911 }
912 }
913
914 return Ok( replacement_strings.join(" ") );
917
918 fn is_repetitive<'a>(prev: &str, optional: &'a str) -> Option<&'a str> {
919 if optional.len() <= 2 * OPTIONAL_INDICATOR_LEN {
922 return None;
923 }
924
925 match optional.find(OPTIONAL_INDICATOR) {
927 None => return None,
928 Some(start_index) => {
929 let optional_word_start_slice = &optional[start_index + OPTIONAL_INDICATOR_LEN..];
930 match optional_word_start_slice.find(OPTIONAL_INDICATOR) {
932 None => panic!("Internal error: missing end optional char -- text handling is corrupted!"),
933 Some(end_index) => {
934 let optional_word = &optional_word_start_slice[..end_index];
935 let prev = prev.trim_end().as_bytes();
938 if prev.len() > optional_word.len() &&
939 &prev[prev.len()-optional_word.len()..] == optional_word.as_bytes() {
940 return Some( optional_word_start_slice[optional_word.len() + OPTIONAL_INDICATOR_LEN..].trim_start() );
941 } else {
942 return None;
943 }
944 }
945 }
946 }
947 }
948 }
949 }
950
951 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>> {
952 if self.replacements.len() == 1 {
954 return rules_with_context.replace::<Element<'m>>(&self.replacements[0], mathml);
955 }
956
957 let new_element = create_mathml_element(&rules_with_context.doc, "Unknown"); let mut new_children = Vec::with_capacity(self.replacements.len());
959 for child in self.replacements.iter() {
960 let child = rules_with_context.replace::<Element<'m>>(child, mathml)?;
961 new_children.push(ChildOfElement::Element(child));
962 };
963 new_element.append_children(new_children);
964 return Ok(new_element);
965 }
966
967
968 pub fn is_empty(&self) -> bool {
970 return self.replacements.is_empty();
971 }
972
973 fn pretty_print_replacements(&self) -> String {
974 let mut group_string = String::with_capacity(128);
975 if self.replacements.len() == 1 {
976 group_string += &format!("[{}]", self.replacements[0]);
977 } else {
978 group_string += &self.replacements.iter()
979 .map(|replacement| format!("\n - {replacement}"))
980 .collect::<Vec<String>>()
981 .join("");
982 group_string += "\n";
983 }
984 return group_string;
985 }
986}
987
988
989
990#[derive(Debug)]
994struct RCMyXPath {
995 xpath: XPath,
996 string: String, }
998
999#[derive(Debug, Clone)]
1000pub struct MyXPath {
1001 rc: Rc<RCMyXPath> }
1003
1004
1005impl fmt::Display for MyXPath {
1006 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1007 return write!(f, "\"{}\"", self.rc.string);
1008 }
1009}
1010
1011thread_local!{
1015 static XPATH_CACHE: RefCell<HashMap<String, MyXPath>> = RefCell::new( HashMap::with_capacity(2047) );
1016}
1017impl MyXPath {
1020 fn new(xpath: String) -> Result<MyXPath> {
1021 return XPATH_CACHE.with( |cache| {
1022 let mut cache = cache.borrow_mut();
1023 return Ok(
1024 match cache.get(&xpath) {
1025 Some(compiled_xpath) => {
1026 compiled_xpath.clone()
1028 },
1029 None => {
1030 let new_xpath = MyXPath {
1031 rc: Rc::new( RCMyXPath {
1032 xpath: MyXPath::compile_xpath(&xpath)?,
1033 string: xpath.clone()
1034 })};
1035 cache.insert(xpath.clone(), new_xpath.clone());
1036 new_xpath
1037 },
1038 }
1039 )
1040 });
1041 }
1042
1043 pub fn build(xpath: &Yaml) -> Result<MyXPath> {
1044 let xpath = match xpath {
1045 Yaml::String(s) => s.to_string(),
1046 Yaml::Integer(i) => i.to_string(),
1047 Yaml::Real(s) => s.to_string(),
1048 Yaml::Boolean(s) => s.to_string(),
1049 Yaml::Array(v) =>
1050 v.iter()
1052 .map(as_str_checked)
1053 .collect::<Result<Vec<&str>>>()?
1054 .join(" "),
1055 _ => bail!("Bad value when trying to create an xpath: {}", yaml_to_string(xpath, 1)),
1056 };
1057 return MyXPath::new(xpath);
1058 }
1059
1060 fn compile_xpath(xpath: &str) -> Result<XPath> {
1061 let factory = Factory::new();
1062 let xpath_with_debug_info = MyXPath::add_debug_string_arg(xpath)?;
1063 let compiled_xpath = factory.build(&xpath_with_debug_info)
1064 .chain_err(|| format!(
1065 "Could not compile XPath for pattern:\n{}{}",
1066 &xpath, more_details(xpath)))?;
1067 return match compiled_xpath {
1068 Some(xpath) => Ok(xpath),
1069 None => bail!("Problem compiling Xpath for pattern:\n{}{}",
1070 &xpath, more_details(xpath)),
1071 };
1072
1073
1074 fn more_details(xpath: &str) -> String {
1075 if xpath.is_empty() {
1077 return "xpath is empty string".to_string();
1078 }
1079 let as_bytes = xpath.trim().as_bytes();
1080 if as_bytes[0] == b'\'' && as_bytes[as_bytes.len()-1] != b'\'' {
1081 return "\nmissing \"'\"".to_string();
1082 }
1083 if (as_bytes[0] == b'"' && as_bytes[as_bytes.len()-1] != b'"') ||
1084 (as_bytes[0] != b'"' && as_bytes[as_bytes.len()-1] == b'"'){
1085 return "\nmissing '\"'".to_string();
1086 }
1087
1088 let mut i_bytes = 0; let mut paren_count = 0; let mut i_paren = 0; let mut bracket_count = 0;
1092 let mut i_bracket = 0;
1093 for ch in xpath.chars() {
1094 if ch == '(' {
1095 if paren_count == 0 {
1096 i_paren = i_bytes;
1097 }
1098 paren_count += 1;
1099 } else if ch == '[' {
1100 if bracket_count == 0 {
1101 i_bracket = i_bytes;
1102 }
1103 bracket_count += 1;
1104 } else if ch == ')' {
1105 if paren_count == 0 {
1106 return format!("\nExtra ')' found after '{}'", &xpath[i_paren..i_bytes]);
1107 }
1108 paren_count -= 1;
1109 if paren_count == 0 && bracket_count > 0 && i_bracket > i_paren {
1110 return format!("\nUnclosed brackets found at '{}'", &xpath[i_paren..i_bytes]);
1111 }
1112 } else if ch == ']' {
1113 if bracket_count == 0 {
1114 return format!("\nExtra ']' found after '{}'", &xpath[i_bracket..i_bytes]);
1115 }
1116 bracket_count -= 1;
1117 if bracket_count == 0 && paren_count > 0 && i_paren > i_bracket {
1118 return format!("\nUnclosed parens found at '{}'", &xpath[i_bracket..i_bytes]);
1119 }
1120 }
1121 i_bytes += ch.len_utf8();
1122 }
1123 return "".to_string();
1124 }
1125 }
1126
1127 fn add_debug_string_arg(xpath: &str) -> Result<String> {
1129 let debug_start = xpath.find("DEBUG(");
1131 if debug_start.is_none() {
1132 return Ok( xpath.to_string() );
1133 }
1134
1135 let debug_start = debug_start.unwrap();
1136 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}'"))?);
1139 return Ok(before_paren);
1141
1142 fn chars_add_debug_string_arg(chars: &[char]) -> Result<String> {
1143 assert_eq!(chars[0], '(', "{} does not start with ')'", chars.iter().collect::<String>());
1148 let mut count = 1; let mut i = 1;
1150 let mut inside_quote = false;
1151 while i < chars.len() {
1152 let ch = chars[i];
1153 match ch {
1154 '\\' => {
1155 if i+1 == chars.len() {
1156 bail!("Syntax error in DEBUG: last char is escape char\n{}");
1157 }
1158 i += 1;
1159 },
1160 '\'' => inside_quote = !inside_quote,
1161 '(' => {
1162 if !inside_quote {
1163 count += 1;
1164 }
1165 },
1167 ')' => {
1168 if !inside_quote {
1169 count -= 1;
1170 if count == 0 {
1171 let arg = &chars[1..i].iter().collect::<String>();
1172 let escaped_arg = arg.replace('"', "\\\"");
1173 let processed_arg = MyXPath::add_debug_string_arg(arg)?;
1175
1176 let processed_rest = MyXPath::add_debug_string_arg(&chars[i+1..].iter().collect::<String>())?;
1178 return Ok( format!("({processed_arg}, \"{escaped_arg}\"){processed_rest}") );
1179 }
1180 }
1181 },
1182 _ => (),
1183 }
1184 i += 1;
1185 }
1186 bail!("Syntax error in DEBUG: didn't find matching closing paren\nDEBUG{}", chars.iter().collect::<String>());
1187 }
1188 }
1189
1190 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1191 return Ok(
1193 match self.evaluate(context, mathml)? {
1194 Value::Boolean(b) => b,
1195 Value::Nodeset(nodes) => nodes.size() > 0,
1196 _ => false,
1197 }
1198 )
1199 }
1200
1201 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> {
1202 if self.rc.string == "process-intent(.)" {
1203 return T::from_element( infer_intent(rules_with_context, mathml)? );
1204 }
1205
1206 let result = self.evaluate(&rules_with_context.context_stack.base, mathml)
1207 .chain_err(|| format!("in '{}' replacing after pattern match", &self.rc.string) )?;
1208 let string = match result {
1209 Value::Nodeset(nodes) => {
1210 if nodes.size() == 0 {
1211 bail!("During replacement, no matching element found");
1212 }
1213 return rules_with_context.replace_nodes(nodes.document_order(), mathml);
1214 },
1215 Value::String(s) => s,
1216 Value::Number(num) => num.to_string(),
1217 Value::Boolean(b) => b.to_string(), };
1219 let result = if self.rc.string.starts_with('$') {string} else {rules_with_context.replace_chars(&string, mathml)?};
1222 return T::from_string(result, rules_with_context.doc );
1223 }
1224
1225 pub fn evaluate<'c>(&self, context: &Context<'c>, mathml: Element<'c>) -> Result<Value<'c>> {
1226 let result = self.rc.xpath.evaluate(context, mathml);
1228 return match result {
1229 Ok(val) => Ok( val ),
1230 Err(e) => {
1231 bail!( "{}\n\n",
1233 e.to_string().replace("OwnedPrefixedName { prefix: None, local_part:", "").replace(" }", "") );
1235 }
1236 };
1237 }
1238
1239 pub fn test_input<F>(self, f: F) -> bool where F: Fn(&str) -> bool {
1240 return f(self.rc.string.as_ref());
1241 }
1242}
1243
1244#[derive(Debug)]
1249struct SpeechPattern {
1250 pattern_name: String,
1251 tag_name: String,
1252 file_name: String,
1253 pattern: MyXPath, match_uses_var_defs: bool, var_defs: VariableDefinitions, replacements: ReplacementArray, }
1258
1259impl fmt::Display for SpeechPattern {
1260 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1261 return write!(f, "[name: {}, tag: {},\n variables: {:?}, pattern: {},\n replacement: {}]",
1262 self.pattern_name, self.tag_name, self.var_defs, self.pattern,
1263 self.replacements.pretty_print_replacements());
1264 }
1265}
1266
1267impl SpeechPattern {
1268 fn build(dict: &Yaml, file: &Path, rules: &mut SpeechRules) -> Result<Option<Vec<PathBuf>>> {
1269 if let Some(include_file_name) = find_str(dict, "include") {
1275 let do_include_fn = |new_file: &Path| {
1276 rules.read_patterns(new_file)
1277 };
1278
1279 return Ok( Some(process_include(file, include_file_name, do_include_fn)?) );
1280 }
1281
1282 let pattern_name = find_str(dict, "name");
1283
1284 let mut tag_names: Vec<&str> = Vec::new();
1286 match find_str(dict, "tag") {
1287 Some(str) => tag_names.push(str),
1288 None => {
1289 let tag_array = &dict["tag"];
1291 tag_names = vec![];
1292 if tag_array.is_array() {
1293 for (i, name) in tag_array.as_vec().unwrap().iter().enumerate() {
1294 match as_str_checked(name) {
1295 Err(e) => return Err(
1296 e.chain_err(||
1297 format!("tag name '{}' is not a string in:\n{}",
1298 &yaml_to_string(&tag_array.as_vec().unwrap()[i], 0),
1299 &yaml_to_string(dict, 1)))
1300 ),
1301 Ok(str) => tag_names.push(str),
1302 };
1303 }
1304 } else {
1305 bail!("Errors trying to find 'tag' in:\n{}", &yaml_to_string(dict, 1));
1306 }
1307 }
1308 }
1309
1310 if pattern_name.is_none() {
1311 if dict.is_null() {
1312 bail!("Error trying to find 'name': empty value (two consecutive '-'s?");
1313 } else {
1314 bail!("Errors trying to find 'name' in:\n{}", &yaml_to_string(dict, 1));
1315 };
1316 };
1317 let pattern_name = pattern_name.unwrap().to_string();
1318
1319 if dict["match"].is_badvalue() {
1321 bail!("Did not find 'match' in\n{}", yaml_to_string(dict, 1));
1322 }
1323 if dict["replace"].is_badvalue() {
1324 bail!("Did not find 'replace' in\n{}", yaml_to_string(dict, 1));
1325 }
1326
1327 for tag_name in tag_names {
1329 let tag_name = tag_name.to_string();
1330 let pattern_xpath = MyXPath::build(&dict["match"])
1331 .chain_err(|| {
1332 format!("value for 'match' in rule ({}: {}):\n{}",
1333 tag_name, pattern_name, yaml_to_string(dict, 1))
1334 })?;
1335 let speech_pattern =
1336 Box::new( SpeechPattern{
1337 pattern_name: pattern_name.clone(),
1338 tag_name: tag_name.clone(),
1339 file_name: file.to_str().unwrap().to_string(),
1340 match_uses_var_defs: dict["variables"].is_array() && pattern_xpath.rc.string.contains('$'), pattern: pattern_xpath,
1342 var_defs: VariableDefinitions::build(&dict["variables"])
1343 .chain_err(|| {
1344 format!("value for 'variables' in rule ({}: {}):\n{}",
1345 tag_name, pattern_name, yaml_to_string(dict, 1))
1346 })?,
1347 replacements: ReplacementArray::build(&dict["replace"])
1348 .chain_err(|| {
1349 format!("value for 'replace' in rule ({}: {}). Replacements:\n{}",
1350 tag_name, pattern_name, yaml_to_string(&dict["replace"], 1))
1351 })?
1352 } );
1353 let rule_value = rules.rules.entry(tag_name).or_default();
1355
1356 match rule_value.iter().enumerate().find(|&pattern| pattern.1.pattern_name == speech_pattern.pattern_name) {
1358 None => rule_value.push(speech_pattern),
1359 Some((i, _old_pattern)) => {
1360 let old_rule = &rule_value[i];
1361 info!("\n\n***WARNING***: replacing {}/'{}' in {} with rule from {}\n",
1362 old_rule.tag_name, old_rule.pattern_name, old_rule.file_name, speech_pattern.file_name);
1363 rule_value[i] = speech_pattern;
1364 },
1365 }
1366 }
1367
1368 return Ok(None);
1369 }
1370
1371 fn is_match(&self, context: &Context, mathml: Element) -> Result<bool> {
1372 if self.tag_name != mathml.name().local_part() && self.tag_name != "*" && self.tag_name != "!*" {
1373 return Ok( false );
1374 }
1375
1376 return Ok(
1380 match self.pattern.evaluate(context, mathml)? {
1381 Value::Boolean(b) => b,
1382 Value::Nodeset(nodes) => nodes.size() > 0,
1383 _ => false,
1384 }
1385 );
1386 }
1387}
1388
1389
1390#[derive(Debug, Clone)]
1394struct TestArray {
1395 tests: Vec<Test>
1396}
1397
1398impl fmt::Display for TestArray {
1399 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1400 for test in &self.tests {
1401 writeln!(f, "{test}")?;
1402 }
1403 return Ok( () );
1404 }
1405}
1406
1407impl TestArray {
1408 fn build(test: &Yaml) -> Result<TestArray> {
1409 let tests = if test.as_hash().is_some() {
1414 vec![test]
1415 } else if let Some(vec) = test.as_vec() {
1416 vec.iter().collect()
1417 } else {
1418 bail!("Value for 'test:' is neither a dictionary or an array.")
1419 };
1420
1421 let mut test_array = vec![];
1427 for test in tests {
1428 if test.as_hash().is_none() {
1429 bail!("Value for array entry in 'test:' must be a dictionary/contain keys");
1430 }
1431 let if_part = &test[if test_array.is_empty() {"if"} else {"else_if"}];
1432 if !if_part.is_badvalue() {
1433 let condition = Some( MyXPath::build(if_part)? );
1435 let then_part = TestOrReplacements::build(test, "then", "then_test", true)?;
1436 let else_part = TestOrReplacements::build(test, "else", "else_test", false)?;
1437 let n_keys = if else_part.is_none() {2} else {3};
1438 if test.as_hash().unwrap().len() > n_keys {
1439 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found in the 'then' clause of 'test'");
1440 };
1441 test_array.push(
1442 Test { condition, then_part, else_part }
1443 );
1444 } else {
1445 let else_part = TestOrReplacements::build(test, "else", "else_test", true)?;
1447 if test.as_hash().unwrap().len() > 1 {
1448 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found the 'else' clause of 'test'");
1449 };
1450 test_array.push(
1451 Test { condition: None, then_part: None, else_part }
1452 );
1453
1454 if test_array.len() < test.as_hash().unwrap().len() {
1456 bail!("'else'/'else_test' key is not last key in 'test:'");
1457 }
1458 }
1459 };
1460
1461 if test_array.is_empty() {
1462 bail!("No entries for 'test:'");
1463 }
1464
1465 return Ok( TestArray { tests: test_array } );
1466 }
1467
1468 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> {
1469 for test in &self.tests {
1470 if test.is_true(&rules_with_context.context_stack.base, mathml)? {
1471 assert!(test.then_part.is_some());
1472 return test.then_part.as_ref().unwrap().replace(rules_with_context, mathml);
1473 } else if let Some(else_part) = test.else_part.as_ref() {
1474 return else_part.replace(rules_with_context, mathml);
1475 }
1476 }
1477 return T::from_string("".to_string(), rules_with_context.doc);
1478 }
1479}
1480
1481#[derive(Debug, Clone)]
1482enum TestOrReplacements {
1484 Replacements(ReplacementArray), Test(TestArray), }
1487
1488impl fmt::Display for TestOrReplacements {
1489 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1490 if let TestOrReplacements::Test(_) = self {
1491 write!(f, " _test")?;
1492 }
1493 write!(f, ":")?;
1494 return match self {
1495 TestOrReplacements::Test(t) => write!(f, "{t}"),
1496 TestOrReplacements::Replacements(r) => write!(f, "{r}"),
1497 };
1498 }
1499}
1500
1501impl TestOrReplacements {
1502 fn build(test: &Yaml, replace_key: &str, test_key: &str, key_required: bool) -> Result<Option<TestOrReplacements>> {
1503 let part = &test[replace_key];
1504 let test_part = &test[test_key];
1505 if !part.is_badvalue() && !test_part.is_badvalue() {
1506 bail!(format!("Only one of '{}' or '{}' is allowed as part of 'test'.\n{}\n \
1507 Suggestion: delete one or adjust indentation",
1508 replace_key, test_key, yaml_to_string(test, 2)));
1509 }
1510 if part.is_badvalue() && test_part.is_badvalue() {
1511 if key_required {
1512 bail!(format!("Missing one of '{}'/'{}:' as part of 'test:'\n{}\n \
1513 Suggestion: add the missing key or indent so it is contained in 'test'",
1514 replace_key, test_key, yaml_to_string(test, 2)))
1515 } else {
1516 return Ok( None );
1517 }
1518 }
1519 if test_part.is_badvalue() {
1521 return Ok( Some( TestOrReplacements::Replacements( ReplacementArray::build(part)? ) ) );
1522 } else {
1523 return Ok( Some( TestOrReplacements::Test( TestArray::build(test_part)? ) ) );
1524 }
1525 }
1526
1527 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> {
1528 return match self {
1529 TestOrReplacements::Replacements(r) => r.replace(rules_with_context, mathml),
1530 TestOrReplacements::Test(t) => t.replace(rules_with_context, mathml),
1531 }
1532 }
1533}
1534
1535#[derive(Debug, Clone)]
1536struct Test {
1537 condition: Option<MyXPath>,
1538 then_part: Option<TestOrReplacements>,
1539 else_part: Option<TestOrReplacements>,
1540}
1541impl fmt::Display for Test {
1542 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1543 write!(f, "test: [ ")?;
1544 if let Some(if_part) = &self.condition {
1545 write!(f, " if: '{if_part}'")?;
1546 }
1547 if let Some(then_part) = &self.then_part {
1548 write!(f, " then{then_part}")?;
1549 }
1550 if let Some(else_part) = &self.else_part {
1551 write!(f, " else{else_part}")?;
1552 }
1553 return write!(f, "]");
1554 }
1555}
1556
1557impl Test {
1558 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1559 return match self.condition.as_ref() {
1560 None => Ok( false ), Some(condition) => condition.is_true(context, mathml)
1562 .chain_err(|| "Failure in conditional test"),
1563 }
1564 }
1565}
1566
1567#[derive(Debug, Clone)]
1569struct VariableDefinition {
1570 name: String, value: MyXPath, }
1573
1574impl fmt::Display for VariableDefinition {
1575 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1576 return write!(f, "[name: {}={}]", self.name, self.value);
1577 }
1578}
1579
1580#[derive(Debug)]
1582struct VariableValue<'v> {
1583 name: String, value: Option<Value<'v>>, }
1586
1587impl fmt::Display for VariableValue<'_> {
1588 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1589 let value = match &self.value {
1590 None => "unset".to_string(),
1591 Some(val) => format!("{val:?}")
1592 };
1593 return write!(f, "[name: {}, value: {}]", self.name, value);
1594 }
1595}
1596
1597impl VariableDefinition {
1598 fn build(name_value_def: &Yaml) -> Result<VariableDefinition> {
1599 match name_value_def.as_hash() {
1600 Some(map) => {
1601 if map.len() != 1 {
1602 bail!("definition is not a key/value pair. Found {}",
1603 yaml_to_string(name_value_def, 1) );
1604 }
1605 let (name, value) = map.iter().next().unwrap();
1606 let name = as_str_checked( name)
1607 .chain_err(|| format!( "definition name is not a string: {}",
1608 yaml_to_string(name, 1) ))?.to_string();
1609 match value {
1610 Yaml::Boolean(_) | Yaml::String(_) | Yaml::Integer(_) | Yaml::Real(_) => (),
1611 _ => bail!("definition value is not a string, boolean, or number. Found {}",
1612 yaml_to_string(value, 1) )
1613 };
1614 return Ok(
1615 VariableDefinition{
1616 name,
1617 value: MyXPath::build(value)?
1618 }
1619 );
1620 },
1621 None => bail!("definition is not a key/value pair. Found {}",
1622 yaml_to_string(name_value_def, 1) )
1623 }
1624 }
1625}
1626
1627
1628#[derive(Debug, Clone)]
1629struct VariableDefinitions {
1630 defs: Vec<VariableDefinition>
1631}
1632
1633impl fmt::Display for VariableDefinitions {
1634 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1635 for def in &self.defs {
1636 write!(f, "{def},")?;
1637 }
1638 return Ok( () );
1639 }
1640}
1641
1642struct VariableValues<'v> {
1643 defs: Vec<VariableValue<'v>>
1644}
1645
1646impl fmt::Display for VariableValues<'_> {
1647 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1648 for value in &self.defs {
1649 write!(f, "{value}")?;
1650 }
1651 return writeln!(f);
1652 }
1653}
1654
1655impl VariableDefinitions {
1656 fn new(len: usize) -> VariableDefinitions {
1657 return VariableDefinitions{ defs: Vec::with_capacity(len) };
1658 }
1659
1660 fn build(defs: &Yaml) -> Result<VariableDefinitions> {
1661 if defs.is_badvalue() {
1662 return Ok( VariableDefinitions::new(0) );
1663 };
1664 if defs.is_array() {
1665 let defs = defs.as_vec().unwrap();
1666 let mut definitions = VariableDefinitions::new(defs.len());
1667 for def in defs {
1668 let variable_def = VariableDefinition::build(def)
1669 .chain_err(|| "definition of 'variables'")?;
1670 definitions.push( variable_def);
1671 };
1672 return Ok (definitions );
1673 }
1674 bail!( "'variables' is not an array of {{name: xpath-value}} definitions. Found {}'",
1675 yaml_to_string(defs, 1) );
1676 }
1677
1678 fn push(&mut self, var_def: VariableDefinition) {
1679 self.defs.push(var_def);
1680 }
1681
1682 fn len(&self) -> usize {
1683 return self.defs.len();
1684 }
1685}
1686
1687struct ContextStack<'c> {
1688 old_values: Vec<VariableValues<'c>>, base: Context<'c> }
1692
1693impl fmt::Display for ContextStack<'_> {
1694 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1695 writeln!(f, " {} old_values", self.old_values.len())?;
1696 for values in &self.old_values {
1697 writeln!(f, " {values}")?;
1698 }
1699 return writeln!(f);
1700 }
1701}
1702
1703impl<'c, 'r> ContextStack<'c> {
1704 fn new<'a,>(pref_manager: &'a PreferenceManager) -> ContextStack<'c> {
1705 let prefs = pref_manager.merge_prefs();
1706 let mut context_stack = ContextStack {
1707 base: ContextStack::base_context(prefs),
1708 old_values: Vec::with_capacity(31) };
1710 context_stack.base.set_variable("MatchingPause", Value::Boolean(false));
1713 context_stack.base.set_variable("IsColumnSilent", Value::Boolean(false));
1714
1715
1716 return context_stack;
1717 }
1718
1719 fn base_context(var_defs: PreferenceHashMap) -> Context<'c> {
1720 let mut context = Context::new();
1721 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
1722 crate::xpath_functions::add_builtin_functions(&mut context);
1723 for (key, value) in var_defs {
1724 context.set_variable(key.as_str(), yaml_to_value(&value));
1725 };
1731 return context;
1732 }
1733
1734 fn set_globals(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1735 for def in &new_vars.defs {
1737 let new_value = match def.value.evaluate(&self.base, mathml) {
1739 Ok(val) => val,
1740 Err(_) => bail!(format!("Can't evaluate variable def for {}", def)),
1741 };
1742 let qname = QName::new(def.name.as_str());
1743 self.base.set_variable(qname, new_value);
1744 }
1745 return Ok( () );
1746 }
1747
1748 fn push(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1749 let mut old_values = VariableValues {defs: Vec::with_capacity(new_vars.defs.len()) };
1751 let evaluation = Evaluation::new(&self.base, Node::Element(mathml));
1752 for def in &new_vars.defs {
1753 let qname = QName::new(def.name.as_str());
1755 let old_value = evaluation.value_of(qname).cloned();
1756 old_values.defs.push( VariableValue{ name: def.name.clone(), value: old_value} );
1757 }
1758
1759 for def in &new_vars.defs {
1761 let new_value = match def.value.evaluate(&self.base, mathml) {
1763 Ok(val) => val,
1764 Err(_) => Value::Nodeset(sxd_xpath::nodeset::Nodeset::new()),
1765 };
1766 let qname = QName::new(def.name.as_str());
1767 self.base.set_variable(qname, new_value);
1768 }
1769 self.old_values.push(old_values);
1770 return Ok( () );
1771 }
1772
1773 fn pop(&mut self) {
1774 const MISSING_VALUE: &str = "-- unset value --"; let old_values = self.old_values.pop().unwrap();
1776 for variable in old_values.defs {
1777 let qname = QName::new(&variable.name);
1778 let old_value = match variable.value {
1779 None => Value::String(MISSING_VALUE.to_string()),
1780 Some(val) => val,
1781 };
1782 self.base.set_variable(qname, old_value);
1783 }
1784 }
1785}
1786
1787
1788fn yaml_to_value<'b>(yaml: &Yaml) -> Value<'b> {
1789 return match yaml {
1790 Yaml::String(s) => Value::String(s.clone()),
1791 Yaml::Boolean(b) => Value::Boolean(*b),
1792 Yaml::Integer(i) => Value::Number(*i as f64),
1793 Yaml::Real(s) => Value::Number(s.parse::<f64>().unwrap()),
1794 _ => {
1795 error!("yaml_to_value: illegal type found in Yaml value: {}", yaml_to_string(yaml, 1));
1796 Value::String("".to_string())
1797 },
1798 }
1799}
1800
1801
1802struct UnicodeDef {
1804 ch: u32,
1805 speech: ReplacementArray
1806}
1807
1808impl fmt::Display for UnicodeDef {
1809 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1810 return write!(f, "UnicodeDef{{ch: {}, speech: {:?}}}", self.ch, self.speech);
1811 }
1812}
1813
1814impl UnicodeDef {
1815 fn build(unicode_def: &Yaml, file_name: &Path, speech_rules: &SpeechRules, use_short: bool) -> Result<Option<Vec<PathBuf>>> {
1816 if let Some(include_file_name) = find_str(unicode_def, "include") {
1817 let do_include_fn = |new_file: &Path| {
1818 speech_rules.read_unicode(Some(new_file.to_path_buf()), use_short)
1819 };
1820 return Ok( Some(process_include(file_name, include_file_name, do_include_fn)?) );
1821 }
1822 let dictionary = unicode_def.as_hash();
1824 if dictionary.is_none() {
1825 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1826 }
1827
1828 let dictionary = dictionary.unwrap();
1829 if dictionary.len() != 1 {
1830 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1831 }
1832
1833 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)))?;
1834 let mut unicode_table = if use_short {
1835 speech_rules.unicode_short.borrow_mut()
1836 } else {
1837 speech_rules.unicode_full.borrow_mut()
1838 };
1839 if let Some(str) = ch.as_str() {
1840 if str.is_empty() {
1841 bail!("Empty character definition. Replacement is {}", replacements.as_str().unwrap());
1842 }
1843 let mut chars = str.chars();
1844 let first_ch = chars.next().unwrap(); if chars.next().is_some() { if str.contains('-') {
1847 return process_range(str, replacements, unicode_table);
1848 } else if first_ch != '0' { for ch in str.chars() { let ch_as_str = ch.to_string();
1851 if unicode_table.insert(ch as u32, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1852 .chain_err(|| format!("In definition of char: '{str}'"))?.replacements).is_some() {
1853 error!("*** Character '{}' (0x{:X}) is repeated", ch, ch as u32);
1854 }
1855 }
1856 return Ok(None);
1857 }
1858 }
1859 }
1860
1861 let ch = UnicodeDef::get_unicode_char(ch)?;
1862 if unicode_table.insert(ch, ReplacementArray::build(replacements)
1863 .chain_err(|| format!("In definition of char: '{}' (0x{})",
1864 char::from_u32(ch).unwrap(), ch))?.replacements).is_some() {
1865 error!("*** Character '{}' (0x{:X}) is repeated", char::from_u32(ch).unwrap(), ch);
1866 }
1867 return Ok(None);
1868
1869 fn process_range(def_range: &str, replacements: &Yaml, mut unicode_table: RefMut<HashMap<u32,Vec<Replacement>>>) -> Result<Option<Vec<PathBuf>>> {
1870 let mut range = def_range.split('-');
1873 let first = range.next().unwrap().chars().next().unwrap() as u32;
1874 let last = range.next().unwrap().chars().next().unwrap() as u32;
1875 if range.next().is_some() {
1876 bail!("Character range definition has more than one '-': '{}'", def_range);
1877 }
1878
1879 for ch in first..last+1 {
1880 let ch_as_str = char::from_u32(ch).unwrap().to_string();
1881 unicode_table.insert(ch, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1882 .chain_err(|| format!("In definition of char: '{def_range}'"))?.replacements);
1883 };
1884
1885 return Ok(None)
1886 }
1887
1888 fn substitute_ch(yaml: &Yaml, ch: &str) -> Yaml {
1889 return match yaml {
1890 Yaml::Array(ref v) => {
1891 Yaml::Array(
1892 v.iter()
1893 .map(|e| substitute_ch(e, ch))
1894 .collect::<Vec<Yaml>>()
1895 )
1896 },
1897 Yaml::Hash(ref h) => {
1898 Yaml::Hash(
1899 h.iter()
1900 .map(|(key,val)| (key.clone(), substitute_ch(val, ch)) )
1901 .collect::<Hash>()
1902 )
1903 },
1904 Yaml::String(s) => Yaml::String( s.replace('.', ch) ),
1905 _ => yaml.clone(),
1906 }
1907 }
1908 }
1909
1910 fn get_unicode_char(ch: &Yaml) -> Result<u32> {
1911 if let Some(ch) = ch.as_str() {
1913 let mut ch_iter = ch.chars();
1914 let unicode_ch = ch_iter.next();
1915 if unicode_ch.is_none() || ch_iter.next().is_some() {
1916 bail!("Wanted unicode char, found string '{}')", ch);
1917 };
1918 return Ok( unicode_ch.unwrap() as u32 );
1919 }
1920
1921 if let Some(num) = ch.as_i64() {
1922 return Ok( num as u32 );
1923 }
1924 bail!("Unicode character '{}' can't be converted to an code point", yaml_to_string(ch, 0));
1925 }
1926}
1927
1928type RuleTable = HashMap<String, Vec<Box<SpeechPattern>>>;
1936 type UnicodeTable = Rc<RefCell<HashMap<u32,Vec<Replacement>>>>;
1937 type FilesAndTimesShared = Rc<RefCell<FilesAndTimes>>;
1938
1939 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1940 pub enum RulesFor {
1941 Intent,
1942 Speech,
1943 OverView,
1944 Navigation,
1945 Braille,
1946 }
1947
1948 impl fmt::Display for RulesFor {
1949 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1950 let name = match self {
1951 RulesFor::Intent => "Intent",
1952 RulesFor::Speech => "Speech",
1953 RulesFor::OverView => "OverView",
1954 RulesFor::Navigation => "Navigation",
1955 RulesFor::Braille => "Braille",
1956 };
1957 return write!(f, "{name}");
1958 }
1959 }
1960
1961
1962#[derive(Debug, Clone)]
1963pub struct FileAndTime {
1964 file: PathBuf,
1965 time: SystemTime,
1966}
1967
1968impl FileAndTime {
1969 fn new(file: PathBuf) -> FileAndTime {
1970 return FileAndTime {
1971 file,
1972 time: SystemTime::UNIX_EPOCH,
1973 }
1974 }
1975
1976 pub fn debug_get_file(&self) -> Option<&str> {
1978 return self.file.to_str();
1979 }
1980
1981 pub fn new_with_time(file: PathBuf) -> FileAndTime {
1982 return FileAndTime {
1983 time: FileAndTime::get_metadata(&file),
1984 file,
1985 }
1986 }
1987
1988 pub fn is_up_to_date(&self) -> bool {
1989 let file_mod_time = FileAndTime::get_metadata(&self.file);
1990 return self.time >= file_mod_time;
1991 }
1992
1993 fn get_metadata(path: &Path) -> SystemTime {
1994 use std::fs;
1995 if !cfg!(target_family = "wasm") {
1996 let metadata = fs::metadata(path);
1997 if let Ok(metadata) = metadata {
1998 if let Ok(mod_time) = metadata.modified() {
1999 return mod_time;
2000 }
2001 }
2002 }
2003 return SystemTime::UNIX_EPOCH
2004 }
2005
2006}
2007#[derive(Debug, Default)]
2008pub struct FilesAndTimes {
2009 ft: Vec<FileAndTime>
2013}
2014
2015impl FilesAndTimes {
2016 pub fn new(start_path: PathBuf) -> FilesAndTimes {
2017 let mut ft = Vec::with_capacity(8);
2018 ft.push( FileAndTime::new(start_path) );
2019 return FilesAndTimes{ ft };
2020 }
2021
2022 pub fn is_file_up_to_date(&self, pref_path: &Path, should_ignore_file_time: bool) -> bool {
2024
2025 if self.ft.is_empty() || self.as_path() != pref_path {
2027 return false;
2028 }
2029 if should_ignore_file_time || cfg!(target_family = "wasm") {
2030 return true;
2031 }
2032 if self.ft[0].time == SystemTime::UNIX_EPOCH {
2033 return false;
2034 }
2035
2036
2037 for file in &self.ft {
2039 if !file.is_up_to_date() {
2040 return false;
2041 }
2042 }
2043 return true;
2044 }
2045
2046 fn set_files_and_times(&mut self, new_files: Vec<PathBuf>) {
2047 self.ft.clear();
2048 for path in new_files {
2049 let time = FileAndTime::get_metadata(&path); self.ft.push( FileAndTime{ file: path, time })
2051 }
2052 }
2053
2054 pub fn as_path(&self) -> &Path {
2055 assert!(!self.ft.is_empty());
2056 return &self.ft[0].file;
2057 }
2058
2059 pub fn paths(&self) -> Vec<PathBuf> {
2060 return self.ft.iter().map(|ft| ft.file.clone()).collect::<Vec<PathBuf>>();
2061 }
2062
2063}
2064
2065
2066pub struct SpeechRules {
2072 error: String,
2073 name: RulesFor,
2074 pub pref_manager: Rc<RefCell<PreferenceManager>>,
2075 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, }
2084
2085impl fmt::Display for SpeechRules {
2086 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2087 writeln!(f, "SpeechRules '{}'\n{})", self.name, self.pref_manager.borrow())?;
2088 let mut rules_vec: Vec<(&String, &Vec<Box<SpeechPattern>>)> = self.rules.iter().collect();
2089 rules_vec.sort_by(|(tag_name1, _), (tag_name2, _)| tag_name1.cmp(tag_name2));
2090 for (tag_name, rules) in rules_vec {
2091 writeln!(f, " {}: #patterns {}", tag_name, rules.len())?;
2092 };
2093 return writeln!(f, " {}+{} unicode entries", &self.unicode_short.borrow().len(), &self.unicode_full.borrow().len());
2094 }
2095}
2096
2097
2098pub struct SpeechRulesWithContext<'c, 's:'c, 'm:'c> {
2102 speech_rules: &'s SpeechRules,
2103 context_stack: ContextStack<'c>, doc: Document<'m>,
2105 nav_node_id: &'m str,
2106 pub inside_spell: bool, pub translate_count: usize, }
2109
2110impl<'c, 's:'c, 'm:'c> fmt::Display for SpeechRulesWithContext<'c, 's,'m> {
2111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2112 writeln!(f, "SpeechRulesWithContext \n{})", self.speech_rules)?;
2113 return writeln!(f, " {} context entries, nav node id '{}'", &self.context_stack, self.nav_node_id);
2114 }
2115}
2116
2117thread_local!{
2118 static SPEECH_UNICODE_SHORT: UnicodeTable =
2120 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2121
2122 static SPEECH_UNICODE_FULL: UnicodeTable =
2124 Rc::new( RefCell::new( HashMap::with_capacity(6500) ) );
2125
2126 static BRAILLE_UNICODE_SHORT: UnicodeTable =
2128 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2129
2130 static BRAILLE_UNICODE_FULL: UnicodeTable =
2132 Rc::new( RefCell::new( HashMap::with_capacity(5000) ) );
2133
2134 static SPEECH_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2136 Rc::new( RefCell::new(FilesAndTimes::default()) );
2137
2138 static BRAILLE_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2140 Rc::new( RefCell::new(FilesAndTimes::default()) );
2141
2142 static SPEECH_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2144 Rc::new( RefCell::new(FilesAndTimes::default()) );
2145
2146 static SPEECH_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2148 Rc::new( RefCell::new(FilesAndTimes::default()) );
2149
2150 static BRAILLE_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2152 Rc::new( RefCell::new(FilesAndTimes::default()) );
2153
2154 static BRAILLE_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2156 Rc::new( RefCell::new(FilesAndTimes::default()) );
2157
2158 pub static INTENT_RULES: RefCell<SpeechRules> =
2161 RefCell::new( SpeechRules::new(RulesFor::Intent, true) );
2162
2163 pub static SPEECH_RULES: RefCell<SpeechRules> =
2164 RefCell::new( SpeechRules::new(RulesFor::Speech, true) );
2165
2166 pub static OVERVIEW_RULES: RefCell<SpeechRules> =
2167 RefCell::new( SpeechRules::new(RulesFor::OverView, true) );
2168
2169 pub static NAVIGATION_RULES: RefCell<SpeechRules> =
2170 RefCell::new( SpeechRules::new(RulesFor::Navigation, true) );
2171
2172 pub static BRAILLE_RULES: RefCell<SpeechRules> =
2173 RefCell::new( SpeechRules::new(RulesFor::Braille, false) );
2174}
2175
2176impl SpeechRules {
2177 pub fn new(name: RulesFor, translate_single_chars_only: bool) -> SpeechRules {
2178 let globals = if name == RulesFor::Braille {
2179 (
2180 (BRAILLE_UNICODE_SHORT.with(Rc::clone), BRAILLE_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2181 (BRAILLE_UNICODE_FULL. with(Rc::clone), BRAILLE_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2182 BRAILLE_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2183 )
2184 } else {
2185 (
2186 (SPEECH_UNICODE_SHORT.with(Rc::clone), SPEECH_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2187 (SPEECH_UNICODE_FULL. with(Rc::clone), SPEECH_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2188 SPEECH_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2189 )
2190 };
2191
2192 return SpeechRules {
2193 error: Default::default(),
2194 name,
2195 rules: HashMap::with_capacity(if name == RulesFor::Intent || name == RulesFor::Speech {500} else {50}), rule_files: FilesAndTimes::default(),
2197 unicode_short: globals.0.0, unicode_short_files: globals.0.1,
2199 unicode_full: globals.1.0, unicode_full_files: globals.1.1,
2201 definitions_files: globals.2,
2202 translate_single_chars_only,
2203 pref_manager: PreferenceManager::get(),
2204 };
2205}
2206
2207 pub fn get_error(&self) -> Option<&str> {
2208 return if self.error.is_empty() {
2209 None
2210 } else {
2211 Some(&self.error)
2212 }
2213 }
2214
2215 pub fn read_files(&mut self) -> Result<()> {
2216 let check_rule_files = self.pref_manager.borrow().pref_to_string("CheckRuleFiles");
2217 if check_rule_files != "None" { self.pref_manager.borrow_mut().set_preference_files()?;
2219 }
2220 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) {
2223 self.rules.clear();
2224 let files_read = self.read_patterns(&rule_file)?;
2225 self.rule_files.set_files_and_times(files_read);
2226 }
2227
2228 let pref_manager = self.pref_manager.borrow();
2229 let unicode_pref_files = if self.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2230
2231 if !self.unicode_short_files.borrow().is_file_up_to_date(unicode_pref_files.0, should_ignore_file_time) {
2232 self.unicode_short.borrow_mut().clear();
2233 self.unicode_short_files.borrow_mut().set_files_and_times(self.read_unicode(None, true)?);
2234 }
2235
2236 if self.definitions_files.borrow().ft.is_empty() || !self.definitions_files.borrow().is_file_up_to_date(
2237 pref_manager.get_definitions_file(self.name != RulesFor::Braille),
2238 should_ignore_file_time
2239 ) {
2240 self.definitions_files.borrow_mut().set_files_and_times(read_definitions_file(self.name != RulesFor::Braille)?);
2241 }
2242 return Ok( () );
2243 }
2244
2245 fn read_patterns(&mut self, path: &Path) -> Result<Vec<PathBuf>> {
2246 let rule_file_contents = read_to_string_shim(path).chain_err(|| format!("cannot read file '{}'", path.to_str().unwrap()))?;
2248 let rules_build_fn = |pattern: &Yaml| {
2249 self.build_speech_patterns(pattern, path)
2250 .chain_err(||format!("in file {:?}", path.to_str().unwrap()))
2251 };
2252 return compile_rule(&rule_file_contents, rules_build_fn)
2253 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2254 }
2255
2256 fn build_speech_patterns(&mut self, patterns: &Yaml, file_name: &Path) -> Result<Vec<PathBuf>> {
2257 let patterns_vec = patterns.as_vec();
2259 if patterns_vec.is_none() {
2260 bail!(yaml_type_err(patterns, "array"));
2261 }
2262 let patterns_vec = patterns.as_vec().unwrap();
2263 let mut files_read = vec![file_name.to_path_buf()];
2264 for entry in patterns_vec.iter() {
2265 if let Some(mut added_files) = SpeechPattern::build(entry, file_name, self)? {
2266 files_read.append(&mut added_files);
2267 }
2268 }
2269 return Ok(files_read)
2270 }
2271
2272 fn read_unicode(&self, path: Option<PathBuf>, use_short: bool) -> Result<Vec<PathBuf>> {
2273 let path = match path {
2274 Some(p) => p,
2275 None => {
2276 let pref_manager = self.pref_manager.borrow();
2278 let unicode_files = if self.name == RulesFor::Braille {
2279 pref_manager.get_braille_unicode_file()
2280 } else {
2281 pref_manager.get_speech_unicode_file()
2282 };
2283 let unicode_files = if use_short {unicode_files.0} else {unicode_files.1};
2284 unicode_files.to_path_buf()
2285 }
2286 };
2287
2288 let unicode_file_contents = read_to_string_shim(&path)?;
2291 let unicode_build_fn = |unicode_def_list: &Yaml| {
2292 let unicode_defs = unicode_def_list.as_vec();
2293 if unicode_defs.is_none() {
2294 bail!("File '{}' does not begin with an array", yaml_to_type(unicode_def_list));
2295 };
2296 let mut files_read = vec![path.to_path_buf()];
2297 for unicode_def in unicode_defs.unwrap() {
2298 if let Some(mut added_files) = UnicodeDef::build(unicode_def, &path, self, use_short)
2299 .chain_err(|| {format!("In file {:?}", path.to_str())})? {
2300 files_read.append(&mut added_files);
2301 }
2302 };
2303 return Ok(files_read)
2304 };
2305
2306 return compile_rule(&unicode_file_contents, unicode_build_fn)
2307 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2308 }
2309
2310 pub fn print_sizes() -> String {
2311 let mut answer = rule_size(&SPEECH_RULES, "SPEECH_RULES");
2319 answer += &rule_size(&INTENT_RULES, "INTENT_RULES");
2320 answer += &rule_size(&BRAILLE_RULES, "BRAILLE_RULES");
2321 answer += &rule_size(&NAVIGATION_RULES, "NAVIGATION_RULES");
2322 answer += &rule_size(&OVERVIEW_RULES, "OVERVIEW_RULES");
2323 SPEECH_RULES.with_borrow(|rule| {
2324 answer += &format!("Speech Unicode tables: short={}/{}, long={}/{}\n",
2325 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2326 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2327 });
2328 BRAILLE_RULES.with_borrow(|rule| {
2329 answer += &format!("Braille Unicode tables: short={}/{}, long={}/{}\n",
2330 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2331 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2332 });
2333 return answer;
2334
2335 fn rule_size(rules: &'static std::thread::LocalKey<RefCell<SpeechRules>>, name: &str) -> String {
2336 rules.with_borrow(|rule| {
2337 let hash_map = &rule.rules;
2338 return format!("{}: {}/{}\n", name, hash_map.len(), hash_map.capacity());
2339 })
2340 }
2341 }
2342}
2343
2344
2345impl<'c, 's:'c, 'r, 'm:'c> SpeechRulesWithContext<'c, 's,'m> {
2350 pub fn new(speech_rules: &'s SpeechRules, doc: Document<'m>, nav_node_id: &'m str) -> SpeechRulesWithContext<'c, 's, 'm> {
2351 return SpeechRulesWithContext {
2352 speech_rules,
2353 context_stack: ContextStack::new(&speech_rules.pref_manager.borrow()),
2354 doc,
2355 nav_node_id,
2356 inside_spell: false,
2357 translate_count: 0,
2358 }
2359 }
2360
2361 pub fn get_rules(&mut self) -> &SpeechRules {
2362 return self.speech_rules;
2363 }
2364
2365 pub fn get_context(&mut self) -> &mut Context<'c> {
2366 return &mut self.context_stack.base;
2367 }
2368
2369 pub fn get_document(&mut self) -> Document<'m> {
2370 return self.doc;
2371 }
2372
2373 pub fn match_pattern<T:TreeOrString<'c, 'm, T>>(&'r mut self, mathml: Element<'c>) -> Result<T> {
2374 let tag_name = mathml.name().local_part();
2376 let rules = &self.speech_rules.rules;
2377
2378 if let Some(rule_vector) = rules.get("!*") {
2380 if let Some(result) = self.find_match(rule_vector, mathml)? {
2381 return Ok(result); }
2383 }
2384
2385 if let Some(rule_vector) = rules.get(tag_name) {
2386 if let Some(result) = self.find_match(rule_vector, mathml)? {
2387 return Ok(result); }
2389 }
2390
2391 if let Some(rule_vector) = rules.get("*") {
2393 if let Some(result) = self.find_match(rule_vector, mathml)? {
2394 return Ok(result); }
2396 }
2397
2398 let speech_manager = self.speech_rules.pref_manager.borrow();
2401 let file_name = speech_manager.get_rule_file(&self.speech_rules.name);
2402 bail!("\nNo match found!\nMissing patterns in {} for MathML.\n{}", file_name.to_string_lossy(), mml_to_string(mathml));
2404 }
2405
2406 fn find_match<T:TreeOrString<'c, 'm, T>>(&'r mut self, rule_vector: &[Box<SpeechPattern>], mathml: Element<'c>) -> Result<Option<T>> {
2407 for pattern in rule_vector {
2408 if pattern.match_uses_var_defs {
2412 self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2413 }
2414 if pattern.is_match(&self.context_stack.base, mathml)
2415 .chain_err(|| error_string(pattern, mathml) )? {
2416 if !pattern.match_uses_var_defs && pattern.var_defs.len() > 0 { self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2419 }
2420 let result: Result<T> = pattern.replacements.replace(self, mathml);
2421 if pattern.var_defs.len() > 0 {
2422 self.context_stack.pop();
2423 }
2424 return match result {
2425 Ok(s) => {
2426 if self.nav_node_id.is_empty() {
2428 Ok( Some(s) )
2429 } else {
2430 Ok ( Some(self.nav_node_adjust(s, mathml)) )
2432 }
2433 },
2434 Err(e) => Err( e.chain_err(||
2435 format!(
2436 "attempting replacement pattern: \"{}\" for \"{}\".\n\
2437 Replacement\n{}\n...due to matching the MathML\n{} with the pattern\n\
2438 {}\n\
2439 The patterns are in {}.\n",
2440 pattern.pattern_name, pattern.tag_name,
2441 pattern.replacements.pretty_print_replacements(),
2442 mml_to_string(mathml), pattern.pattern,
2443 pattern.file_name
2444 )
2445 ))
2446 }
2447 } else if pattern.match_uses_var_defs {
2448 self.context_stack.pop();
2449 }
2450 };
2451 return Ok(None); fn error_string(pattern: &SpeechPattern, mathml: Element) -> String {
2454 return format!(
2455 "error during pattern match using: \"{}\" for \"{}\".\n\
2456 Pattern is \n{}\nMathML for the match:\n\
2457 {}\
2458 The patterns are in {}.\n",
2459 pattern.pattern_name, pattern.tag_name,
2460 pattern.pattern,
2461 mml_to_string(mathml),
2462 pattern.file_name
2463 );
2464 }
2465
2466 }
2467
2468 fn nav_node_adjust<T:TreeOrString<'c, 'm, T>>(&self, speech: T, mathml: Element<'c>) -> T {
2469 if let Some(id) = mathml.attribute_value("id") {
2470 if self.nav_node_id == id {
2471 if self.speech_rules.name == RulesFor::Braille {
2472 let highlight_style = self.speech_rules.pref_manager.borrow().pref_to_string("BrailleNavHighlight");
2473 return T::highlight_braille(speech, highlight_style);
2474 } else {
2475 return T::mark_nav_speech(speech)
2476 }
2477 }
2478 }
2479 return speech;
2480
2481 }
2482
2483 fn highlight_braille_string(braille: String, highlight_style: String) -> String {
2484 if &highlight_style == "Off" || braille.is_empty() {
2486 return braille;
2487 }
2488
2489 let mut chars = braille.chars().collect::<Vec<char>>();
2492
2493 let baseline_indicator_hack = PreferenceManager::get().borrow().pref_to_string("BrailleCode") == "Nemeth";
2495 let mut i_first_modified = 0;
2497 for (i, ch) in chars.iter_mut().enumerate() {
2498 let modified_ch = add_dots_to_braille_char(*ch, baseline_indicator_hack);
2499 if *ch != modified_ch {
2500 *ch = modified_ch;
2501 i_first_modified = i;
2502 break;
2503 };
2504 };
2505
2506 let mut i_last_modified = i_first_modified;
2507 if &highlight_style != "FirstChar" {
2508 for i in (i_first_modified..chars.len()).rev(){
2510 let ch = chars[i];
2511 let modified_ch = add_dots_to_braille_char(ch, baseline_indicator_hack);
2512 chars[i] = modified_ch;
2513 if ch != modified_ch {
2514 i_last_modified = i;
2515 break;
2516 }
2517 }
2518 }
2519
2520 if &highlight_style == "All" {
2521 #[allow(clippy::needless_range_loop)] for i in i_first_modified+1..i_last_modified {
2524 chars[i] = add_dots_to_braille_char(chars[i], baseline_indicator_hack);
2525 };
2526 }
2527
2528 let result = chars.into_iter().collect::<String>();
2529 return result;
2531
2532 fn add_dots_to_braille_char(ch: char, baseline_indicator_hack: bool) -> char {
2533 let as_u32 = ch as u32;
2534 if (0x2800..0x28FF).contains(&as_u32) {
2535 return unsafe {char::from_u32_unchecked(as_u32 | 0xC0)};
2536 } else if baseline_indicator_hack && ch == 'b' {
2537 return '𝑏'
2538 } else {
2539 return ch;
2540 }
2541 }
2542 }
2543
2544 fn mark_nav_speech(speech: String) -> String {
2545 return "[[".to_string() + &speech + "]]";
2548 }
2549
2550 fn replace<T:TreeOrString<'c, 'm, T>>(&'r mut self, replacement: &Replacement, mathml: Element<'c>) -> Result<T> {
2551 return Ok(
2552 match replacement {
2553 Replacement::Text(t) => T::from_string(t.clone(), self.doc)?,
2554 Replacement::XPath(xpath) => xpath.replace(self, mathml)?,
2555 Replacement::TTS(tts) => {
2556 T::from_string(
2557 self.speech_rules.pref_manager.borrow().get_tts().replace(tts, &self.speech_rules.pref_manager.borrow(), self, mathml)?,
2558 self.doc
2559 )?
2560 },
2561 Replacement::Intent(intent) => {
2562 intent.replace(self, mathml)?
2563 },
2564 Replacement::Test(test) => {
2565 test.replace(self, mathml)?
2566 },
2567 Replacement::With(with) => {
2568 with.replace(self, mathml)?
2569 },
2570 Replacement::SetVariables(vars) => {
2571 vars.replace(self, mathml)?
2572 },
2573 Replacement::Insert(ic) => {
2574 ic.replace(self, mathml)?
2575 },
2576 Replacement::Translate(id) => {
2577 id.replace(self, mathml)?
2578 },
2579 }
2580 )
2581 }
2582
2583 fn replace_nodes<T:TreeOrString<'c, 'm, T>>(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T> {
2587 return T::replace_nodes(self, nodes, mathml);
2588 }
2589
2590 fn replace_nodes_tree(&'r mut self, nodes: Vec<Node<'c>>, _mathml: Element<'c>) -> Result<Element<'m>> {
2593 let mut children = Vec::with_capacity(3*nodes.len()); for node in nodes {
2595 let matched = match node {
2596 Node::Element(n) => self.match_pattern::<Element<'m>>(n)?,
2597 Node::Text(t) => {
2598 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2599 leaf.set_text(t.text());
2601 leaf
2602 },
2603 Node::Attribute(attr) => {
2604 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2606 leaf.set_text(attr.value());
2607 leaf
2608 },
2609 _ => {
2610 bail!("replace_nodes: found unexpected node type!!!");
2611 },
2612 };
2613 children.push(matched);
2614 }
2615
2616 let result = create_mathml_element(&self.doc, "TEMP_NAME"); result.append_children(children);
2618 return Ok( result );
2620 }
2621
2622 fn replace_nodes_string(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
2623 let mut result = String::with_capacity(3*nodes.len()); let mut first_time = true;
2626 for node in nodes {
2627 if first_time {
2628 first_time = false;
2629 } else {
2630 result.push(' ');
2631 };
2632 let matched = match node {
2633 Node::Element(n) => self.match_pattern::<String>(n)?,
2634 Node::Text(t) => self.replace_chars(t.text(), mathml)?,
2635 Node::Attribute(attr) => self.replace_chars(attr.value(), mathml)?,
2636 _ => bail!("replace_nodes: found unexpected node type!!!"),
2637 };
2638 result += &matched;
2639 }
2640 return Ok( result );
2641 }
2642
2643 pub fn replace_chars(&'r mut self, str: &str, mathml: Element<'c>) -> Result<String> {
2646 if is_quoted_string(str) {
2647 return Ok(unquote_string(str).to_string());
2648 }
2649 let rules = self.speech_rules;
2650 let mut chars = str.chars();
2651 if rules.translate_single_chars_only {
2653 let ch = chars.next().unwrap_or(' ');
2654 if chars.next().is_none() {
2655 return replace_single_char(self, ch, mathml)
2657 } else {
2658 return Ok(str.replace('\u{00A0}', " ").replace(['\u{2061}', '\u{2062}', '\u{2063}', '\u{2064}'], ""))
2660 }
2661 };
2662
2663 let result = chars
2664 .map(|ch| replace_single_char(self, ch, mathml))
2665 .collect::<Result<Vec<String>>>()?
2666 .join("");
2667 return Ok( result );
2668
2669 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> {
2670 let ch_as_u32 = ch as u32;
2671 let rules = rules_with_context.speech_rules;
2672 let mut unicode = rules.unicode_short.borrow();
2673 let mut replacements = unicode.get( &ch_as_u32 );
2674 if replacements.is_none() {
2675 let pref_manager = rules.pref_manager.borrow();
2677 let unicode_pref_files = if rules.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2678 let should_ignore_file_time = pref_manager.pref_to_string("CheckRuleFiles") == "All";
2679 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) {
2680 info!("*** Loading full unicode {} for char '{}'/{:#06x}", rules.name, ch, ch_as_u32);
2681 rules.unicode_full.borrow_mut().clear();
2682 rules.unicode_full_files.borrow_mut().set_files_and_times(rules.read_unicode(None, false)?);
2683 info!("# Unicode defs = {}/{}", rules.unicode_short.borrow().len(), rules.unicode_full.borrow().len());
2684 }
2685 unicode = rules.unicode_full.borrow();
2686 replacements = unicode.get( &ch_as_u32 );
2687 if replacements.is_none() {
2688 rules_with_context.translate_count = 0; return Ok(String::from(ch)); }
2692 };
2693
2694 let result = replacements.unwrap()
2696 .iter()
2697 .map(|replacement|
2698 rules_with_context.replace(replacement, mathml)
2699 .chain_err(|| format!("Unicode replacement error: {replacement}")) )
2700 .collect::<Result<Vec<String>>>()?
2701 .join(" ");
2702 rules_with_context.translate_count = 0; return Ok(result);
2704 }
2705 }
2706}
2707
2708pub fn braille_replace_chars(str: &str, mathml: Element) -> Result<String> {
2710 return BRAILLE_RULES.with(|rules| {
2711 let rules = rules.borrow();
2712 let new_package = Package::new();
2713 let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), "");
2714 return rules_with_context.replace_chars(str, mathml);
2715 })
2716}
2717
2718
2719
2720#[cfg(test)]
2721mod tests {
2722 #[allow(unused_imports)]
2723 use crate::init_logger;
2724
2725 use super::*;
2726
2727 #[test]
2728 fn test_read_statement() {
2729 let str = r#"---
2730 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2731 let doc = YamlLoader::load_from_str(str).unwrap();
2732 assert_eq!(doc.len(), 1);
2733 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2734
2735 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2736 assert_eq!(rules.rules["math"].len(), 1, "\nshould only be one rule");
2737
2738 let speech_pattern = &rules.rules["math"][0];
2739 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2740 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2741 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2742 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2743 assert_eq!(speech_pattern.replacements.replacements[0].to_string(), r#""./*""#, "\nreplacement failure");
2744 }
2745
2746 #[test]
2747 fn test_read_statements_with_replace() {
2748 let str = r#"---
2749 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2750 let doc = YamlLoader::load_from_str(str).unwrap();
2751 assert_eq!(doc.len(), 1);
2752 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2753 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2754
2755 let str = r#"---
2756 {name: default, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2757 let doc2 = YamlLoader::load_from_str(str).unwrap();
2758 assert_eq!(doc2.len(), 1);
2759 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2760 assert_eq!(rules.rules["math"].len(), 1, "\nfirst rule not replaced");
2761
2762 let speech_pattern = &rules.rules["math"][0];
2763 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2764 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2765 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2766 assert_eq!(speech_pattern.replacements.replacements.len(), 2, "\nreplacement failure");
2767 }
2768
2769 #[test]
2770 fn test_read_statements_with_add() {
2771 let str = r#"---
2772 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2773 let doc = YamlLoader::load_from_str(str).unwrap();
2774 assert_eq!(doc.len(), 1);
2775 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2776 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2777
2778 let str = r#"---
2779 {name: another-rule, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2780 let doc2 = YamlLoader::load_from_str(str).unwrap();
2781 assert_eq!(doc2.len(), 1);
2782 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2783 assert_eq!(rules.rules["math"].len(), 2, "\nsecond rule not added");
2784
2785 let speech_pattern = &rules.rules["math"][0];
2786 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2787 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2788 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2789 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2790 }
2791
2792 #[test]
2793 fn test_debug_no_debug() {
2794 let str = r#"*[2]/*[3][text()='3']"#;
2795 let result = MyXPath::add_debug_string_arg(str);
2796 assert!(result.is_ok());
2797 assert_eq!(result.unwrap(), str);
2798 }
2799
2800 #[test]
2801 fn test_debug_no_debug_with_quote() {
2802 let str = r#"*[2]/*[3][text()='(']"#;
2803 let result = MyXPath::add_debug_string_arg(str);
2804 assert!(result.is_ok());
2805 assert_eq!(result.unwrap(), str);
2806 }
2807
2808 #[test]
2809 fn test_debug_no_quoted_paren() {
2810 let str = r#"DEBUG(*[2]/*[3][text()='3'])"#;
2811 let result = MyXPath::add_debug_string_arg(str);
2812 assert!(result.is_ok());
2813 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='3'], "*[2]/*[3][text()='3']")"#);
2814 }
2815
2816 #[test]
2817 fn test_debug_quoted_paren() {
2818 let str = r#"DEBUG(*[2]/*[3][text()='('])"#;
2819 let result = MyXPath::add_debug_string_arg(str);
2820 assert!(result.is_ok());
2821 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='('], "*[2]/*[3][text()='(']")"#);
2822 }
2823
2824 #[test]
2825 fn test_debug_quoted_paren_before_paren() {
2826 let str = r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics') and IsBracketed(., '(', ')')"#;
2827 let result = MyXPath::add_debug_string_arg(str);
2828 assert!(result.is_ok());
2829 assert_eq!(result.unwrap(), r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics', "ClearSpeak_Matrix = 'Combinatorics'") and IsBracketed(., '(', ')')"#);
2830 }
2831
2832
2833cfg_if::cfg_if! {if #[cfg(not(feature = "include-zip"))] {
2835 #[test]
2836 fn test_up_to_date() {
2837 use crate::interface::*;
2838 set_rules_dir(super::super::abs_rules_dir_path()).unwrap();
2840 set_preference("Language".to_string(), "zz-aa".to_string()).unwrap();
2841 if let Err(e) = set_mathml("<math><mi>x</mi></math>".to_string()) {
2843 error!("{}", crate::errors_to_string(&e));
2844 panic!("Should not be an error in setting MathML")
2845 }
2846
2847 set_preference("CheckRuleFiles".to_string(), "All".to_string()).unwrap();
2848 assert!(!is_file_time_same(), "file's time did not get updated");
2849 set_preference("CheckRuleFiles".to_string(), "None".to_string()).unwrap();
2850 assert!(is_file_time_same(), "file's time was wrongly updated (preference 'CheckRuleFiles' should have prevented updating)");
2851
2852 fn is_file_time_same() -> bool {
2854 use std::time::Duration;
2858 return SPEECH_RULES.with(|rules| {
2859 let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone();
2860
2861 let contents = std::fs::read(&start_main_file.file).expect(&format!("Failed to read file {} during test", &start_main_file.file.to_string_lossy()));
2863 std::fs::write(start_main_file.file, contents).unwrap();
2864 std::thread::sleep(Duration::from_millis(5)); if let Err(e) = get_spoken_text() {
2868 error!("{}", crate::errors_to_string(&e));
2869 panic!("Should not be an error in speech")
2870 }
2871 return rules.borrow().unicode_short_files.borrow().ft[0].time == start_main_file.time;
2872 });
2873 }
2874 }
2875}}
2876
2877 }