Home | History | Annotate | Download | only in common
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 goog.provide('cvox.TraverseMath');
      6 
      7 goog.require('cvox.ChromeVox');
      8 goog.require('cvox.DomUtil');
      9 goog.require('cvox.SemanticTree');
     10 
     11 
     12 /**
     13  * Initializes the traversal with the provided math node.
     14  *
     15  * @constructor
     16  */
     17 cvox.TraverseMath = function() {
     18   /**
     19    * The active math <MATH> node. In this context, "active" means that this is
     20    * the math expression the TraverseMath object is navigating.
     21    * @type {Node}
     22    */
     23   this.activeMath = null;
     24 
     25   /**
     26    * The node currently under inspection.
     27    * @type {Node}
     28    */
     29   this.activeNode = null;
     30 
     31   /**
     32    * Dictionary of all LaTeX elements in the page if there are any.
     33    * @type {!Object.<string, !Node>}
     34    * @private
     35    */
     36   this.allTexs_ = {};
     37 
     38   /**
     39    * Dictionary of all MathJaxs elements in the page if there are any.
     40    * @type {!Object.<string, !Node>}
     41    * @private
     42    */
     43   this.allMathjaxs_ = {};
     44 
     45   /**
     46    * Dictionary of all MathJaxs elements that have not yet been translated at
     47    * page load or during MathJax rendering.
     48    * @type {!Object.<string, !Node>}
     49    * @private
     50    */
     51   this.todoMathjaxs_ = {};
     52 
     53   /**
     54    * When traversing a Mathjax node this will contain the internal
     55    * MathML representation of the node.
     56    * @type {Node}
     57    */
     58   this.activeMathmlHost = null;
     59 
     60   /**
     61    * Semantic representation of the current node.
     62    * @type {Node}
     63    */
     64   this.activeSemanticHost = null;
     65 
     66   /**
     67    * List of domain names.
     68    * @type {Array.<string>}
     69    */
     70   this.allDomains = [];
     71 
     72   /**
     73    * List of style names.
     74    * @type {Array.<string>}
     75    */
     76   this.allStyles = [];
     77 
     78   /**
     79    * Current domain.
     80    * @type {string}
     81    */
     82   this.domain = 'default';
     83 
     84   /**
     85    * Current style.
     86    * @type {string}
     87    */
     88   this.style = 'short';
     89 
     90   /**
     91    * Initialize special objects if necessary.
     92    */
     93   if (cvox.ChromeVox.mathJax) {
     94     this.initializeMathjaxs();
     95     this.initializeAltMaths();
     96   }
     97 };
     98 goog.addSingletonGetter(cvox.TraverseMath);
     99 
    100 
    101 /**
    102  * @type {boolean}
    103  * @private
    104  */
    105 cvox.TraverseMath.setSemantic_ = false;
    106 
    107 
    108 /**
    109  * Toggles the semantic setting.
    110  * @return {boolean} True if semantic interpretation is switched on. False
    111  *     otherwise.
    112  */
    113 cvox.TraverseMath.toggleSemantic = function() {
    114   return cvox.TraverseMath.setSemantic_ = !cvox.TraverseMath.setSemantic_;
    115 };
    116 
    117 
    118 /**
    119  * Initializes a traversal of a math expression.
    120  * @param {Node} node A MathML node.
    121  */
    122 cvox.TraverseMath.prototype.initialize = function(node) {
    123   if (cvox.DomUtil.isMathImg(node)) {
    124     // If a node has a cvoxid attribute we know that it contains a LaTeX
    125     // expression that we have rewritten into its corresponding MathML
    126     // representation, which we can speak and walk.
    127     if (!node.hasAttribute('cvoxid')) {
    128       return;
    129     }
    130     var cvoxid = node.getAttribute('cvoxid');
    131     node = this.allTexs_[cvoxid];
    132   }
    133   if (cvox.DomUtil.isMathJax(node)) {
    134       this.activeMathmlHost = this.allMathjaxs_[node.getAttribute('id')];
    135   }
    136   this.activeMath = this.activeMathmlHost || node;
    137   this.activeNode = this.activeMathmlHost || node;
    138   if (this.activeNode && cvox.TraverseMath.setSemantic_ &&
    139       this.activeNode.nodeType == Node.ELEMENT_NODE) {
    140     this.activeNode =
    141         (new cvox.SemanticTree(/** @type {!Element} */ (this.activeNode))).xml();
    142   }
    143 };
    144 
    145 
    146 /**
    147  * Adds a mapping of a MathJax node to its MathML representation to the
    148  * dictionary of MathJax elements.
    149  * @param {!Node} mml The MathML node.
    150  * @param {string} id The MathJax node id.
    151  */
    152 cvox.TraverseMath.prototype.addMathjax = function(mml, id) {
    153   var spanId = cvox.DomUtil.getMathSpanId(id);
    154   if (spanId) {
    155     this.allMathjaxs_[spanId] = mml;
    156   } else {
    157     this.redoMathjaxs(mml, id);
    158   }
    159 };
    160 
    161 
    162 /**
    163  * Retries to compute MathML representations of MathJax elements, if
    164  * they have not been filled in during rendering.
    165  * @param {!Node} mml The MathML node.
    166  * @param {string} id The MathJax node id.
    167  */
    168 cvox.TraverseMath.prototype.redoMathjaxs = function(mml, id) {
    169   var fetch = goog.bind(function() {this.addMathjax(mml, id);}, this);
    170   setTimeout(fetch, 500);
    171 };
    172 
    173 
    174 /**
    175  * Initializes the MathJax to MathML mapping.
    176  * We first try to get all MathJax elements that are already being rendered.
    177  * Secondly, we register a signal to get updated on all elements that are
    178  * rendered or re-rendered later.
    179  */
    180 cvox.TraverseMath.prototype.initializeMathjaxs = function() {
    181   var callback =
    182       goog.bind(function(mml, id) {
    183                   this.addMathjax(mml, id);
    184                 }, this);
    185   cvox.ChromeVox.mathJax.isMathjaxActive(
    186       function(bool) {
    187         if (bool) {
    188           cvox.ChromeVox.mathJax.getAllJax(callback);
    189           cvox.ChromeVox.mathJax.registerSignal(callback, 'New Math');
    190         }
    191       });
    192 };
    193 
    194 
    195 /**
    196  * Initializes the elements in the page that we identify as potentially
    197  * containing tex or asciimath alt text.
    198  */
    199 cvox.TraverseMath.prototype.initializeAltMaths = function() {
    200   if (!document.querySelector(
    201       cvox.DomUtil.altMathQuerySelector('tex') + ', ' +
    202           cvox.DomUtil.altMathQuerySelector('asciimath'))) {
    203     return;
    204   }
    205   var callback = goog.bind(
    206       function(mml, id) {
    207         this.allTexs_[id] = mml;
    208       }, this);
    209   // Inject a minimalistic version of MathJax into the page.
    210   cvox.ChromeVox.mathJax.injectScripts();
    211   // Once MathJax is injected we harvest all Latex and AsciiMath in alt
    212   // attributes and translate them to MathML expression.
    213   cvox.ChromeVox.mathJax.isMathjaxActive(
    214       function(active) {
    215         if (active) {
    216           cvox.ChromeVox.mathJax.configMediaWiki();
    217           cvox.ChromeVox.mathJax.getAllTexs(callback);
    218           cvox.ChromeVox.mathJax.getAllAsciiMaths(callback);
    219         }
    220       });
    221 };
    222 
    223 
    224 /**
    225  * Moves to the next leaf node in the current Math expression if it exists.
    226  * @param {boolean} reverse True if reversed. False by default.
    227  * @param {function(!Node):boolean} pred Predicate deciding what a leaf is.
    228  * @return {Node} The next node.
    229  */
    230 cvox.TraverseMath.prototype.nextLeaf = function(reverse, pred) {
    231   if (this.activeNode && this.activeMath) {
    232     var next = pred(this.activeNode) ?
    233       cvox.DomUtil.directedFindNextNode(
    234           this.activeNode, this.activeMath, reverse, pred) :
    235       cvox.DomUtil.directedFindFirstNode(this.activeNode, reverse, pred);
    236     if (next) {
    237       this.activeNode = next;
    238     }
    239   }
    240   return this.activeNode;
    241 };
    242 
    243 
    244 // TODO (sorge) Refactor this logic into single walkers.
    245 /**
    246  * Returns a string with the content of the active node.
    247  * @return {string} The active content.
    248  */
    249 cvox.TraverseMath.prototype.activeContent = function() {
    250   return this.activeNode.textContent;
    251 };
    252 
    253 
    254 /**
    255  * Moves to the next subtree from a given node in a depth first fashion.
    256  * @param {boolean} reverse True if reversed. False by default.
    257  * @param {function(!Node):boolean} pred Predicate deciding what a subtree is.
    258  * @return {Node} The next subtree.
    259  */
    260 cvox.TraverseMath.prototype.nextSubtree = function(reverse, pred) {
    261   if (!this.activeNode || !this.activeMath) {
    262     return null;
    263   }
    264   if (!reverse) {
    265     var child = cvox.DomUtil.directedFindFirstNode(
    266         this.activeNode, reverse, pred);
    267     if (child) {
    268       this.activeNode = child;
    269     } else {
    270       var next = cvox.DomUtil.directedFindNextNode(
    271           this.activeNode, this.activeMath, reverse, pred);
    272       if (next) {
    273           this.activeNode = next;
    274       }
    275     }
    276   } else {
    277     if (this.activeNode == this.activeMath) {
    278       var child = cvox.DomUtil.directedFindDeepestNode(
    279           this.activeNode, reverse, pred);
    280       if (child != this.activeNode) {
    281         this.activeNode = child;
    282         return this.activeNode;
    283       }
    284     }
    285     var prev = cvox.DomUtil.directedFindNextNode(
    286       this.activeNode, this.activeMath, reverse, pred, true, true);
    287     if (prev) {
    288       this.activeNode = prev;
    289     }
    290   }
    291   return this.activeNode;
    292 };
    293 
    294 
    295 /**
    296  * left or right in the math expression.
    297  * Navigation is bounded by the presence of a sibling.
    298  * @param {boolean} r True to move left; false to move right.
    299  * @return {Node} The result.
    300  */
    301 cvox.TraverseMath.prototype.nextSibling = function(r) {
    302   if (!this.activeNode || !this.activeMath) {
    303     return null;
    304   }
    305   var node = this.activeNode;
    306       node = r ? node.previousSibling : node.nextSibling;
    307   if (!node) {
    308     return null;
    309   }
    310   this.activeNode = node;
    311   return this.activeNode;
    312 };
    313 
    314 
    315 /**
    316  * Moves up or down the math expression.
    317  * Navigation is bounded by the root math expression.
    318  * @param {boolean} r True to move up; false to move down.
    319  * @return {Node} The result.
    320  */
    321 cvox.TraverseMath.prototype.nextParentChild = function(r) {
    322   if (!this.activeNode || !this.activeMath) {
    323     return null;
    324   }
    325   if (this.activeNode == this.activeMath && r) {
    326     return null;
    327   }
    328   var node = this.activeNode;
    329   node = r ? node.parentNode : node.firstChild;
    330   if (!node) {
    331     return null;
    332   }
    333   this.activeNode = node;
    334   return this.activeNode;
    335 };
    336 
    337 
    338 /**
    339  * Adds a list of domains and styles to the existing one.
    340  * @param {Array.<string>} domains List of domain names.
    341  * @param {Array.<string>} styles List of style names.
    342  */
    343 cvox.TraverseMath.prototype.addDomainsAndStyles = function(domains, styles) {
    344   this.allDomains.push.apply(
    345       this.allDomains,
    346       domains.filter(
    347           goog.bind(function(x) {return this.allDomains.indexOf(x) < 0;},
    348                     this)));
    349   this.allStyles.push.apply(
    350       this.allStyles,
    351       styles.filter(
    352           goog.bind(function(x) {return this.allStyles.indexOf(x) < 0;},
    353                     this)));
    354 };
    355 
    356 
    357 /**
    358  * Gets a list of domains and styles from the symbol and function mappings.
    359  * Depending on the platform they either live in the background page or
    360  * in the android math map.
    361  */
    362 cvox.TraverseMath.prototype.initDomainsAndStyles = function() {
    363   if (cvox.ChromeVox.host['mathMap']) {
    364     this.addDomainsAndStyles(
    365         cvox.ChromeVox.host['mathMap'].allDomains,
    366         cvox.ChromeVox.host['mathMap'].allStyles);
    367     } else {
    368       cvox.ChromeVox.host.sendToBackgroundPage(
    369           {'target': 'Math',
    370            'action': 'getDomains'});
    371     }
    372 };
    373 
    374 
    375 /**
    376  * Sets the domain for the TraverseMath object to the next one in the list
    377  * restarting from the first, if necessary.
    378  * @return {string} The name of the newly set domain.
    379  */
    380 cvox.TraverseMath.prototype.cycleDomain = function() {
    381   this.initDomainsAndStyles();
    382   var index = this.allDomains.indexOf(this.domain);
    383   if (index == -1) {
    384     return this.domain;
    385   }
    386   this.domain = this.allDomains[(++index) % this.allDomains.length];
    387   return this.domain;
    388 };
    389 
    390 
    391 /**
    392  * Sets the style for the TraverseMath object to the next one in the list
    393  * restarting from the first, if necessary.
    394  * @return {string} The name of the newly set style.
    395  */
    396 cvox.TraverseMath.prototype.cycleStyle = function() {
    397   this.initDomainsAndStyles();
    398   var index = this.allStyles.indexOf(this.style);
    399   if (index == -1) {
    400     return this.domain;
    401   }
    402   this.style = this.allStyles[(++index) % this.allStyles.length];
    403   return this.style;
    404 };
    405 
    406 
    407 /**
    408  *  Sets the domain for the TraverseMath object.
    409  * @param {string} domain Name of the domain.
    410  * @private
    411  */
    412 cvox.TraverseMath.prototype.setDomain_ = function(domain) {
    413   if (this.allDomains.indexOf(domain) != -1) {
    414     this.domain = domain;
    415   } else {
    416     this.domain = 'default';
    417   }
    418 };
    419 
    420 
    421 /**
    422  *  Sets the style for the TraverseMath object.
    423  * @param {string} style Name of the style.
    424  * @private
    425  */
    426 cvox.TraverseMath.prototype.setStyle_ = function(style) {
    427   if (this.allStyles.indexOf(style) != -1) {
    428     this.style = style;
    429   } else {
    430     this.style = 'default';
    431   }
    432 };
    433 
    434 
    435 /**
    436  * Gets the active node attached to the current document.
    437  * @return {Node} The active node, if it exists.
    438  */
    439 cvox.TraverseMath.prototype.getAttachedActiveNode = function() {
    440   var node = this.activeNode;
    441   if (!node || node.nodeType != Node.ELEMENT_NODE) {
    442     return null;
    443   }
    444   var id = node.getAttribute('spanID');
    445   return document.getElementById(id);
    446 };
    447