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
155
156pub fn yaml_to_type(yaml: &Yaml) -> String {
158 return match yaml {
159 Yaml::Real(v)=> format!("real='{:#}'", v),
160 Yaml::Integer(v)=> format!("integer='{:#}'", v),
161 Yaml::String(v)=> format!("string='{:#}'", v),
162 Yaml::Boolean(v)=> format!("boolean='{:#}'", v),
163 Yaml::Array(v)=> match v.len() {
164 0 => "array with no entries".to_string(),
165 1 => format!("array with the entry: {}", yaml_to_type(&v[0])),
166 _ => format!("array with {} entries. First entry: {}", v.len(), yaml_to_type(&v[0])),
167 }
168 Yaml::Hash(h)=> {
169 let first_pair =
170 if h.is_empty() {
171 "no pairs".to_string()
172 } else {
173 let (key, val) = h.iter().next().unwrap();
174 format!("({}, {})", yaml_to_type(key), yaml_to_type(val))
175 };
176 format!("dictionary with {} pair{}. A pair: {}", h.len(), if h.len()==1 {""} else {"s"}, first_pair)
177 }
178 Yaml::Alias(_)=> "Alias".to_string(),
179 Yaml::Null=> "Null".to_string(),
180 Yaml::BadValue=> "BadValue".to_string(),
181 }
182}
183
184fn yaml_type_err(yaml: &Yaml, str: &str) -> String {
185 return format!("Expected {}, found {}", str, yaml_to_type(yaml));
186}
187
188fn find_str<'a>(dict: &'a Yaml, key: &'a str) -> Option<&'a str> {
201 return dict[key].as_str();
202}
203
204pub fn as_hash_checked(value: &Yaml) -> Result<&Hash> {
206 let result = value.as_hash();
207 let result = result.ok_or_else(|| yaml_type_err(value, "hashmap"))?;
208 return Ok( result );
209}
210
211pub fn as_vec_checked(value: &Yaml) -> Result<&Vec<Yaml>> {
213 let result = value.as_vec();
214 let result = result.ok_or_else(|| yaml_type_err(value, "array"))?;
215 return Ok( result );
216}
217
218pub fn as_str_checked(yaml: &Yaml) -> Result<&str> {
220 return Ok( yaml.as_str().ok_or_else(|| yaml_type_err(yaml, "string"))? );
221}
222
223
224pub const CONCAT_INDICATOR: &str = "\u{F8FE}";
228
229pub const CONCAT_STRING: &str = " \u{F8FE}";
231
232const OPTIONAL_INDICATOR: &str = "\u{F8FD}";
235const OPTIONAL_INDICATOR_LEN: usize = OPTIONAL_INDICATOR.len();
236
237pub fn remove_optional_indicators(str: &str) -> String {
238 return str.replace(OPTIONAL_INDICATOR, "");
239}
240
241pub fn compile_rule<F>(str: &str, mut build_fn: F) -> Result<Vec<PathBuf>> where
245 F: FnMut(&Yaml) -> Result<Vec<PathBuf>> {
246 let docs = YamlLoader::load_from_str(str);
247 match docs {
248 Err(e) => {
249 bail!("Parse error!!: {}", e);
250 },
251 Ok(docs) => {
252 if docs.len() != 1 {
253 bail!("Didn't find rules!");
254 }
255 return build_fn(&docs[0]);
256 }
257 }
258}
259
260pub fn process_include<F>(current_file: &Path, new_file_name: &str, mut read_new_file: F) -> Result<Vec<PathBuf>>
261 where F: FnMut(&Path) -> Result<Vec<PathBuf>> {
262 let parent_path = current_file.parent();
263 if parent_path.is_none() {
264 bail!("Internal error: {:?} is not a valid file name", current_file);
265 }
266 let mut new_file = match canonicalize_shim(parent_path.unwrap()) {
267 Ok(path) => path,
268 Err(e) => bail!("process_include: canonicalize failed for {} with message {}", parent_path.unwrap().display(), e.to_string()),
269 };
270
271 for unzip_dir in new_file.ancestors() {
273 if unzip_dir.ends_with("Rules") {
274 break; }
276 if unzip_dir.ends_with("Languages") || unzip_dir.ends_with("Braille") {
277 if let Some(subdir) = new_file.strip_prefix(unzip_dir).unwrap().iter().next() {
280 let default_lang = if unzip_dir.ends_with("Languages") {"en"} else {"UEB;"};
281 PreferenceManager::unzip_files(unzip_dir, subdir.to_str().unwrap(), Some(default_lang)).unwrap_or_default();
282 }
283 }
284 }
285 new_file.push(new_file_name);
286 info!("...processing include: {}...", new_file_name);
287 let new_file = match crate::shim_filesystem::canonicalize_shim(new_file.as_path()) {
288 Ok(buf) => buf,
289 Err(msg) => bail!("-include: constructed file name '{}' causes error '{}'",
290 new_file.to_str().unwrap(), msg),
291 };
292
293 let mut included_files = read_new_file(new_file.as_path())?;
294 let mut files_read = vec![new_file];
295 files_read.append(&mut included_files);
296 return Ok(files_read);
297}
298
299pub trait TreeOrString<'c, 'm:'c, T> {
302 fn from_element(e: Element<'m>) -> Result<T>;
303 fn from_string(s: String, doc: Document<'m>) -> Result<T>;
304 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>;
305 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<T>;
306 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T>;
307 fn highlight_braille(braille: T, highlight_style: String) -> T;
308 fn mark_nav_speech(speech: T) -> T;
309}
310
311impl<'c, 'm:'c> TreeOrString<'c, 'm, String> for String {
312 fn from_element(_e: Element<'m>) -> Result<String> {
313 bail!("from_element not allowed for strings");
314 }
315
316 fn from_string(s: String, _doc: Document<'m>) -> Result<String> {
317 return Ok(s);
318 }
319
320 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> {
321 return tts.replace_string(command, prefs, rules_with_context, mathml);
322 }
323
324 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
325 return ra.replace_array_string(rules_with_context, mathml);
326 }
327
328 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
329 return rules.replace_nodes_string(nodes, mathml);
330 }
331
332 fn highlight_braille(braille: String, highlight_style: String) -> String {
333 return SpeechRulesWithContext::highlight_braille_string(braille, highlight_style);
334 }
335
336 fn mark_nav_speech(speech: String) -> String {
337 return SpeechRulesWithContext::mark_nav_speech(speech);
338 }
339}
340
341impl<'c, 'm:'c> TreeOrString<'c, 'm, Element<'m>> for Element<'m> {
342 fn from_element(e: Element<'m>) -> Result<Element<'m>> {
343 return Ok(e);
344 }
345
346 fn from_string(s: String, doc: Document<'m>) -> Result<Element<'m>> {
347 let leaf = create_mathml_element(&doc, "mi");
349 leaf.set_text(&s);
350 return Ok(leaf);
351}
352
353 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>> {
354 bail!("Internal error: applying a TTS rule to a tree");
355 }
356
357 fn replace<'s:'c, 'r>(ra: &ReplacementArray, rules_with_context: &'r mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<Element<'m>> {
358 return ra.replace_array_tree(rules_with_context, mathml);
359 }
360
361 fn replace_nodes<'s:'c, 'r>(rules: &'r mut SpeechRulesWithContext<'c, 's,'m>, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<Element<'m>> {
362 return rules.replace_nodes_tree(nodes, mathml);
363 }
364
365 fn highlight_braille(_braille: Element<'c>, _highlight_style: String) -> Element<'m> {
366 panic!("Internal error: highlight_braille called on a tree");
367 }
368
369 fn mark_nav_speech(_speech: Element<'c>) -> Element<'m> {
370 panic!("Internal error: mark_nav_speech called on a tree");
371 }
372}
373
374#[derive(Debug, Clone)]
377#[allow(clippy::upper_case_acronyms)]
378enum Replacement {
379 Text(String),
381 XPath(MyXPath),
382 Intent(Box<Intent>),
383 Test(Box<TestArray>),
384 TTS(Box<TTSCommandRule>),
385 With(Box<With>),
386 SetVariables(Box<SetVariables>),
387 Insert(Box<InsertChildren>),
388 Translate(TranslateExpression),
389}
390
391impl fmt::Display for Replacement {
392 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
393 return write!(f, "{}",
394 match self {
395 Replacement::Test(c) => c.to_string(),
396 Replacement::Text(t) => format!("t: \"{}\"", t),
397 Replacement::XPath(x) => x.to_string(),
398 Replacement::Intent(i) => i.to_string(),
399 Replacement::TTS(t) => t.to_string(),
400 Replacement::With(w) => w.to_string(),
401 Replacement::SetVariables(v) => v.to_string(),
402 Replacement::Insert(ic) => ic.to_string(),
403 Replacement::Translate(x) => x.to_string(),
404 }
405 );
406 }
407}
408
409impl Replacement {
410 fn build(replacement: &Yaml) -> Result<Replacement> {
411 let dictionary = replacement.as_hash();
413 if dictionary.is_none() {
414 bail!(" expected a key/value pair. Found {}.", yaml_to_string(replacement, 0));
415 };
416 let dictionary = dictionary.unwrap();
417 if dictionary.is_empty() {
418 bail!("No key/value pairs found for key 'replace'.\n\
419 Suggestion: are the following lines indented properly?");
420 }
421 if dictionary.len() > 1 {
422 bail!("Should only be one key/value pair for the replacement.\n \
423 Suggestion: are the following lines indented properly?\n \
424 The key/value pairs found are\n{}", yaml_to_string(replacement, 2));
425 }
426
427 let (key, value) = dictionary.iter().next().unwrap();
429 let key = key.as_str().ok_or("replacement key(e.g, 't') is not a string")?;
430 match key {
431 "t" | "T" => {
432 return Ok( Replacement::Text( as_str_checked(value)?.to_string() ) );
433 },
434 "ct" | "CT" => {
435 return Ok( Replacement::Text( CONCAT_INDICATOR.to_string() + as_str_checked(value)? ) );
436 },
437 "ot" | "OT" => {
438 return Ok( Replacement::Text( OPTIONAL_INDICATOR.to_string() + as_str_checked(value)? + OPTIONAL_INDICATOR ) );
439 },
440 "x" => {
441 return Ok( Replacement::XPath( MyXPath::build(value)
442 .chain_err(|| "while trying to evaluate value of 'x:'")? ) );
443 },
444 "pause" | "rate" | "pitch" | "volume" | "audio" | "gender" | "voice" | "spell" | "SPELL" | "bookmark" | "pronounce" | "PRONOUNCE" => {
445 return Ok( Replacement::TTS( TTS::build(&key.to_ascii_lowercase(), value)? ) );
446 },
447 "intent" => {
448 return Ok( Replacement::Intent( Intent::build(value)? ) );
449 },
450 "test" => {
451 return Ok( Replacement::Test( Box::new( TestArray::build(value)? ) ) );
452 },
453 "with" => {
454 return Ok( Replacement::With( With::build(value)? ) );
455 },
456 "set_variables" => {
457 return Ok( Replacement::SetVariables( SetVariables::build(value)? ) );
458 },
459 "insert" => {
460 return Ok( Replacement::Insert( InsertChildren::build(value)? ) );
461 },
462 "translate" => {
463 return Ok( Replacement::Translate( TranslateExpression::build(value)
464 .chain_err(|| "while trying to evaluate value of 'speak:'")? ) );
465 },
466 _ => {
467 bail!("Unknown 'replace' command ({}) with value: {}", key, yaml_to_string(value, 0));
468 }
469 }
470 }
471}
472
473#[derive(Debug, Clone)]
476struct InsertChildren {
477 xpath: MyXPath, replacements: ReplacementArray, }
480
481impl fmt::Display for InsertChildren {
482 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
483 return write!(f, "InsertChildren:\n nodes {}\n replacements {}", self.xpath, &self.replacements);
484 }
485}
486
487impl InsertChildren {
488 fn build(insert: &Yaml) -> Result<Box<InsertChildren>> {
489 if insert.as_hash().is_none() {
491 bail!("")
492 }
493 let nodes = &insert["nodes"];
494 if nodes.is_badvalue() {
495 bail!("Missing 'nodes' as part of 'insert'.\n \
496 Suggestion: add 'nodes:' or if present, indent so it is contained in 'insert'");
497 }
498 let nodes = as_str_checked(nodes)?;
499 let replace = &insert["replace"];
500 if replace.is_badvalue() {
501 bail!("Missing 'replace' as part of 'insert'.\n \
502 Suggestion: add 'replace:' or if present, indent so it is contained in 'insert'");
503 }
504 return Ok( Box::new( InsertChildren {
505 xpath: MyXPath::new(nodes.to_string())?,
506 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
507 } ) );
508 }
509
510 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> {
519 let result = self.xpath.evaluate(&rules_with_context.context_stack.base, mathml)
520 .chain_err(||format!("in '{}' replacing after pattern match", &self.xpath.rc.string) )?;
521 match result {
522 Value::Nodeset(nodes) => {
523 if nodes.size() == 0 {
524 bail!("During replacement, no matching element found");
525 };
526 let nodes = nodes.document_order();
527 let n_nodes = nodes.len();
528 let mut expanded_result = Vec::with_capacity(n_nodes + (n_nodes+1)*self.replacements.replacements.len());
529 expanded_result.push(
530 Replacement::XPath(
531 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , 1))?
532 )
533 );
534 for i in 2..n_nodes+1 {
535 expanded_result.extend_from_slice(&self.replacements.replacements);
536 expanded_result.push(
537 Replacement::XPath(
538 MyXPath::new(format!("{}[{}]", self.xpath.rc.string , i))?
539 )
540 );
541 }
542 let replacements = ReplacementArray{ replacements: expanded_result };
543 return replacements.replace(rules_with_context, mathml);
544 },
545
546 Value::String(t) => { return T::from_string(rules_with_context.replace_chars(&t, mathml)?, rules_with_context.doc); },
548 Value::Number(num) => { return T::from_string( num.to_string(), rules_with_context.doc ); },
549 Value::Boolean(b) => { return T::from_string( b.to_string(), rules_with_context.doc ); }, }
551
552 }
553}
554
555
556lazy_static! {
557 static ref ATTR_NAME_VALUE: Regex = Regex::new(
558 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>[^']+)'"#
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 } else if let Some(my_xpath) = &self.xpath{ let xpath_value = my_xpath.evaluate(rules_with_context.get_context(), mathml)?;
628 match xpath_value {
629 Value::String(intent_name) => {
630 result.set_attribute_value(MATHML_FROM_NAME_ATTR, name(mathml));
631 set_mathml_name(result, intent_name.as_str())
632 },
633 _ => bail!("'xpath-name' value '{}' was not a string", &my_xpath),
634 }
635 } else {
636 panic!("Intent::replace: internal error -- neither 'name' nor 'xpath' is set");
637 };
638
639 for attr in mathml.attributes() {
640 result.set_attribute_value(attr.name(), attr.value());
641 }
642
643 if !self.attrs.is_empty() {
644 for cap in ATTR_NAME_VALUE.captures_iter(&self.attrs) {
646 let value_as_xpath = MyXPath::new(cap["value"].to_string()).chain_err(||"attr value inside 'intent'")?;
647 let value = value_as_xpath.evaluate(rules_with_context.get_context(), mathml)
648 .chain_err(||"attr xpath evaluation value inside 'intent'")?;
649 let mut value = value.into_string();
650 if &cap["name"] == INTENT_PROPERTY {
651 value = simplify_fixity_properties(&value)
652 }
653 result.set_attribute_value(&cap["name"], &value);
655 };
656 }
657
658 return T::from_element(result);
660
661
662 fn lift_children(result: Element) -> Element {
664 result.replace_children(
666 result.children().iter()
667 .map(|&child_of_element| {
668 match child_of_element {
669 ChildOfElement::Element(child) => {
670 if name(child) == "TEMP_NAME" {
671 assert_eq!(child.children().len(), 1);
672 child.children()[0]
673 } else {
674 child_of_element
675 }
676 },
677 _ => child_of_element, }
679 })
680 .collect::<Vec<ChildOfElement>>()
681 );
682 return result;
683 }
684 }
685}
686
687#[derive(Debug, Clone)]
690struct With {
691 variables: VariableDefinitions, replacements: ReplacementArray, }
694
695impl fmt::Display for With {
696 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
697 return write!(f, "with:\n variables: {}\n replace: {}", &self.variables, &self.replacements);
698 }
699}
700
701impl With {
702 fn build(vars_replacements: &Yaml) -> Result<Box<With>> {
703 if vars_replacements.as_hash().is_none() {
705 bail!("Array found for contents of 'with' -- should be dictionary with keys 'variables' and 'replace'")
706 }
707 let var_defs = &vars_replacements["variables"];
708 if var_defs.is_badvalue() {
709 bail!("Missing 'variables' as part of 'with'.\n \
710 Suggestion: add 'variables:' or if present, indent so it is contained in 'with'");
711 }
712 let replace = &vars_replacements["replace"];
713 if replace.is_badvalue() {
714 bail!("Missing 'replace' as part of 'with'.\n \
715 Suggestion: add 'replace:' or if present, indent so it is contained in 'with'");
716 }
717 return Ok( Box::new( With {
718 variables: VariableDefinitions::build(var_defs).chain_err(|| "'variables'")?,
719 replacements: ReplacementArray::build(replace).chain_err(|| "'replace:'")?,
720 } ) );
721 }
722
723 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> {
724 rules_with_context.context_stack.push(self.variables.clone(), mathml)?;
725 let result = self.replacements.replace(rules_with_context, mathml)
726 .chain_err(||"replacing inside 'with'")?;
727 rules_with_context.context_stack.pop();
728 return Ok( result );
729 }
730}
731
732#[derive(Debug, Clone)]
735struct SetVariables {
736 variables: VariableDefinitions, }
738
739impl fmt::Display for SetVariables {
740 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
741 return write!(f, "SetVariables: variables {}", &self.variables);
742 }
743}
744
745impl SetVariables {
746 fn build(vars: &Yaml) -> Result<Box<SetVariables>> {
747 if vars.as_vec().is_none() {
749 bail!("'set_variables' -- should be an array of variable name, xpath value");
750 }
751 return Ok( Box::new( SetVariables {
752 variables: VariableDefinitions::build(vars).chain_err(|| "'set_variables'")?
753 } ) );
754 }
755
756 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> {
757 rules_with_context.context_stack.set_globals(self.variables.clone(), mathml)?;
758 return T::from_string( "".to_string(), rules_with_context.doc );
759 }
760}
761
762
763#[derive(Debug, Clone)]
765struct TranslateExpression {
766 id: MyXPath, }
768
769impl fmt::Display for TranslateExpression {
770 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
771 return write!(f, "speak: {}", &self.id);
772 }
773}
774impl TranslateExpression {
775 fn build(vars: &Yaml) -> Result<TranslateExpression> {
776 return Ok( TranslateExpression { id: MyXPath::build(vars).chain_err(|| "'translate'")? } );
778 }
779
780 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> {
781 if self.id.rc.string.contains('@') {
782 let xpath_value = self.id.evaluate(rules_with_context.get_context(), mathml)?;
783 let id = match xpath_value {
784 Value::String(s) => Some(s),
785 Value::Nodeset(nodes) => {
786 if nodes.size() == 1 {
787 nodes.document_order_first().unwrap().attribute().map(|attr| attr.value().to_string())
788 } else {
789 None
790 }
791 },
792 _ => None,
793 };
794 match id {
795 None => bail!("'translate' value '{}' is not a string or an attribute value (correct by using '@id'??):\n", self.id),
796 Some(id) => {
797 let speech = speak_mathml(mathml, &id)?;
798 return T::from_string(speech, rules_with_context.doc);
799 }
800 }
801 } else {
802 return T::from_string(
803 self.id.replace(rules_with_context, mathml).chain_err(||"'translate'")?,
804 rules_with_context.doc
805 );
806 }
807 }
808}
809
810
811#[derive(Debug, Clone)]
813pub struct ReplacementArray {
814 replacements: Vec<Replacement>
815}
816
817impl fmt::Display for ReplacementArray {
818 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
819 return write!(f, "{}", self.pretty_print_replacements());
820 }
821}
822
823impl ReplacementArray {
824 pub fn build_empty() -> ReplacementArray {
826 return ReplacementArray {
827 replacements: vec![]
828 }
829 }
830
831 pub fn build(replacements: &Yaml) -> Result<ReplacementArray> {
834 let result= if replacements.is_array() {
836 let replacements = replacements.as_vec().unwrap();
837 replacements
838 .iter()
839 .enumerate() .map(|(i, r)| Replacement::build(r)
841 .chain_err(|| format!("replacement #{} of {}", i+1, replacements.len())))
842 .collect::<Result<Vec<Replacement>>>()?
843 } else {
844 vec![ Replacement::build(replacements)?]
845 };
846
847 return Ok( ReplacementArray{ replacements: result } );
848 }
849
850 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> {
852 return T::replace(self, rules_with_context, mathml);
853 }
854
855 pub fn replace_array_string<'c, 's:'c, 'm:'c>(&self, rules_with_context: &mut SpeechRulesWithContext<'c, 's,'m>, mathml: Element<'c>) -> Result<String> {
856 let mut replacement_strings = Vec::with_capacity(self.replacements.len()); for replacement in self.replacements.iter() {
862 let string: String = rules_with_context.replace(replacement, mathml)?;
863 if !string.is_empty() {
864 replacement_strings.push(string);
865 }
866 }
867
868 if replacement_strings.is_empty() {
869 return Ok( "".to_string() );
870 }
871 for i in 1..replacement_strings.len()-1 {
879 if let Some(bytes) = is_repetitive(&replacement_strings[i-1], &replacement_strings[i]) {
880 replacement_strings[i] = bytes.to_string();
881 }
882 }
883
884 for i in 0..replacement_strings.len() {
885 if replacement_strings[i].contains(PAUSE_AUTO_STR) {
886 let before = if i == 0 {""} else {&replacement_strings[i-1]};
887 let after = if i+1 == replacement_strings.len() {""} else {&replacement_strings[i+1]};
888 replacement_strings[i] = replacement_strings[i].replace(
889 PAUSE_AUTO_STR,
890 &rules_with_context.speech_rules.pref_manager.borrow().get_tts().compute_auto_pause(&rules_with_context.speech_rules.pref_manager.borrow(), before, after));
891 }
892 }
893
894 return Ok( replacement_strings.join(" ") );
897
898 fn is_repetitive<'a>(prev: &str, optional: &'a str) -> Option<&'a str> {
899 if optional.len() <= 2 * OPTIONAL_INDICATOR_LEN {
902 return None;
903 }
904
905 match optional.find(OPTIONAL_INDICATOR) {
907 None => return None,
908 Some(start_index) => {
909 let optional_word_start_slice = &optional[start_index + OPTIONAL_INDICATOR_LEN..];
910 match optional_word_start_slice.find(OPTIONAL_INDICATOR) {
912 None => panic!("Internal error: missing end optional char -- text handling is corrupted!"),
913 Some(end_index) => {
914 let optional_word = &optional_word_start_slice[..end_index];
915 let prev = prev.trim_end().as_bytes();
918 if prev.len() > optional_word.len() &&
919 &prev[prev.len()-optional_word.len()..] == optional_word.as_bytes() {
920 return Some( optional_word_start_slice[optional_word.len() + OPTIONAL_INDICATOR_LEN..].trim_start() );
921 } else {
922 return None;
923 }
924 }
925 }
926 }
927 }
928 }
929 }
930
931 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>> {
932 if self.replacements.len() == 1 {
934 return rules_with_context.replace::<Element<'m>>(&self.replacements[0], mathml);
935 }
936
937 let new_element = create_mathml_element(&rules_with_context.doc, "Unknown"); let mut new_children = Vec::with_capacity(self.replacements.len());
939 for child in self.replacements.iter() {
940 let child = rules_with_context.replace::<Element<'m>>(child, mathml)?;
941 new_children.push(ChildOfElement::Element(child));
942 };
943 new_element.append_children(new_children);
944 return Ok(new_element);
945 }
946
947
948 pub fn is_empty(&self) -> bool {
950 return self.replacements.is_empty();
951 }
952
953 fn pretty_print_replacements(&self) -> String {
954 let mut group_string = String::with_capacity(128);
955 if self.replacements.len() == 1 {
956 group_string += &format!("[{}]", self.replacements[0]);
957 } else {
958 group_string += &self.replacements.iter()
959 .map(|replacement| format!("\n - {}", replacement))
960 .collect::<Vec<String>>()
961 .join("");
962 group_string += "\n";
963 }
964 return group_string;
965 }
966}
967
968
969
970#[derive(Debug)]
974struct RCMyXPath {
975 xpath: XPath,
976 string: String, }
978
979#[derive(Debug, Clone)]
980pub struct MyXPath {
981 rc: Rc<RCMyXPath> }
983
984
985impl fmt::Display for MyXPath {
986 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
987 return write!(f, "x: \"{}\"", self.rc.string);
988 }
989}
990
991thread_local!{
995 static XPATH_CACHE: RefCell<HashMap<String, MyXPath>> = RefCell::new( HashMap::with_capacity(2047) );
996}
997impl MyXPath {
1000 fn new(xpath: String) -> Result<MyXPath> {
1001 return XPATH_CACHE.with( |cache| {
1002 let mut cache = cache.borrow_mut();
1003 return Ok(
1004 match cache.get(&xpath) {
1005 Some(compiled_xpath) => {
1006 compiled_xpath.clone()
1008 },
1009 None => {
1010 let new_xpath = MyXPath {
1011 rc: Rc::new( RCMyXPath {
1012 xpath: MyXPath::compile_xpath(&xpath)?,
1013 string: xpath.clone()
1014 })};
1015 cache.insert(xpath.clone(), new_xpath.clone());
1016 new_xpath
1017 },
1018 }
1019 )
1020 });
1021 }
1022
1023 pub fn build(xpath: &Yaml) -> Result<MyXPath> {
1024 let xpath = match xpath {
1025 Yaml::String(s) => s.to_string(),
1026 Yaml::Integer(i) => i.to_string(),
1027 Yaml::Real(s) => s.to_string(),
1028 Yaml::Boolean(s) => s.to_string(),
1029 Yaml::Array(v) =>
1030 v.iter()
1032 .map(as_str_checked)
1033 .collect::<Result<Vec<&str>>>()?
1034 .join(" "),
1035 _ => bail!("Bad value when trying to create an xpath: {}", yaml_to_string(xpath, 1)),
1036 };
1037 return MyXPath::new(xpath);
1038 }
1039
1040 fn compile_xpath(xpath: &str) -> Result<XPath> {
1041 let factory = Factory::new();
1042 let xpath_with_debug_info = MyXPath::add_debug_string_arg(xpath)?;
1043 let compiled_xpath = factory.build(&xpath_with_debug_info)
1044 .chain_err(|| format!(
1045 "Could not compile XPath for pattern:\n{}{}",
1046 &xpath, more_details(xpath)))?;
1047 return match compiled_xpath {
1048 Some(xpath) => Ok(xpath),
1049 None => bail!("Problem compiling Xpath for pattern:\n{}{}",
1050 &xpath, more_details(xpath)),
1051 };
1052
1053
1054 fn more_details(xpath: &str) -> String {
1055 if xpath.is_empty() {
1057 return "xpath is empty string".to_string();
1058 }
1059 let as_bytes = xpath.trim().as_bytes();
1060 if as_bytes[0] == b'\'' && as_bytes[as_bytes.len()-1] != b'\'' {
1061 return "\nmissing \"'\"".to_string();
1062 }
1063 if (as_bytes[0] == b'"' && as_bytes[as_bytes.len()-1] != b'"') ||
1064 (as_bytes[0] != b'"' && as_bytes[as_bytes.len()-1] == b'"'){
1065 return "\nmissing '\"'".to_string();
1066 }
1067
1068 let mut i_bytes = 0; let mut paren_count = 0; let mut i_paren = 0; let mut bracket_count = 0;
1072 let mut i_bracket = 0;
1073 for ch in xpath.chars() {
1074 if ch == '(' {
1075 if paren_count == 0 {
1076 i_paren = i_bytes;
1077 }
1078 paren_count += 1;
1079 } else if ch == '[' {
1080 if bracket_count == 0 {
1081 i_bracket = i_bytes;
1082 }
1083 bracket_count += 1;
1084 } else if ch == ')' {
1085 if paren_count == 0 {
1086 return format!("\nExtra ')' found after '{}'", &xpath[i_paren..i_bytes]);
1087 }
1088 paren_count -= 1;
1089 if paren_count == 0 && bracket_count > 0 && i_bracket > i_paren {
1090 return format!("\nUnclosed brackets found at '{}'", &xpath[i_paren..i_bytes]);
1091 }
1092 } else if ch == ']' {
1093 if bracket_count == 0 {
1094 return format!("\nExtra ']' found after '{}'", &xpath[i_bracket..i_bytes]);
1095 }
1096 bracket_count -= 1;
1097 if bracket_count == 0 && paren_count > 0 && i_paren > i_bracket {
1098 return format!("\nUnclosed parens found at '{}'", &xpath[i_bracket..i_bytes]);
1099 }
1100 }
1101 i_bytes += ch.len_utf8();
1102 }
1103 return "".to_string();
1104 }
1105 }
1106
1107 fn add_debug_string_arg(xpath: &str) -> Result<String> {
1108 let debug_start = xpath.find("DEBUG(");
1118 if debug_start.is_none() {
1119 return Ok( xpath.to_string() );
1120 }
1121 let debug_start = debug_start.unwrap();
1122 let string_start = xpath[..debug_start+6].to_string(); let mut count = 1; let mut remainder: &str = &xpath[debug_start+6..];
1125
1126 loop {
1127 let next = remainder.find(['(', ')']);
1129 match next {
1130 None => bail!("Did not find closing paren for DEBUG in\n{}", xpath),
1131 Some(i_paren) => {
1132 let remainder_as_bytes = remainder.as_bytes();
1133
1134 if i_paren == 0 || remainder_as_bytes[i_paren-1] != b'\'' ||
1137 i_paren+1 >= remainder.len() || remainder_as_bytes[i_paren+1] != b'\'' {
1138 if remainder_as_bytes[i_paren] == b'(' {
1140 count += 1;
1141 } else { count -= 1;
1143 if count == 0 {
1144 let i_end = xpath.len() - remainder.len() + i_paren;
1145 let escaped_arg = &xpath[debug_start+6..i_end].to_string().replace('"', "\\\"");
1146 let contents = MyXPath::add_debug_string_arg(&xpath[debug_start+6..i_end])?;
1147 return Ok( string_start + &contents + ", \"" + escaped_arg + "\" "
1148 + &MyXPath::add_debug_string_arg(&xpath[i_end..])? );
1149 }
1150 }
1151 }
1152 remainder = &remainder[i_paren+1..];
1153 }
1154 }
1155 }
1156 }
1157
1158 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1159 return Ok(
1161 match self.evaluate(context, mathml)? {
1162 Value::Boolean(b) => b,
1163 Value::Nodeset(nodes) => nodes.size() > 0,
1164 _ => false,
1165 }
1166 )
1167 }
1168
1169 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> {
1170 if self.rc.string == "process-intent(.)" {
1171 return T::from_element( infer_intent(rules_with_context, mathml)? );
1172 }
1173
1174 let result = self.evaluate(&rules_with_context.context_stack.base, mathml)
1175 .chain_err(|| format!("in '{}' replacing after pattern match", &self.rc.string) )?;
1176 let string = match result {
1177 Value::Nodeset(nodes) => {
1178 if nodes.size() == 0 {
1179 bail!("During replacement, no matching element found");
1180 }
1181 return rules_with_context.replace_nodes(nodes.document_order(), mathml);
1182 },
1183 Value::String(s) => s,
1184 Value::Number(num) => num.to_string(),
1185 Value::Boolean(b) => b.to_string(), };
1187 let result = if self.rc.string.starts_with('$') {string} else {rules_with_context.replace_chars(&string, mathml)?};
1190 return T::from_string(result, rules_with_context.doc );
1191 }
1192
1193 pub fn evaluate<'c>(&self, context: &Context<'c>, mathml: Element<'c>) -> Result<Value<'c>> {
1194 let result = self.rc.xpath.evaluate(context, mathml);
1196 return match result {
1197 Ok(val) => Ok( val ),
1198 Err(e) => {
1199 bail!( "{}\n\n",
1200 e.to_string().replace("OwnedPrefixedName { prefix: None, local_part:", "").replace(" }", "") );
1202 }
1203 };
1204 }
1205
1206 pub fn test_input<F>(self, f: F) -> bool where F: Fn(&str) -> bool {
1207 return f(self.rc.string.as_ref());
1208 }
1209}
1210
1211#[derive(Debug)]
1216struct SpeechPattern {
1217 pattern_name: String,
1218 tag_name: String,
1219 file_name: String,
1220 pattern: MyXPath, match_uses_var_defs: bool, var_defs: VariableDefinitions, replacements: ReplacementArray, }
1225
1226impl fmt::Display for SpeechPattern {
1227 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1228 return write!(f, "[name: {}, tag: {},\n variables: {:?}, pattern: {},\n replacement: {}]",
1229 self.pattern_name, self.tag_name, self.var_defs, self.pattern,
1230 self.replacements.pretty_print_replacements());
1231 }
1232}
1233
1234impl SpeechPattern {
1235 fn build(dict: &Yaml, file: &Path, rules: &mut SpeechRules) -> Result<Option<Vec<PathBuf>>> {
1236 if let Some(include_file_name) = find_str(dict, "include") {
1242 let do_include_fn = |new_file: &Path| {
1243 rules.read_patterns(new_file)
1244 };
1245
1246 return Ok( Some(process_include(file, include_file_name, do_include_fn)?) );
1247 }
1248
1249 let pattern_name = find_str(dict, "name");
1250
1251 let mut tag_names: Vec<&str> = Vec::new();
1253 match find_str(dict, "tag") {
1254 Some(str) => tag_names.push(str),
1255 None => {
1256 let tag_array = &dict["tag"];
1258 tag_names = vec![];
1259 if tag_array.is_array() {
1260 for (i, name) in tag_array.as_vec().unwrap().iter().enumerate() {
1261 match as_str_checked(name) {
1262 Err(e) => return Err(
1263 e.chain_err(||
1264 format!("tag name '{}' is not a string in:\n{}",
1265 &yaml_to_string(&tag_array.as_vec().unwrap()[i], 0),
1266 &yaml_to_string(dict, 1)))
1267 ),
1268 Ok(str) => tag_names.push(str),
1269 };
1270 }
1271 } else {
1272 bail!("Errors trying to find 'tag' in:\n{}", &yaml_to_string(dict, 1));
1273 }
1274 }
1275 }
1276
1277 if pattern_name.is_none() {
1278 if dict.is_null() {
1279 bail!("Error trying to find 'name': empty value (two consecutive '-'s?");
1280 } else {
1281 bail!("Errors trying to find 'name' in:\n{}", &yaml_to_string(dict, 1));
1282 };
1283 };
1284 let pattern_name = pattern_name.unwrap().to_string();
1285
1286 if dict["match"].is_badvalue() {
1288 bail!("Did not find 'match' in\n{}", yaml_to_string(dict, 1));
1289 }
1290 if dict["replace"].is_badvalue() {
1291 bail!("Did not find 'replace' in\n{}", yaml_to_string(dict, 1));
1292 }
1293
1294 for tag_name in tag_names {
1296 let tag_name = tag_name.to_string();
1297 let pattern_xpath = MyXPath::build(&dict["match"])
1298 .chain_err(|| {
1299 format!("value for 'match' in rule ({}: {}):\n{}",
1300 tag_name, pattern_name, yaml_to_string(dict, 1))
1301 })?;
1302 let speech_pattern =
1303 Box::new( SpeechPattern{
1304 pattern_name: pattern_name.clone(),
1305 tag_name: tag_name.clone(),
1306 file_name: file.to_str().unwrap().to_string(),
1307 match_uses_var_defs: dict["variables"].is_array() && pattern_xpath.rc.string.contains('$'), pattern: pattern_xpath,
1309 var_defs: VariableDefinitions::build(&dict["variables"])
1310 .chain_err(|| {
1311 format!("value for 'variables' in rule ({}: {}):\n{}",
1312 tag_name, pattern_name, yaml_to_string(dict, 1))
1313 })?,
1314 replacements: ReplacementArray::build(&dict["replace"])
1315 .chain_err(|| {
1316 format!("value for 'replace' in rule ({}: {}). Replacements:\n{}",
1317 tag_name, pattern_name, yaml_to_string(&dict["replace"], 1))
1318 })?
1319 } );
1320 let rule_value = rules.rules.entry(tag_name).or_default();
1322
1323 match rule_value.iter().enumerate().find(|&pattern| pattern.1.pattern_name == speech_pattern.pattern_name) {
1325 None => rule_value.push(speech_pattern),
1326 Some((i, _old_pattern)) => {
1327 let old_rule = &rule_value[i];
1328 info!("\n\n***WARNING***: replacing {}/'{}' in {} with rule from {}\n",
1329 old_rule.tag_name, old_rule.pattern_name, old_rule.file_name, speech_pattern.file_name);
1330 rule_value[i] = speech_pattern;
1331 },
1332 }
1333 }
1334
1335 return Ok(None);
1336 }
1337
1338 fn is_match(&self, context: &Context, mathml: Element) -> Result<bool> {
1339 if self.tag_name != mathml.name().local_part() && self.tag_name != "*" && self.tag_name != "!*" {
1340 return Ok( false );
1341 }
1342
1343 return Ok(
1347 match self.pattern.evaluate(context, mathml)? {
1348 Value::Boolean(b) => b,
1349 Value::Nodeset(nodes) => nodes.size() > 0,
1350 _ => false,
1351 }
1352 );
1353 }
1354}
1355
1356
1357#[derive(Debug, Clone)]
1361struct TestArray {
1362 tests: Vec<Test>
1363}
1364
1365impl fmt::Display for TestArray {
1366 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1367 for test in &self.tests {
1368 writeln!(f, "{}", test)?;
1369 }
1370 return Ok( () );
1371 }
1372}
1373
1374impl TestArray {
1375 fn build(test: &Yaml) -> Result<TestArray> {
1376 let tests = if test.as_hash().is_some() {
1381 vec![test]
1382 } else if let Some(vec) = test.as_vec() {
1383 vec.iter().collect()
1384 } else {
1385 bail!("Value for 'test:' is neither a dictionary or an array.")
1386 };
1387
1388 let mut test_array = vec![];
1394 for test in tests {
1395 if test.as_hash().is_none() {
1396 bail!("Value for array entry in 'test:' must be a dictionary/contain keys");
1397 }
1398 let if_part = &test[if test_array.is_empty() {"if"} else {"else_if"}];
1399 if !if_part.is_badvalue() {
1400 let condition = Some( MyXPath::build(if_part)? );
1402 let then_part = TestOrReplacements::build(test, "then", "then_test", true)?;
1403 let else_part = TestOrReplacements::build(test, "else", "else_test", false)?;
1404 let n_keys = if else_part.is_none() {2} else {3};
1405 if test.as_hash().unwrap().len() > n_keys {
1406 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found in the 'then' clause of 'test'");
1407 };
1408 test_array.push(
1409 Test { condition, then_part, else_part }
1410 );
1411 } else {
1412 let else_part = TestOrReplacements::build(test, "else", "else_test", true)?;
1414 if test.as_hash().unwrap().len() > 1 {
1415 bail!("A key other than 'if', 'else_if', 'then', 'then_test', 'else', or 'else_test' was found the 'else' clause of 'test'");
1416 };
1417 test_array.push(
1418 Test { condition: None, then_part: None, else_part }
1419 );
1420
1421 if test_array.len() < test.as_hash().unwrap().len() {
1423 bail!("'else'/'else_test' key is not last key in 'test:'");
1424 }
1425 }
1426 };
1427
1428 if test_array.is_empty() {
1429 bail!("No entries for 'test:'");
1430 }
1431
1432 return Ok( TestArray { tests: test_array } );
1433 }
1434
1435 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> {
1436 for test in &self.tests {
1437 if test.is_true(&rules_with_context.context_stack.base, mathml)? {
1438 assert!(test.then_part.is_some());
1439 return test.then_part.as_ref().unwrap().replace(rules_with_context, mathml);
1440 } else if let Some(else_part) = test.else_part.as_ref() {
1441 return else_part.replace(rules_with_context, mathml);
1442 }
1443 }
1444 return T::from_string("".to_string(), rules_with_context.doc);
1445 }
1446}
1447
1448#[derive(Debug, Clone)]
1449enum TestOrReplacements {
1451 Replacements(ReplacementArray), Test(TestArray), }
1454
1455impl fmt::Display for TestOrReplacements {
1456 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1457 if let TestOrReplacements::Test(_) = self {
1458 write!(f, " _test")?;
1459 }
1460 write!(f, ":")?;
1461 return match self {
1462 TestOrReplacements::Test(t) => write!(f, "{}", t),
1463 TestOrReplacements::Replacements(r) => write!(f, "{}", r),
1464 };
1465 }
1466}
1467
1468impl TestOrReplacements {
1469 fn build(test: &Yaml, replace_key: &str, test_key: &str, key_required: bool) -> Result<Option<TestOrReplacements>> {
1470 let part = &test[replace_key];
1471 let test_part = &test[test_key];
1472 if !part.is_badvalue() && !test_part.is_badvalue() {
1473 bail!(format!("Only one of '{}' or '{}' is allowed as part of 'test'.\n{}\n \
1474 Suggestion: delete one or adjust indentation",
1475 replace_key, test_key, yaml_to_string(test, 2)));
1476 }
1477 if part.is_badvalue() && test_part.is_badvalue() {
1478 if key_required {
1479 bail!(format!("Missing one of '{}'/'{}:' as part of 'test:'\n{}\n \
1480 Suggestion: add the missing key or indent so it is contained in 'test'",
1481 replace_key, test_key, yaml_to_string(test, 2)))
1482 } else {
1483 return Ok( None );
1484 }
1485 }
1486 if test_part.is_badvalue() {
1488 return Ok( Some( TestOrReplacements::Replacements( ReplacementArray::build(part)? ) ) );
1489 } else {
1490 return Ok( Some( TestOrReplacements::Test( TestArray::build(test_part)? ) ) );
1491 }
1492 }
1493
1494 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> {
1495 return match self {
1496 TestOrReplacements::Replacements(r) => r.replace(rules_with_context, mathml),
1497 TestOrReplacements::Test(t) => t.replace(rules_with_context, mathml),
1498 }
1499 }
1500}
1501
1502#[derive(Debug, Clone)]
1503struct Test {
1504 condition: Option<MyXPath>,
1505 then_part: Option<TestOrReplacements>,
1506 else_part: Option<TestOrReplacements>,
1507}
1508impl fmt::Display for Test {
1509 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1510 write!(f, "test: [ ")?;
1511 if let Some(if_part) = &self.condition {
1512 write!(f, " if: '{}'", if_part)?;
1513 }
1514 if let Some(then_part) = &self.then_part {
1515 write!(f, " then{}", then_part)?;
1516 }
1517 if let Some(else_part) = &self.else_part {
1518 write!(f, " else{}", else_part)?;
1519 }
1520 return write!(f, "]");
1521 }
1522}
1523
1524impl Test {
1525 fn is_true(&self, context: &Context, mathml: Element) -> Result<bool> {
1526 return match self.condition.as_ref() {
1527 None => Ok( false ), Some(condition) => condition.is_true(context, mathml)
1529 .chain_err(|| "Failure in conditional test"),
1530 }
1531 }
1532}
1533
1534#[derive(Debug, Clone)]
1536struct VariableDefinition {
1537 name: String, value: MyXPath, }
1540
1541impl fmt::Display for VariableDefinition {
1542 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1543 return write!(f, "[name: {}={}]", self.name, self.value);
1544 }
1545}
1546
1547#[derive(Debug)]
1549struct VariableValue<'v> {
1550 name: String, value: Option<Value<'v>>, }
1553
1554impl fmt::Display for VariableValue<'_> {
1555 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1556 let value = match &self.value {
1557 None => "unset".to_string(),
1558 Some(val) => format!("{:?}", val)
1559 };
1560 return write!(f, "[name: {}, value: {}]", self.name, value);
1561 }
1562}
1563
1564impl VariableDefinition {
1565 fn build(name_value_def: &Yaml) -> Result<VariableDefinition> {
1566 match name_value_def.as_hash() {
1567 Some(map) => {
1568 if map.len() != 1 {
1569 bail!("definition is not a key/value pair. Found {}",
1570 yaml_to_string(name_value_def, 1) );
1571 }
1572 let (name, value) = map.iter().next().unwrap();
1573 let name = as_str_checked( name)
1574 .chain_err(|| format!( "definition name is not a string: {}",
1575 yaml_to_string(name, 1) ))?.to_string();
1576 match value {
1577 Yaml::Boolean(_) | Yaml::String(_) | Yaml::Integer(_) | Yaml::Real(_) => (),
1578 _ => bail!("definition value is not a string, boolean, or number. Found {}",
1579 yaml_to_string(value, 1) )
1580 };
1581 return Ok(
1582 VariableDefinition{
1583 name,
1584 value: MyXPath::build(value)?
1585 }
1586 );
1587 },
1588 None => bail!("definition is not a key/value pair. Found {}",
1589 yaml_to_string(name_value_def, 1) )
1590 }
1591 }
1592}
1593
1594
1595#[derive(Debug, Clone)]
1596struct VariableDefinitions {
1597 defs: Vec<VariableDefinition>
1598}
1599
1600impl fmt::Display for VariableDefinitions {
1601 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1602 for def in &self.defs {
1603 write!(f, "{},", def)?;
1604 }
1605 return Ok( () );
1606 }
1607}
1608
1609struct VariableValues<'v> {
1610 defs: Vec<VariableValue<'v>>
1611}
1612
1613impl fmt::Display for VariableValues<'_> {
1614 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1615 for value in &self.defs {
1616 write!(f, "{}", value)?;
1617 }
1618 return writeln!(f);
1619 }
1620}
1621
1622impl VariableDefinitions {
1623 fn new(len: usize) -> VariableDefinitions {
1624 return VariableDefinitions{ defs: Vec::with_capacity(len) };
1625 }
1626
1627 fn build(defs: &Yaml) -> Result<VariableDefinitions> {
1628 if defs.is_badvalue() {
1629 return Ok( VariableDefinitions::new(0) );
1630 };
1631 if defs.is_array() {
1632 let defs = defs.as_vec().unwrap();
1633 let mut definitions = VariableDefinitions::new(defs.len());
1634 for def in defs {
1635 let variable_def = VariableDefinition::build(def)
1636 .chain_err(|| "definition of 'variables'")?;
1637 definitions.push( variable_def);
1638 };
1639 return Ok (definitions );
1640 }
1641 bail!( "'variables' is not an array of {{name: xpath-value}} definitions. Found {}'",
1642 yaml_to_string(defs, 1) );
1643 }
1644
1645 fn push(&mut self, var_def: VariableDefinition) {
1646 self.defs.push(var_def);
1647 }
1648
1649 fn len(&self) -> usize {
1650 return self.defs.len();
1651 }
1652}
1653
1654struct ContextStack<'c> {
1655 old_values: Vec<VariableValues<'c>>, base: Context<'c> }
1659
1660impl fmt::Display for ContextStack<'_> {
1661 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1662 writeln!(f, " {} old_values", self.old_values.len())?;
1663 for values in &self.old_values {
1664 writeln!(f, " {}", values)?;
1665 }
1666 return writeln!(f);
1667 }
1668}
1669
1670impl<'c, 'r> ContextStack<'c> {
1671 fn new<'a,>(pref_manager: &'a PreferenceManager) -> ContextStack<'c> {
1672 let prefs = pref_manager.merge_prefs();
1673 let mut context_stack = ContextStack {
1674 base: ContextStack::base_context(prefs),
1675 old_values: Vec::with_capacity(31) };
1677 context_stack.base.set_variable("MatchingPause", Value::Boolean(false));
1680 context_stack.base.set_variable("IsColumnSilent", Value::Boolean(false));
1681
1682
1683 return context_stack;
1684 }
1685
1686 fn base_context(var_defs: PreferenceHashMap) -> Context<'c> {
1687 let mut context = Context::new();
1688 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
1689 crate::xpath_functions::add_builtin_functions(&mut context);
1690 for (key, value) in var_defs {
1691 context.set_variable(key.as_str(), yaml_to_value(&value));
1692 };
1698 return context;
1699 }
1700
1701 fn set_globals(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1702 for def in &new_vars.defs {
1704 let new_value = match def.value.evaluate(&self.base, mathml) {
1706 Ok(val) => val,
1707 Err(_) => bail!(format!("Can't evaluate variable def for {}", def)),
1708 };
1709 let qname = QName::new(def.name.as_str());
1710 self.base.set_variable(qname, new_value);
1711 }
1712 return Ok( () );
1713 }
1714
1715 fn push(&'r mut self, new_vars: VariableDefinitions, mathml: Element<'c>) -> Result<()> {
1716 let mut old_values = VariableValues {defs: Vec::with_capacity(new_vars.defs.len()) };
1718 let evaluation = Evaluation::new(&self.base, Node::Element(mathml));
1719 for def in &new_vars.defs {
1720 let qname = QName::new(def.name.as_str());
1722 let old_value = evaluation.value_of(qname).cloned();
1723 old_values.defs.push( VariableValue{ name: def.name.clone(), value: old_value} );
1724 }
1725
1726 for def in &new_vars.defs {
1728 let new_value = match def.value.evaluate(&self.base, mathml) {
1730 Ok(val) => val,
1731 Err(_) => bail!(format!("Can't evaluate variable def for {} with ContextStack {}", def, self)),
1732 };
1733 let qname = QName::new(def.name.as_str());
1734 self.base.set_variable(qname, new_value);
1735 }
1736 self.old_values.push(old_values);
1737 return Ok( () );
1738 }
1739
1740 fn pop(&mut self) {
1741 const MISSING_VALUE: &str = "-- unset value --"; let old_values = self.old_values.pop().unwrap();
1743 for variable in old_values.defs {
1744 let qname = QName::new(&variable.name);
1745 let old_value = match variable.value {
1746 None => Value::String(MISSING_VALUE.to_string()),
1747 Some(val) => val,
1748 };
1749 self.base.set_variable(qname, old_value);
1750 }
1751 }
1752}
1753
1754
1755fn yaml_to_value<'b>(yaml: &Yaml) -> Value<'b> {
1756 return match yaml {
1757 Yaml::String(s) => Value::String(s.clone()),
1758 Yaml::Boolean(b) => Value::Boolean(*b),
1759 Yaml::Integer(i) => Value::Number(*i as f64),
1760 Yaml::Real(s) => Value::Number(s.parse::<f64>().unwrap()),
1761 _ => {
1762 error!("yaml_to_value: illegal type found in Yaml value: {}", yaml_to_string(yaml, 1));
1763 Value::String("".to_string())
1764 },
1765 }
1766}
1767
1768
1769struct UnicodeDef {
1771 ch: u32,
1772 speech: ReplacementArray
1773}
1774
1775impl fmt::Display for UnicodeDef {
1776 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1777 return write!(f, "UnicodeDef{{ch: {}, speech: {:?}}}", self.ch, self.speech);
1778 }
1779}
1780
1781impl UnicodeDef {
1782 fn build(unicode_def: &Yaml, file_name: &Path, speech_rules: &SpeechRules, use_short: bool) -> Result<Option<Vec<PathBuf>>> {
1783 if let Some(include_file_name) = find_str(unicode_def, "include") {
1784 let do_include_fn = |new_file: &Path| {
1785 speech_rules.read_unicode(Some(new_file.to_path_buf()), use_short)
1786 };
1787 return Ok( Some(process_include(file_name, include_file_name, do_include_fn)?) );
1788 }
1789 let dictionary = unicode_def.as_hash();
1791 if dictionary.is_none() {
1792 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1793 }
1794
1795 let dictionary = dictionary.unwrap();
1796 if dictionary.len() != 1 {
1797 bail!("Expected a unicode definition (e.g, '+':[t: \"plus\"]'), found {}", yaml_to_string(unicode_def, 0));
1798 }
1799
1800 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)))?;
1801 let mut unicode_table = if use_short {
1802 speech_rules.unicode_short.borrow_mut()
1803 } else {
1804 speech_rules.unicode_full.borrow_mut()
1805 };
1806 if let Some(str) = ch.as_str() {
1807 if str.is_empty() {
1808 bail!("Empty character definition. Replacement is {}", replacements.as_str().unwrap());
1809 }
1810 let mut chars = str.chars();
1811 let first_ch = chars.next().unwrap(); if chars.next().is_some() { if str.contains('-') {
1814 return process_range(str, replacements, unicode_table);
1815 } else if first_ch != '0' { for ch in str.chars() { let ch_as_str = ch.to_string();
1818 if unicode_table.insert(ch as u32, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1819 .chain_err(|| format!("In definition of char: '{}'", str))?.replacements).is_some() {
1820 error!("*** Character '{}' (0x{:X}) is repeated", ch, ch as u32);
1821 }
1822 }
1823 return Ok(None);
1824 }
1825 }
1826 }
1827
1828 let ch = UnicodeDef::get_unicode_char(ch)?;
1829 if unicode_table.insert(ch, ReplacementArray::build(replacements)
1830 .chain_err(|| format!("In definition of char: '{}' (0x{})",
1831 char::from_u32(ch).unwrap(), ch))?.replacements).is_some() {
1832 error!("*** Character '{}' (0x{:X}) is repeated", char::from_u32(ch).unwrap(), ch);
1833 }
1834 return Ok(None);
1835
1836 fn process_range(def_range: &str, replacements: &Yaml, mut unicode_table: RefMut<HashMap<u32,Vec<Replacement>>>) -> Result<Option<Vec<PathBuf>>> {
1837 let mut range = def_range.split('-');
1840 let first = range.next().unwrap().chars().next().unwrap() as u32;
1841 let last = range.next().unwrap().chars().next().unwrap() as u32;
1842 if range.next().is_some() {
1843 bail!("Character range definition has more than one '-': '{}'", def_range);
1844 }
1845
1846 for ch in first..last+1 {
1847 let ch_as_str = char::from_u32(ch).unwrap().to_string();
1848 unicode_table.insert(ch, ReplacementArray::build(&substitute_ch(replacements, &ch_as_str))
1849 .chain_err(|| format!("In definition of char: '{}'", def_range))?.replacements);
1850 };
1851
1852 return Ok(None)
1853 }
1854
1855 fn substitute_ch(yaml: &Yaml, ch: &str) -> Yaml {
1856 return match yaml {
1857 Yaml::Array(ref v) => {
1858 Yaml::Array(
1859 v.iter()
1860 .map(|e| substitute_ch(e, ch))
1861 .collect::<Vec<Yaml>>()
1862 )
1863 },
1864 Yaml::Hash(ref h) => {
1865 Yaml::Hash(
1866 h.iter()
1867 .map(|(key,val)| (key.clone(), substitute_ch(val, ch)) )
1868 .collect::<Hash>()
1869 )
1870 },
1871 Yaml::String(s) => Yaml::String( s.replace('.', ch) ),
1872 _ => yaml.clone(),
1873 }
1874 }
1875 }
1876
1877 fn get_unicode_char(ch: &Yaml) -> Result<u32> {
1878 if let Some(ch) = ch.as_str() {
1880 let mut ch_iter = ch.chars();
1881 let unicode_ch = ch_iter.next();
1882 if unicode_ch.is_none() || ch_iter.next().is_some() {
1883 bail!("Wanted unicode char, found string '{}')", ch);
1884 };
1885 return Ok( unicode_ch.unwrap() as u32 );
1886 }
1887
1888 if let Some(num) = ch.as_i64() {
1889 return Ok( num as u32 );
1890 }
1891 bail!("Unicode character '{}' can't be converted to an code point", yaml_to_string(ch, 0));
1892 }
1893}
1894
1895type RuleTable = HashMap<String, Vec<Box<SpeechPattern>>>;
1903 type UnicodeTable = Rc<RefCell<HashMap<u32,Vec<Replacement>>>>;
1904 type FilesAndTimesShared = Rc<RefCell<FilesAndTimes>>;
1905
1906 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1907 pub enum RulesFor {
1908 Intent,
1909 Speech,
1910 OverView,
1911 Navigation,
1912 Braille,
1913 }
1914
1915 impl fmt::Display for RulesFor {
1916 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1917 let name = match self {
1918 RulesFor::Intent => "Intent",
1919 RulesFor::Speech => "Speech",
1920 RulesFor::OverView => "OverView",
1921 RulesFor::Navigation => "Navigation",
1922 RulesFor::Braille => "Braille",
1923 };
1924 return write!(f, "{}", name);
1925 }
1926 }
1927
1928
1929#[derive(Debug, Clone)]
1930pub struct FileAndTime {
1931 file: PathBuf,
1932 time: SystemTime,
1933}
1934
1935impl FileAndTime {
1936 fn new(file: PathBuf) -> FileAndTime {
1937 return FileAndTime {
1938 file,
1939 time: SystemTime::UNIX_EPOCH,
1940 }
1941 }
1942
1943 pub fn debug_get_file(&self) -> Option<&str> {
1945 return self.file.to_str();
1946 }
1947
1948 pub fn new_with_time(file: PathBuf) -> FileAndTime {
1949 return FileAndTime {
1950 time: FileAndTime::get_metadata(&file),
1951 file,
1952 }
1953 }
1954
1955 pub fn is_up_to_date(&self) -> bool {
1956 let file_mod_time = FileAndTime::get_metadata(&self.file);
1957 return self.time >= file_mod_time;
1958 }
1959
1960 fn get_metadata(path: &Path) -> SystemTime {
1961 use std::fs;
1962 if !cfg!(target_family = "wasm") {
1963 let metadata = fs::metadata(path);
1964 if let Ok(metadata) = metadata {
1965 if let Ok(mod_time) = metadata.modified() {
1966 return mod_time;
1967 }
1968 }
1969 }
1970 return SystemTime::UNIX_EPOCH
1971 }
1972
1973}
1974#[derive(Debug, Default)]
1975pub struct FilesAndTimes {
1976 ft: Vec<FileAndTime>
1980}
1981
1982impl FilesAndTimes {
1983 pub fn new(start_path: PathBuf) -> FilesAndTimes {
1984 let mut ft = Vec::with_capacity(8);
1985 ft.push( FileAndTime::new(start_path) );
1986 return FilesAndTimes{ ft };
1987 }
1988
1989 pub fn is_file_up_to_date(&self, pref_path: &Path, should_ignore_file_time: bool) -> bool {
1991
1992 if self.ft.is_empty() || self.as_path() != pref_path {
1994 return false;
1995 }
1996 if should_ignore_file_time || cfg!(target_family = "wasm") {
1997 return true;
1998 }
1999 if self.ft[0].time == SystemTime::UNIX_EPOCH {
2000 return false;
2001 }
2002
2003
2004 for file in &self.ft {
2006 if !file.is_up_to_date() {
2007 return false;
2008 }
2009 }
2010 return true;
2011 }
2012
2013 fn set_files_and_times(&mut self, new_files: Vec<PathBuf>) {
2014 self.ft.clear();
2015 for path in new_files {
2016 let time = FileAndTime::get_metadata(&path); self.ft.push( FileAndTime{ file: path, time })
2018 }
2019 }
2020
2021 pub fn as_path(&self) -> &Path {
2022 assert!(!self.ft.is_empty());
2023 return &self.ft[0].file;
2024 }
2025
2026 pub fn paths(&self) -> Vec<PathBuf> {
2027 return self.ft.iter().map(|ft| ft.file.clone()).collect::<Vec<PathBuf>>();
2028 }
2029
2030}
2031
2032
2033pub struct SpeechRules {
2039 error: String,
2040 name: RulesFor,
2041 pub pref_manager: Rc<RefCell<PreferenceManager>>,
2042 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, }
2051
2052impl fmt::Display for SpeechRules {
2053 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2054 writeln!(f, "SpeechRules '{}'\n{})", self.name, self.pref_manager.borrow())?;
2055 let mut rules_vec: Vec<(&String, &Vec<Box<SpeechPattern>>)> = self.rules.iter().collect();
2056 rules_vec.sort_by(|(tag_name1, _), (tag_name2, _)| tag_name1.cmp(tag_name2));
2057 for (tag_name, rules) in rules_vec {
2058 writeln!(f, " {}: #patterns {}", tag_name, rules.len())?;
2059 };
2060 return writeln!(f, " {}+{} unicode entries", &self.unicode_short.borrow().len(), &self.unicode_full.borrow().len());
2061 }
2062}
2063
2064
2065pub struct SpeechRulesWithContext<'c, 's:'c, 'm:'c> {
2069 speech_rules: &'s SpeechRules,
2070 context_stack: ContextStack<'c>, doc: Document<'m>,
2072 nav_node_id: &'m str,
2073 pub inside_spell: bool, pub translate_count: usize, }
2076
2077impl<'c, 's:'c, 'm:'c> fmt::Display for SpeechRulesWithContext<'c, 's,'m> {
2078 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2079 writeln!(f, "SpeechRulesWithContext \n{})", self.speech_rules)?;
2080 return writeln!(f, " {} context entries, nav node id '{}'", &self.context_stack, self.nav_node_id);
2081 }
2082}
2083
2084thread_local!{
2085 static SPEECH_UNICODE_SHORT: UnicodeTable =
2087 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2088
2089 static SPEECH_UNICODE_FULL: UnicodeTable =
2091 Rc::new( RefCell::new( HashMap::with_capacity(6500) ) );
2092
2093 static BRAILLE_UNICODE_SHORT: UnicodeTable =
2095 Rc::new( RefCell::new( HashMap::with_capacity(500) ) );
2096
2097 static BRAILLE_UNICODE_FULL: UnicodeTable =
2099 Rc::new( RefCell::new( HashMap::with_capacity(5000) ) );
2100
2101 static SPEECH_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2103 Rc::new( RefCell::new(FilesAndTimes::default()) );
2104
2105 static BRAILLE_DEFINITION_FILES_AND_TIMES: FilesAndTimesShared =
2107 Rc::new( RefCell::new(FilesAndTimes::default()) );
2108
2109 static SPEECH_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2111 Rc::new( RefCell::new(FilesAndTimes::default()) );
2112
2113 static SPEECH_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2115 Rc::new( RefCell::new(FilesAndTimes::default()) );
2116
2117 static BRAILLE_UNICODE_SHORT_FILES_AND_TIMES: FilesAndTimesShared =
2119 Rc::new( RefCell::new(FilesAndTimes::default()) );
2120
2121 static BRAILLE_UNICODE_FULL_FILES_AND_TIMES: FilesAndTimesShared =
2123 Rc::new( RefCell::new(FilesAndTimes::default()) );
2124
2125 pub static INTENT_RULES: RefCell<SpeechRules> =
2128 RefCell::new( SpeechRules::new(RulesFor::Intent, true) );
2129
2130 pub static SPEECH_RULES: RefCell<SpeechRules> =
2131 RefCell::new( SpeechRules::new(RulesFor::Speech, true) );
2132
2133 pub static OVERVIEW_RULES: RefCell<SpeechRules> =
2134 RefCell::new( SpeechRules::new(RulesFor::OverView, true) );
2135
2136 pub static NAVIGATION_RULES: RefCell<SpeechRules> =
2137 RefCell::new( SpeechRules::new(RulesFor::Navigation, true) );
2138
2139 pub static BRAILLE_RULES: RefCell<SpeechRules> =
2140 RefCell::new( SpeechRules::new(RulesFor::Braille, false) );
2141}
2142
2143impl SpeechRules {
2144 pub fn new(name: RulesFor, translate_single_chars_only: bool) -> SpeechRules {
2145 let globals = if name == RulesFor::Braille {
2146 (
2147 (BRAILLE_UNICODE_SHORT.with(Rc::clone), BRAILLE_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2148 (BRAILLE_UNICODE_FULL. with(Rc::clone), BRAILLE_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2149 BRAILLE_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2150 )
2151 } else {
2152 (
2153 (SPEECH_UNICODE_SHORT.with(Rc::clone), SPEECH_UNICODE_SHORT_FILES_AND_TIMES.with(Rc::clone)),
2154 (SPEECH_UNICODE_FULL. with(Rc::clone), SPEECH_UNICODE_FULL_FILES_AND_TIMES.with(Rc::clone)),
2155 SPEECH_DEFINITION_FILES_AND_TIMES.with(Rc::clone),
2156 )
2157 };
2158
2159 return SpeechRules {
2160 error: Default::default(),
2161 name,
2162 rules: HashMap::with_capacity(if name == RulesFor::Intent || name == RulesFor::Speech {500} else {50}), rule_files: FilesAndTimes::default(),
2164 unicode_short: globals.0.0, unicode_short_files: globals.0.1,
2166 unicode_full: globals.1.0, unicode_full_files: globals.1.1,
2168 definitions_files: globals.2,
2169 translate_single_chars_only,
2170 pref_manager: PreferenceManager::get(),
2171 };
2172}
2173
2174 pub fn get_error(&self) -> Option<&str> {
2175 return if self.error.is_empty() {
2176 None
2177 } else {
2178 Some(&self.error)
2179 }
2180 }
2181
2182 pub fn read_files(&mut self) -> Result<()> {
2183 let check_rule_files = self.pref_manager.borrow().pref_to_string("CheckRuleFiles");
2184 if check_rule_files != "None" { self.pref_manager.borrow_mut().set_preference_files()?;
2186 }
2187 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) {
2190 self.rules.clear();
2191 let files_read = self.read_patterns(&rule_file)?;
2192 self.rule_files.set_files_and_times(files_read);
2193 }
2194
2195 let pref_manager = self.pref_manager.borrow();
2196 let unicode_pref_files = if self.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2197
2198 if !self.unicode_short_files.borrow().is_file_up_to_date(unicode_pref_files.0, should_ignore_file_time) {
2199 self.unicode_short.borrow_mut().clear();
2200 self.unicode_short_files.borrow_mut().set_files_and_times(self.read_unicode(None, true)?);
2201 }
2202
2203 if self.definitions_files.borrow().ft.is_empty() || !self.definitions_files.borrow().is_file_up_to_date(
2204 pref_manager.get_definitions_file(self.name != RulesFor::Braille),
2205 should_ignore_file_time
2206 ) {
2207 self.definitions_files.borrow_mut().set_files_and_times(read_definitions_file(self.name != RulesFor::Braille)?);
2208 }
2209 return Ok( () );
2210 }
2211
2212 fn read_patterns(&mut self, path: &Path) -> Result<Vec<PathBuf>> {
2213 let rule_file_contents = read_to_string_shim(path).chain_err(|| format!("cannot read file '{}'", path.to_str().unwrap()))?;
2215 let rules_build_fn = |pattern: &Yaml| {
2216 self.build_speech_patterns(pattern, path)
2217 .chain_err(||format!("in file {:?}", path.to_str().unwrap()))
2218 };
2219 return compile_rule(&rule_file_contents, rules_build_fn)
2220 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2221 }
2222
2223 fn build_speech_patterns(&mut self, patterns: &Yaml, file_name: &Path) -> Result<Vec<PathBuf>> {
2224 let patterns_vec = patterns.as_vec();
2226 if patterns_vec.is_none() {
2227 bail!(yaml_type_err(patterns, "array"));
2228 }
2229 let patterns_vec = patterns.as_vec().unwrap();
2230 let mut files_read = vec![file_name.to_path_buf()];
2231 for entry in patterns_vec.iter() {
2232 if let Some(mut added_files) = SpeechPattern::build(entry, file_name, self)? {
2233 files_read.append(&mut added_files);
2234 }
2235 }
2236 return Ok(files_read)
2237 }
2238
2239 fn read_unicode(&self, path: Option<PathBuf>, use_short: bool) -> Result<Vec<PathBuf>> {
2240 let path = match path {
2241 Some(p) => p,
2242 None => {
2243 let pref_manager = self.pref_manager.borrow();
2245 let unicode_files = if self.name == RulesFor::Braille {
2246 pref_manager.get_braille_unicode_file()
2247 } else {
2248 pref_manager.get_speech_unicode_file()
2249 };
2250 let unicode_files = if use_short {unicode_files.0} else {unicode_files.1};
2251 unicode_files.to_path_buf()
2252 }
2253 };
2254
2255 let unicode_file_contents = read_to_string_shim(&path)?;
2258 let unicode_build_fn = |unicode_def_list: &Yaml| {
2259 let unicode_defs = unicode_def_list.as_vec();
2260 if unicode_defs.is_none() {
2261 bail!("File '{}' does not begin with an array", yaml_to_type(unicode_def_list));
2262 };
2263 let mut files_read = vec![path.to_path_buf()];
2264 for unicode_def in unicode_defs.unwrap() {
2265 if let Some(mut added_files) = UnicodeDef::build(unicode_def, &path, self, use_short)
2266 .chain_err(|| {format!("In file {:?}", path.to_str())})? {
2267 files_read.append(&mut added_files);
2268 }
2269 };
2270 return Ok(files_read)
2271 };
2272
2273 return compile_rule(&unicode_file_contents, unicode_build_fn)
2274 .chain_err(||format!("in file {:?}", path.to_str().unwrap()));
2275 }
2276
2277 pub fn print_sizes() -> String {
2278 let mut answer = rule_size(&SPEECH_RULES, "SPEECH_RULES");
2286 answer += &rule_size(&INTENT_RULES, "INTENT_RULES");
2287 answer += &rule_size(&BRAILLE_RULES, "BRAILLE_RULES");
2288 answer += &rule_size(&NAVIGATION_RULES, "NAVIGATION_RULES");
2289 answer += &rule_size(&OVERVIEW_RULES, "OVERVIEW_RULES");
2290 SPEECH_RULES.with_borrow(|rule| {
2291 answer += &format!("Speech Unicode tables: short={}/{}, long={}/{}\n",
2292 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2293 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2294 });
2295 BRAILLE_RULES.with_borrow(|rule| {
2296 answer += &format!("Braille Unicode tables: short={}/{}, long={}/{}\n",
2297 rule.unicode_short.borrow().len(), rule.unicode_short.borrow().capacity(),
2298 rule.unicode_full.borrow().len(), rule.unicode_full.borrow().capacity());
2299 });
2300 return answer;
2301
2302 fn rule_size(rules: &'static std::thread::LocalKey<RefCell<SpeechRules>>, name: &str) -> String {
2303 rules.with_borrow(|rule| {
2304 let hash_map = &rule.rules;
2305 return format!("{}: {}/{}\n", name, hash_map.len(), hash_map.capacity());
2306 })
2307 }
2308 }
2309}
2310
2311
2312impl<'c, 's:'c, 'r, 'm:'c> SpeechRulesWithContext<'c, 's,'m> {
2317 pub fn new(speech_rules: &'s SpeechRules, doc: Document<'m>, nav_node_id: &'m str) -> SpeechRulesWithContext<'c, 's, 'm> {
2318 return SpeechRulesWithContext {
2319 speech_rules,
2320 context_stack: ContextStack::new(&speech_rules.pref_manager.borrow()),
2321 doc,
2322 nav_node_id,
2323 inside_spell: false,
2324 translate_count: 0,
2325 }
2326 }
2327
2328 pub fn get_rules(&mut self) -> &SpeechRules {
2329 return self.speech_rules;
2330 }
2331
2332 pub fn get_context(&mut self) -> &mut Context<'c> {
2333 return &mut self.context_stack.base;
2334 }
2335
2336 pub fn get_document(&mut self) -> Document<'m> {
2337 return self.doc;
2338 }
2339
2340 pub fn match_pattern<T:TreeOrString<'c, 'm, T>>(&'r mut self, mathml: Element<'c>) -> Result<T> {
2341 let tag_name = mathml.name().local_part();
2343 let rules = &self.speech_rules.rules;
2344
2345 if let Some(rule_vector) = rules.get("!*") {
2347 if let Some(result) = self.find_match(rule_vector, mathml)? {
2348 return Ok(result); }
2350 }
2351
2352 if let Some(rule_vector) = rules.get(tag_name) {
2353 if let Some(result) = self.find_match(rule_vector, mathml)? {
2354 return Ok(result); }
2356 }
2357
2358 if let Some(rule_vector) = rules.get("*") {
2360 if let Some(result) = self.find_match(rule_vector, mathml)? {
2361 return Ok(result); }
2363 }
2364
2365 let speech_manager = self.speech_rules.pref_manager.borrow();
2368 let file_name = speech_manager.get_rule_file(&self.speech_rules.name);
2369 bail!("\nNo match found!\nMissing patterns in {} for MathML.\n{}", file_name.to_string_lossy(), mml_to_string(mathml));
2371 }
2372
2373 fn find_match<T:TreeOrString<'c, 'm, T>>(&'r mut self, rule_vector: &[Box<SpeechPattern>], mathml: Element<'c>) -> Result<Option<T>> {
2374 for pattern in rule_vector {
2375 if pattern.match_uses_var_defs {
2379 self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2380 }
2381 if pattern.is_match(&self.context_stack.base, mathml)
2382 .chain_err(|| error_string(pattern, mathml) )? {
2383 if !pattern.match_uses_var_defs && pattern.var_defs.len() > 0 { self.context_stack.push(pattern.var_defs.clone(), mathml)?;
2386 }
2387 let result: Result<T> = pattern.replacements.replace(self, mathml);
2388 if pattern.var_defs.len() > 0 {
2389 self.context_stack.pop();
2390 }
2391 return match result {
2392 Ok(s) => {
2393 if self.nav_node_id.is_empty() {
2395 Ok( Some(s) )
2396 } else {
2397 Ok ( Some(self.nav_node_adjust(s, mathml)) )
2399 }
2400 },
2401 Err(e) => Err( e.chain_err(||
2402 format!(
2403 "attempting replacement pattern: \"{}\" for \"{}\".\n\
2404 Replacement\n{}\n...due to matching the MathML\n{} with the pattern\n\
2405 {}\n\
2406 The patterns are in {}.\n",
2407 pattern.pattern_name, pattern.tag_name,
2408 pattern.replacements.pretty_print_replacements(),
2409 mml_to_string(mathml), pattern.pattern,
2410 pattern.file_name
2411 )
2412 ))
2413 }
2414 } else if pattern.match_uses_var_defs {
2415 self.context_stack.pop();
2416 }
2417 };
2418 return Ok(None); fn error_string(pattern: &SpeechPattern, mathml: Element) -> String {
2421 return format!(
2422 "error during pattern match using: \"{}\" for \"{}\".\n\
2423 Pattern is \n{}\nMathML for the match:\n\
2424 {}\
2425 The patterns are in {}.\n",
2426 pattern.pattern_name, pattern.tag_name,
2427 pattern.pattern,
2428 mml_to_string(mathml),
2429 pattern.file_name
2430 );
2431 }
2432
2433 }
2434
2435 fn nav_node_adjust<T:TreeOrString<'c, 'm, T>>(&self, speech: T, mathml: Element<'c>) -> T {
2436 if let Some(id) = mathml.attribute_value("id") {
2437 if self.nav_node_id == id {
2438 if self.speech_rules.name == RulesFor::Braille {
2439 let highlight_style = self.speech_rules.pref_manager.borrow().pref_to_string("BrailleNavHighlight");
2440 return T::highlight_braille(speech, highlight_style);
2441 } else {
2442 return T::mark_nav_speech(speech)
2443 }
2444 }
2445 }
2446 return speech;
2447
2448 }
2449
2450 fn highlight_braille_string(braille: String, highlight_style: String) -> String {
2451 if &highlight_style == "Off" || braille.is_empty() {
2453 return braille;
2454 }
2455
2456 let mut result = String::with_capacity(braille.len());
2459 let mut i_bytes = 0;
2460 let mut chars = braille.chars();
2461
2462 let baseline_indicator_hack = PreferenceManager::get().borrow().pref_to_string("BrailleCode") == "Nemeth";
2464 for ch in chars.by_ref() {
2465 let modified_ch = add_dots_to_braille_char(ch, baseline_indicator_hack);
2466 i_bytes += ch.len_utf8();
2467 result.push(modified_ch);
2468 if ch != modified_ch {
2469 break;
2470 };
2471 };
2472
2473 let mut i_end = braille.len();
2474 if &highlight_style != "FirstChar" {
2475 let rev_chars = braille.chars().rev();
2477 for ch in rev_chars {
2478 let modified_ch = add_dots_to_braille_char(ch, baseline_indicator_hack);
2479 i_end -= ch.len_utf8();
2480 if ch != modified_ch {
2481 break;
2482 }
2483 }
2484 }
2485
2486 for ch in chars {
2488 result.push( if i_bytes == i_end {add_dots_to_braille_char(ch, baseline_indicator_hack)} else {ch} );
2489 i_bytes += ch.len_utf8();
2490 };
2491
2492 return result;
2493
2494 fn add_dots_to_braille_char(ch: char, baseline_indicator_hack: bool) -> char {
2495 let as_u32 = ch as u32;
2496 if (0x2800..0x28FF).contains(&as_u32) {
2497 return unsafe {char::from_u32_unchecked(as_u32 | 0xC0)};
2498 } else if baseline_indicator_hack && ch == 'b' {
2499 return '𝑏'
2500 } else {
2501 return ch;
2502 }
2503 }
2504 }
2505
2506 fn mark_nav_speech(speech: String) -> String {
2507 return "[[".to_string() + &speech + "]]";
2510 }
2511
2512 fn replace<T:TreeOrString<'c, 'm, T>>(&'r mut self, replacement: &Replacement, mathml: Element<'c>) -> Result<T> {
2513 return Ok(
2514 match replacement {
2515 Replacement::Text(t) => T::from_string(t.clone(), self.doc)?,
2516 Replacement::XPath(xpath) => xpath.replace(self, mathml)?,
2517 Replacement::TTS(tts) => {
2518 T::from_string(
2519 self.speech_rules.pref_manager.borrow().get_tts().replace(tts, &self.speech_rules.pref_manager.borrow(), self, mathml)?,
2520 self.doc
2521 )?
2522 },
2523 Replacement::Intent(intent) => {
2524 intent.replace(self, mathml)?
2525 },
2526 Replacement::Test(test) => {
2527 test.replace(self, mathml)?
2528 },
2529 Replacement::With(with) => {
2530 with.replace(self, mathml)?
2531 },
2532 Replacement::SetVariables(vars) => {
2533 vars.replace(self, mathml)?
2534 },
2535 Replacement::Insert(ic) => {
2536 ic.replace(self, mathml)?
2537 },
2538 Replacement::Translate(id) => {
2539 id.replace(self, mathml)?
2540 },
2541 }
2542 )
2543 }
2544
2545 fn replace_nodes<T:TreeOrString<'c, 'm, T>>(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<T> {
2549 return T::replace_nodes(self, nodes, mathml);
2550 }
2551
2552 fn replace_nodes_tree(&'r mut self, nodes: Vec<Node<'c>>, _mathml: Element<'c>) -> Result<Element<'m>> {
2555 let mut children = Vec::with_capacity(3*nodes.len()); for node in nodes {
2557 let matched = match node {
2558 Node::Element(n) => self.match_pattern::<Element<'m>>(n)?,
2559 Node::Text(t) => {
2560 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2561 leaf.set_text(t.text());
2563 leaf
2564 },
2565 Node::Attribute(attr) => {
2566 let leaf = create_mathml_element(&self.doc, "TEMP_NAME");
2568 leaf.set_text(attr.value());
2569 leaf
2570 },
2571 _ => {
2572 bail!("replace_nodes: found unexpected node type!!!");
2573 },
2574 };
2575 children.push(matched);
2576 }
2577
2578 let result = create_mathml_element(&self.doc, "TEMP_NAME"); result.append_children(children);
2580 return Ok( result );
2582 }
2583
2584 fn replace_nodes_string(&'r mut self, nodes: Vec<Node<'c>>, mathml: Element<'c>) -> Result<String> {
2585 let mut result = String::with_capacity(3*nodes.len()); let mut first_time = true;
2588 for node in nodes {
2589 if first_time {
2590 first_time = false;
2591 } else {
2592 result.push(' ');
2593 };
2594 let matched = match node {
2595 Node::Element(n) => self.match_pattern::<String>(n)?,
2596 Node::Text(t) => self.replace_chars(t.text(), mathml)?,
2597 Node::Attribute(attr) => self.replace_chars(attr.value(), mathml)?,
2598 _ => bail!("replace_nodes: found unexpected node type!!!"),
2599 };
2600 result += &matched;
2601 }
2602 return Ok( result );
2603 }
2604
2605 pub fn replace_chars(&'r mut self, str: &str, mathml: Element<'c>) -> Result<String> {
2608 if is_quoted_string(str) {
2609 return Ok(unquote_string(str).to_string());
2610 }
2611 let rules = self.speech_rules;
2612 let mut chars = str.chars();
2613 if rules.translate_single_chars_only {
2615 let ch = chars.next().unwrap_or(' ');
2616 if chars.next().is_none() {
2617 return replace_single_char(self, ch, mathml)
2619 } else {
2620 return Ok(str.replace('\u{00A0}', " ").replace(['\u{2061}', '\u{2062}', '\u{2063}', '\u{2064}'], ""))
2622 }
2623 };
2624
2625 let result = chars
2626 .map(|ch| replace_single_char(self, ch, mathml))
2627 .collect::<Result<Vec<String>>>()?
2628 .join("");
2629 return Ok( result );
2630
2631 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> {
2632 let ch_as_u32 = ch as u32;
2633 let rules = rules_with_context.speech_rules;
2634 let mut unicode = rules.unicode_short.borrow();
2635 let mut replacements = unicode.get( &ch_as_u32 );
2636 if replacements.is_none() {
2637 let pref_manager = rules.pref_manager.borrow();
2639 let unicode_pref_files = if rules.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()};
2640 let should_ignore_file_time = pref_manager.pref_to_string("CheckRuleFiles") == "All";
2641 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) {
2642 info!("*** Loading full unicode {} for char '{}'/{:#06x}", rules.name, ch, ch_as_u32);
2643 rules.unicode_full.borrow_mut().clear();
2644 rules.unicode_full_files.borrow_mut().set_files_and_times(rules.read_unicode(None, false)?);
2645 info!("# Unicode defs = {}/{}", rules.unicode_short.borrow().len(), rules.unicode_full.borrow().len());
2646 }
2647 unicode = rules.unicode_full.borrow();
2648 replacements = unicode.get( &ch_as_u32 );
2649 if replacements.is_none() {
2650 rules_with_context.translate_count = 0; return Ok(String::from(ch)); }
2654 };
2655
2656 let result = replacements.unwrap()
2658 .iter()
2659 .map(|replacement|
2660 rules_with_context.replace(replacement, mathml)
2661 .chain_err(|| format!("Unicode replacement error: {}", replacement)) )
2662 .collect::<Result<Vec<String>>>()?
2663 .join(" ");
2664 rules_with_context.translate_count = 0; return Ok(result);
2666 }
2667 }
2668}
2669
2670pub fn braille_replace_chars(str: &str, mathml: Element) -> Result<String> {
2672 return BRAILLE_RULES.with(|rules| {
2673 let rules = rules.borrow();
2674 let new_package = Package::new();
2675 let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), "");
2676 return rules_with_context.replace_chars(str, mathml);
2677 })
2678}
2679
2680
2681
2682#[cfg(test)]
2683mod tests {
2684 #[allow(unused_imports)]
2685 use crate::init_logger;
2686
2687 use super::*;
2688
2689 #[test]
2690 fn test_read_statement() {
2691 let str = r#"---
2692 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2693 let doc = YamlLoader::load_from_str(str).unwrap();
2694 assert_eq!(doc.len(), 1);
2695 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2696
2697 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2698 assert_eq!(rules.rules["math"].len(), 1, "\nshould only be one rule");
2699
2700 let speech_pattern = &rules.rules["math"][0];
2701 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2702 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2703 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2704 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2705 assert_eq!(speech_pattern.replacements.replacements[0].to_string(), r#"x: "./*""#, "\nreplacement failure");
2706 }
2707
2708 #[test]
2709 fn test_read_statements_with_replace() {
2710 let str = r#"---
2711 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2712 let doc = YamlLoader::load_from_str(str).unwrap();
2713 assert_eq!(doc.len(), 1);
2714 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2715 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2716
2717 let str = r#"---
2718 {name: default, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2719 let doc2 = YamlLoader::load_from_str(str).unwrap();
2720 assert_eq!(doc2.len(), 1);
2721 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2722 assert_eq!(rules.rules["math"].len(), 1, "\nfirst rule not replaced");
2723
2724 let speech_pattern = &rules.rules["math"][0];
2725 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2726 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2727 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2728 assert_eq!(speech_pattern.replacements.replacements.len(), 2, "\nreplacement failure");
2729 }
2730
2731 #[test]
2732 fn test_read_statements_with_add() {
2733 let str = r#"---
2734 {name: default, tag: math, match: ".", replace: [x: "./*"] }"#;
2735 let doc = YamlLoader::load_from_str(str).unwrap();
2736 assert_eq!(doc.len(), 1);
2737 let mut rules = SpeechRules::new(RulesFor::Speech, true);
2738 SpeechPattern::build(&doc[0], Path::new("testing"), &mut rules).unwrap();
2739
2740 let str = r#"---
2741 {name: another-rule, tag: math, match: ".", replace: [t: "test", x: "./*"] }"#;
2742 let doc2 = YamlLoader::load_from_str(str).unwrap();
2743 assert_eq!(doc2.len(), 1);
2744 SpeechPattern::build(&doc2[0], Path::new("testing"), &mut rules).unwrap();
2745 assert_eq!(rules.rules["math"].len(), 2, "\nsecond rule not added");
2746
2747 let speech_pattern = &rules.rules["math"][0];
2748 assert_eq!(speech_pattern.pattern_name, "default", "\npattern name failure");
2749 assert_eq!(speech_pattern.tag_name, "math", "\ntag name failure");
2750 assert_eq!(speech_pattern.pattern.rc.string, ".", "\npattern failure");
2751 assert_eq!(speech_pattern.replacements.replacements.len(), 1, "\nreplacement failure");
2752 }
2753
2754 #[test]
2755 fn test_debug_no_debug() {
2756 let str = r#"*[2]/*[3][text()='3']"#;
2757 let result = MyXPath::add_debug_string_arg(str);
2758 assert!(result.is_ok());
2759 assert_eq!(result.unwrap(), str);
2760 }
2761
2762 #[test]
2763 fn test_debug_no_debug_with_quote() {
2764 let str = r#"*[2]/*[3][text()='(']"#;
2765 let result = MyXPath::add_debug_string_arg(str);
2766 assert!(result.is_ok());
2767 assert_eq!(result.unwrap(), str);
2768 }
2769
2770 #[test]
2771 fn test_debug_no_quoted_paren() {
2772 let str = r#"DEBUG(*[2]/*[3][text()='3'])"#;
2773 let result = MyXPath::add_debug_string_arg(str);
2774 assert!(result.is_ok());
2775 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='3'], "*[2]/*[3][text()='3']" )"#);
2776 }
2777
2778 #[test]
2779 fn test_debug_quoted_paren() {
2780 let str = r#"DEBUG(*[2]/*[3][text()='('])"#;
2781 let result = MyXPath::add_debug_string_arg(str);
2782 assert!(result.is_ok());
2783 assert_eq!(result.unwrap(), r#"DEBUG(*[2]/*[3][text()='('], "*[2]/*[3][text()='(']" )"#);
2784 }
2785
2786 #[test]
2787 fn test_debug_quoted_paren_before_paren() {
2788 let str = r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics') and IsBracketed(., '(', ')')"#;
2789 let result = MyXPath::add_debug_string_arg(str);
2790 assert!(result.is_ok());
2791 assert_eq!(result.unwrap(), r#"DEBUG(ClearSpeak_Matrix = 'Combinatorics', "ClearSpeak_Matrix = 'Combinatorics'" ) and IsBracketed(., '(', ')')"#);
2792 }
2793
2794
2795 #[test]
2796 fn test_up_to_date() {
2797 use crate::interface::*;
2798 set_rules_dir(super::super::abs_rules_dir_path()).unwrap();
2800 set_preference("Language".to_string(), "zz-aa".to_string()).unwrap();
2801 if let Err(e) = set_mathml("<math><mi>x</mi></math>".to_string()) {
2803 error!("{}", crate::errors_to_string(&e));
2804 panic!("Should not be an error in setting MathML")
2805 }
2806
2807 set_preference("CheckRuleFiles".to_string(), "All".to_string()).unwrap();
2808 assert!(!is_file_time_same(), "file's time did not get updated");
2809 set_preference("CheckRuleFiles".to_string(), "None".to_string()).unwrap();
2810 assert!(is_file_time_same(), "file's time was wrongly updated (preference 'CheckRuleFiles' should have prevented updating)");
2811
2812 fn is_file_time_same() -> bool {
2814 use std::time::Duration;
2818 return SPEECH_RULES.with(|rules| {
2819 let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone();
2820
2821 let contents = std::fs::read(&start_main_file.file).expect(&format!("Failed to read file {} during test", &start_main_file.file.to_string_lossy()));
2823 std::fs::write(start_main_file.file, contents).unwrap();
2824 std::thread::sleep(Duration::from_millis(5)); if let Err(e) = get_spoken_text() {
2828 error!("{}", crate::errors_to_string(&e));
2829 panic!("Should not be an error in speech")
2830 }
2831 return rules.borrow().unicode_short_files.borrow().ft[0].time == start_main_file.time;
2832 });
2833 }
2834 }
2835
2836 }