11320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/* Copyright (c) 2014 The Chromium Authors. All rights reserved. 21320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Use of this source code is governed by a BSD-style license that can be 31320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * found in the LICENSE file. */ 41320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 61320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @fileoverview Low-level DOM traversal utility functions to find the 71320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * next (or previous) character, word, sentence, line, or paragraph, 81320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * in a completely stateless manner without actually manipulating the 91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * selection. 101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * A class to represent a cursor location in the document, 141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * like the start position or end position of a selection range. 151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Later this may be extended to support "virtual text" for an object, 171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * like the ALT text for an image. 181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Note: we cache the text of a particular node at the time we 201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * traverse into it. Later we should add support for dynamically 211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * reloading it. 221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Node} node The DOM node. 231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {number} index The index of the character within the node. 241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {string} text The cached text contents of the node. 251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @constructor 261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCursor = function(node, index, text) { 281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.node = node; 291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.index = index; 301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.text = text; 311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {Cursor} A new cursor pointing to the same location. 351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCursor.prototype.clone = function() { 371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return new Cursor(this.node, this.index, this.text); 381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Modify this cursor to point to the location that another cursor points to. 421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} otherCursor The cursor to copy from. 431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCursor.prototype.copyFrom = function(otherCursor) { 451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.node = otherCursor.node; 461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.index = otherCursor.index; 471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci this.text = otherCursor.text; 481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Utility functions for stateless DOM traversal. 521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @constructor 531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil = function() {}; 551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Gets the text representation of a node. This allows us to substitute 581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * alt text, names, or titles for html elements that provide them. 591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Node} node A DOM node. 601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {string} A text string representation of the node. 611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNodeText = function(node) { 631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (node.constructor == Text) { 641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return node.data; 651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } else { 661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ''; 671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Return true if a node should be treated as a leaf node, because 721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * its children are properties of the object that shouldn't be traversed. 731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * TODO(dmazzoni): replace this with a predicate that detects nodes with 751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * ARIA roles and other objects that have their own description. 761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * For now we just detect a couple of common cases. 771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Node} node A DOM node. 791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {boolean} True if the node should be treated as a leaf node. 801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.treatAsLeafNode = function(node) { 821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return node.childNodes.length == 0 || 831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node.nodeName == 'SELECT' || 841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node.nodeName == 'OBJECT'; 851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Return true only if a single character is whitespace. 891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * From https://developer.mozilla.org/en/Whitespace_in_the_DOM, 901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * whitespace is defined as one of the characters 911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * "\t" TAB \u0009 921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * "\n" LF \u000A 931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * "\r" CR \u000D 941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * " " SPC \u0020. 951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {string} c A string containing a single character. 971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {boolean} True if the character is whitespace, otherwise false. 981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.isWhitespace = function(c) { 1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return (c == ' ' || c == '\n' || c == '\r' || c == '\t'); 1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Set the selection to the range between the given start and end cursors. 1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} start The desired start of the selection. 1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} end The desired end of the selection. 1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {Selection} the selection object. 1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.setSelection = function(start, end) { 1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var sel = window.getSelection(); 1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci sel.removeAllRanges(); 1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var range = document.createRange(); 1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci range.setStart(start.node, start.index); 1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci range.setEnd(end.node, end.index); 1151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci sel.addRange(range); 1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return sel; 1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Use the computed CSS style to figure out if this DOM node is currently 1221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * visible. 1231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Node} node A HTML DOM node. 1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {boolean} Whether or not the html node is visible. 1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.isVisible = function(node) { 1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (!node.style) 1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 1291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(/** @type {Element} */(node), null); 1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return (!!style && style.display != 'none' && style.visibility != 'hidden'); 1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Use the class name to figure out if this DOM node should be traversed. 1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Node} node A HTML DOM node. 1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {boolean} Whether or not the html node should be traversed. 1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 1381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.isSkipped = function(node) { 1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (node.constructor == Text) 1401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node = node.parentElement; 1411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (node.className == 'CaretBrowsing_Caret' || 1421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node.className == 'CaretBrowsing_AnimateCaret') { 1431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 1471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 1491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Moves the cursor forwards until it has crossed exactly one character. 1501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} cursor The cursor location where the search should start. 1511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * On exit, the cursor will be immediately to the right of the 1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * character returned. 1531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 1541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 1551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The character found, or null if the bottom of the 1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 1571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 1581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.forwardsChar = function(cursor, nodesCrossed) { 1591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (true) { 1601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Move down until we get to a leaf node. 1611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var childNode = null; 1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (!TraverseUtil.treatAsLeafNode(cursor.node)) { 1631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = cursor.index; i < cursor.node.childNodes.length; i++) { 1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var node = cursor.node.childNodes[i]; 1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(node)) { 1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(node); 1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 1681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isVisible(node)) { 1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci childNode = node; 1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (childNode) { 1761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = childNode; 1771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = 0; 1781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = TraverseUtil.getNodeText(cursor.node); 1791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.constructor != Text) { 1801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(cursor.node); 1811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 1831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 1841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Return the next character from this leaf node. 1861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.index < cursor.text.length) 1871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return cursor.text[cursor.index++]; 1881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Move to the next sibling, going up the tree as necessary. 1901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (cursor.node != null) { 1911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Try to move to the next sibling. 1921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var siblingNode = null; 1931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var node = cursor.node.nextSibling; 1941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node != null; 1951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node = node.nextSibling) { 1961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(node)) { 1971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(node); 1981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 1991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isVisible(node)) { 2011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci siblingNode = node; 2021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 2031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (siblingNode) { 2061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = siblingNode; 2071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = TraverseUtil.getNodeText(siblingNode); 2081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = 0; 2091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.constructor != Text) { 2111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(cursor.node); 2121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 2151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Otherwise, move to the parent. 2181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.parentNode && 2191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node.parentNode.constructor != HTMLBodyElement) { 2201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = cursor.node.parentNode; 2211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = null; 2221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = 0; 2231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } else { 2241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 2251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 2291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 2311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Moves the cursor backwards until it has crossed exactly one character. 2321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} cursor The cursor location where the search should start. 2331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * On exit, the cursor will be immediately to the left of the 2341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * character returned. 2351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 2361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 2371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous character, or null if the top of the 2381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 2391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 2401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.backwardsChar = function(cursor, nodesCrossed) { 2411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (true) { 2421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Move down until we get to a leaf node. 2431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var childNode = null; 2441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (!TraverseUtil.treatAsLeafNode(cursor.node)) { 2451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = cursor.index - 1; i >= 0; i--) { 2461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var node = cursor.node.childNodes[i]; 2471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(node)) { 2481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(node); 2491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 2501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isVisible(node)) { 2521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci childNode = node; 2531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 2541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (childNode) { 2581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = childNode; 2591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = TraverseUtil.getNodeText(cursor.node); 2601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.text.length) 2611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = cursor.text.length; 2621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci else 2631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = cursor.node.childNodes.length; 2641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.constructor != Text) 2651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(cursor.node); 2661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 2671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Return the previous character from this leaf node. 2701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.text.length > 0 && cursor.index > 0) { 2711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return cursor.text[--cursor.index]; 2721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 2741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Move to the previous sibling, going up the tree as necessary. 2751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (true) { 2761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Try to move to the previous sibling. 2771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var siblingNode = null; 2781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var node = cursor.node.previousSibling; 2791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node != null; 2801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci node = node.previousSibling) { 2811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(node)) { 2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(node); 2831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci continue; 2841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isVisible(node)) { 2861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci siblingNode = node; 2871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 2881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 2901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (siblingNode) { 2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = siblingNode; 2921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = TraverseUtil.getNodeText(siblingNode); 2931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.text.length) 2941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = cursor.text.length; 2951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci else 2961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = cursor.node.childNodes.length; 2971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.constructor != Text) 2981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed.push(cursor.node); 2991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci break; 3001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Otherwise, move to the parent. 3031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (cursor.node.parentNode && 3041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node.parentNode.constructor != HTMLBodyElement) { 3051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.node = cursor.node.parentNode; 3061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.text = null; 3071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cursor.index = 0; 3081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } else { 3091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 3101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 3141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 3161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next character, starting from endCursor. Upon exit, startCursor 3171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * and endCursor will surround the next character. If skipWhitespace is 3181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * true, will skip until a real character is found. Otherwise, it will 3191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * attempt to select all of the whitespace between the initial position 3201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * of endCursor and the next non-whitespace character. 3211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, points to the position before 3221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the char. 3231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 3241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * char. On exit, will point to the position past the char. 3251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 3261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 3271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {boolean} skipWhitespace If true, will keep scanning until a 3281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * non-whitespace character is found. 3291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next char, or null if the bottom of the 3301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 3311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 3321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextChar = function( 3331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, skipWhitespace) { 3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Save the starting position and get the first character. 3361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(endCursor); 3371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var c = TraverseUtil.forwardsChar(endCursor, nodesCrossed); 3381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 3391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 3401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep track of whether the first character was whitespace. 3421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var initialWhitespace = TraverseUtil.isWhitespace(c); 3431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep scanning until we find a non-whitespace or non-skipped character. 3451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while ((TraverseUtil.isWhitespace(c)) || 3461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (TraverseUtil.isSkipped(endCursor.node))) { 3471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.forwardsChar(endCursor, nodesCrossed); 3481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 3491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 3501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (skipWhitespace || !initialWhitespace) { 3521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // If skipWhitepace is true, or if the first character we encountered 3531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // was not whitespace, return that non-whitespace character. 3541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(endCursor); 3551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.index--; 3561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return c; 3571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci else { 3591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodesCrossed.length; i++) { 3601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodesCrossed[i])) { 3611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // We need to make sure that startCursor and endCursor aren't 3621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // surrounding a skippable node. 3631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.index--; 3641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(endCursor); 3651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.index--; 3661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ' '; 3671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Otherwise, return all of the whitespace before that last character. 3701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.index--; 3711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ' '; 3721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 3731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 3741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 3761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous character, starting from startCursor. Upon exit, 3771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the previous character. 3781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * If skipWhitespace is true, will skip until a real character is found. 3791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Otherwise, it will attempt to select all of the whitespace between 3801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the initial position of endCursor and the next non-whitespace character. 3811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the 3821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * char. On exit, will point to the position before the char. 3831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 3841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * char. On exit, will point to the position past the char. 3851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 3861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 3871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {boolean} skipWhitespace If true, will keep scanning until a 3881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * non-whitespace character is found. 3891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous char, or null if the top of the 3901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 3911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 3921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousChar = function( 3931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, skipWhitespace) { 3941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 3951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Save the starting position and get the first character. 3961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(startCursor); 3971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var c = TraverseUtil.backwardsChar(startCursor, nodesCrossed); 3981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 3991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 4001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep track of whether the first character was whitespace. 4021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var initialWhitespace = TraverseUtil.isWhitespace(c); 4031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep scanning until we find a non-whitespace or non-skipped character. 4051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while ((TraverseUtil.isWhitespace(c)) || 4061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (TraverseUtil.isSkipped(startCursor.node))) { 4071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.backwardsChar(startCursor, nodesCrossed); 4081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 4091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 4101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (skipWhitespace || !initialWhitespace) { 4121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // If skipWhitepace is true, or if the first character we encountered 4131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // was not whitespace, return that non-whitespace character. 4141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(startCursor); 4151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.index++; 4161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return c; 4171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } else { 4181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodesCrossed.length; i++) { 4191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodesCrossed[i])) { 4201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.index++; 4211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(startCursor); 4221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.index++; 4231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ' '; 4241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Otherwise, return all of the whitespace before that last character. 4271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.index++; 4281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ' '; 4291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 4311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 4331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next word, starting from endCursor. Upon exit, startCursor 4341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * and endCursor will surround the next word. A word is defined to be 4351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * a string of 1 or more non-whitespace characters in the same DOM node. 4361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, will point to the beginning of the 4371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * word returned. 4381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 4391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * word. On exit, will point to the end of the word returned. 4401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 4411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 4421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next word, or null if the bottom of the 4431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 4441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 4451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextWord = function(startCursor, endCursor, 4461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed) { 4471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Find the first non-whitespace or non-skipped character. 4491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var cursor = endCursor.clone(); 4501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var c = TraverseUtil.forwardsChar(cursor, nodesCrossed); 4511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 4521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 4531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while ((TraverseUtil.isWhitespace(c)) || 4541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (TraverseUtil.isSkipped(cursor.node))) { 4551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.forwardsChar(cursor, nodesCrossed); 4561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 4571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 4581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Set startCursor to the position immediately before the first 4611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // character in our word. It's safe to decrement |index| because 4621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // forwardsChar guarantees that the cursor will be immediately to the 4631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // right of the returned character on exit. 4641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(cursor); 4651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.index--; 4661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep building up our word until we reach a whitespace character or 4681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // would cross a tag. Don't actually return any tags crossed, because this 4691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // word goes up until the tag boundary but not past it. 4701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(cursor); 4711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var word = c; 4721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var newNodesCrossed = []; 4731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.forwardsChar(cursor, newNodesCrossed); 4741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) { 4751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 4761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (!TraverseUtil.isWhitespace(c) && 4781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci newNodesCrossed.length == 0) { 4791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci word += c; 4801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(cursor); 4811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.forwardsChar(cursor, newNodesCrossed); 4821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) { 4831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 4841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 4861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 4871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 4881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 4891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 4901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous word, starting from startCursor. Upon exit, startCursor 4911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * and endCursor will surround the previous word. A word is defined to be 4921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * a string of 1 or more non-whitespace characters in the same DOM node. 4931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the 4941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * previous word. On exit, will point to the beginning of the 4951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * word returned. 4961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor On exit, will point to the end of the 4971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * word returned. 4981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 4991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 5001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous word, or null if the bottom of the 5011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 5021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 5031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousWord = function(startCursor, endCursor, 5041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed) { 5051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Find the first non-whitespace or non-skipped character. 5061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var cursor = startCursor.clone(); 5071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var c = TraverseUtil.backwardsChar(cursor, nodesCrossed); 5081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 5091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 5101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while ((TraverseUtil.isWhitespace(c) || 5111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (TraverseUtil.isSkipped(cursor.node)))) { 5121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.backwardsChar(cursor, nodesCrossed); 5131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 5141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 5151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 5171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Set endCursor to the position immediately after the first 5181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // character we've found (the last character of the word, since we're 5191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // searching backwards). 5201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(cursor); 5211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.index++; 5221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 5231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Keep building up our word until we reach a whitespace character or 5241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // would cross a tag. Don't actually return any tags crossed, because this 5251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // word goes up until the tag boundary but not past it. 5261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(cursor); 5271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var word = c; 5281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var newNodesCrossed = []; 5291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.backwardsChar(cursor, newNodesCrossed); 5301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 5311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 5321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (!TraverseUtil.isWhitespace(c) && 5331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci newNodesCrossed.length == 0) { 5341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci word = c + word; 5351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(cursor); 5361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci c = TraverseUtil.backwardsChar(cursor, newNodesCrossed); 5371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (c == null) 5381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 5391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 5411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return word; 5421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 5431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 5441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 5451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next sentence, starting from endCursor. Upon exit, 5461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the next sentence. 5471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 5481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, marks the beginning of the sentence. 5491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 5501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * sentence. On exit, will point to the end of the returned string. 5511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 5521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 5531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Object} breakTags Associative array of tags that should break 5541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the sentence. 5551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next sentence, or null if the bottom of the 5561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 5571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 5581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextSentence = function( 5591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, breakTags) { 5601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getNextString( 5611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 5621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 5631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (str.substr(-1) == '.') 5641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 5651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 5661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 5671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 5681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 5701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && (style.display != 'inline' || 5711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci breakTags[nodes[i].tagName])) { 5721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 5731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 5761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 5771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 5781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 5791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 5801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous sentence, starting from startCursor. Upon exit, 5811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the previous sentence. 5821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 5831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the next 5841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * sentence. On exit, will point to the start of the returned string. 5851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor On exit, the end of the returned string. 5861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 5871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 5881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Object} breakTags Associative array of tags that should break 5891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the sentence. 5901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous sentence, or null if the bottom of the 5911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 5921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 5931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousSentence = function( 5941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, breakTags) { 5951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getPreviousString( 5961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 5971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 5981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (word.substr(-1) == '.') 5991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 6011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 6021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 6051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && (style.display != 'inline' || 6061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci breakTags[nodes[i].tagName])) { 6071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 6111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 6121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 6131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 6141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 6151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next line, starting from endCursor. Upon exit, 6161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the next line. 6171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 6181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, marks the beginning of the line. 6191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 6201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * line. On exit, will point to the end of the returned string. 6211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 6221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 6231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {number} lineLength The maximum number of characters in a line. 6241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Object} breakTags Associative array of tags that should break 6251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the line. 6261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next line, or null if the bottom of the 6271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 6281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 6291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextLine = function( 6301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, lineLength, breakTags) { 6311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getNextString( 6321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 6331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 6341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (str.length + word.length + 1 > lineLength) 6351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 6371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 6381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 6411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && (style.display != 'inline' || 6421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci breakTags[nodes[i].tagName])) { 6431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 6471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 6481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 6491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 6501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 6511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous line, starting from startCursor. Upon exit, 6521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the previous line. 6531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 6541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the next 6551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * line. On exit, will point to the start of the returned string. 6561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor On exit, the end of the returned string. 6571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 6581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 6591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {number} lineLength The maximum number of characters in a line. 6601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Object} breakTags Associative array of tags that should break 6611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the sentence. 6621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous line, or null if the bottom of the 6631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 6641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 6651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousLine = function( 6661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, lineLength, breakTags) { 6671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getPreviousString( 6681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 6691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 6701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (str.length + word.length + 1 > lineLength) 6711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 6731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 6741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 6771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && (style.display != 'inline' || 6781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci breakTags[nodes[i].tagName])) { 6791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 6801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 6821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 6831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 6841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 6851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 6861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 6871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next paragraph, starting from endCursor. Upon exit, 6881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the next paragraph. 6891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 6901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, marks the beginning of the paragraph. 6911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 6921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * paragraph. On exit, will point to the end of the returned string. 6931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 6941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 6951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next paragraph, or null if the bottom of the 6961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 6971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 6981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextParagraph = function(startCursor, endCursor, 6991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed) { 7001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getNextString( 7011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 7021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 7031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 7041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 7051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 7061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 7081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && style.display != 'inline') { 7091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 7101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 7131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 7141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 7151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 7161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 7171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous paragraph, starting from startCursor. Upon exit, 7181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the previous paragraph. 7191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 7201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the next 7211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * paragraph. On exit, will point to the start of the returned string. 7221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor On exit, the end of the returned string. 7231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 7241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 7251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The previous paragraph, or null if the bottom of the 7261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 7271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 7281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousParagraph = function( 7291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed) { 7301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return TraverseUtil.getPreviousString( 7311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, 7321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(str, word, nodes) { 7331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for (var i = 0; i < nodes.length; i++) { 7341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (TraverseUtil.isSkipped(nodes[i])) { 7351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 7361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var style = window.getComputedStyle(nodes[i], null); 7381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (style && style.display != 'inline') { 7391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return true; 7401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 7421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 7431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 7441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 7451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 7461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 7471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Customizable function to return the next string of words in the DOM, based 7481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * on provided functions to decide when to break one string and start 7491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the next. This can be used to get the next sentence, line, paragraph, 7501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * or potentially other granularities. 7511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 7521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the next contiguous string, starting from endCursor. Upon exit, 7531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the next string. 7541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 7551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * The breakBefore function takes three parameters, and 7561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * should return true if the string should be broken before the proposed 7571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * next word: 7581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * str The string so far. 7591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * word The next word to be added. 7601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * nodesCrossed The nodes crossed in reaching this next word. 7611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 7621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor On exit, will point to the beginning of the 7631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * next string. 7641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor The position to start searching for the next 7651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * string. On exit, will point to the end of the returned string. 7661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 7671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 7681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {function(string, string, Array.<string>)} breakBefore 7691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Function that takes the string so far, next word to be added, and 7701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * nodes crossed, and returns true if the string should be ended before 7711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * adding this word. 7721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next string, or null if the bottom of the 7731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 7741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 7751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getNextString = function( 7761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, breakBefore) { 7771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Get the first word and set the start cursor to the start of the 7781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // first word. 7791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var wordStartCursor = endCursor.clone(); 7801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var wordEndCursor = endCursor.clone(); 7811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var newNodesCrossed = []; 7821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var str = ''; 7831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var word = TraverseUtil.getNextWord( 7841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci wordStartCursor, wordEndCursor, newNodesCrossed); 7851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (word == null) 7861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 7871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(wordStartCursor); 7881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 7891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Always add the first word when the string is empty, and then keep 7901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // adding more words as long as breakBefore returns false 7911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (!str || !breakBefore(str, word, newNodesCrossed)) { 7921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Append this word, set the end cursor to the end of this word, and 7931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // update the returned list of nodes crossed to include ones we crossed 7941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // in reaching this word. 7951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (str) 7961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci str += ' '; 7971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci str += word; 7981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed = nodesCrossed.concat(newNodesCrossed); 7991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(wordEndCursor); 8001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 8011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Get the next word and go back to the top of the loop. 8021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci newNodesCrossed = []; 8031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci word = TraverseUtil.getNextWord( 8041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci wordStartCursor, wordEndCursor, newNodesCrossed); 8051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (word == null) 8061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return str; 8071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 8081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 8091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return str; 8101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 8111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 8121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci/** 8131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Customizable function to return the previous string of words in the DOM, 8141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * based on provided functions to decide when to break one string and start 8151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * the next. See getNextString, above, for more details. 8161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 8171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Finds the previous contiguous string, starting from startCursor. Upon exit, 8181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * startCursor and endCursor will surround the next string. 8191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * 8201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} startCursor The position to start searching for the 8211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * previous string. On exit, will point to the beginning of the 8221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * string returned. 8231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Cursor} endCursor On exit, will point to the end of the 8241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * string returned. 8251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the 8261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * initial and final cursor position will be pushed onto this array. 8271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {function(string, string, Array.<string>)} breakBefore 8281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * Function that takes the string so far, the word to be added, and 8291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * nodes crossed, and returns true if the string should be ended before 8301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * adding this word. 8311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @return {?string} The next string, or null if the top of the 8321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * document has been reached. 8331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci */ 8341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciTraverseUtil.getPreviousString = function( 8351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor, endCursor, nodesCrossed, breakBefore) { 8361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Get the first word and set the end cursor to the end of the 8371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // first word. 8381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var wordStartCursor = startCursor.clone(); 8391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var wordEndCursor = startCursor.clone(); 8401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var newNodesCrossed = []; 8411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var str = ''; 8421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci var word = TraverseUtil.getPreviousWord( 8431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci wordStartCursor, wordEndCursor, newNodesCrossed); 8441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (word == null) 8451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return null; 8461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci endCursor.copyFrom(wordEndCursor); 8471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 8481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Always add the first word when the string is empty, and then keep 8491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // adding more words as long as breakBefore returns false 8501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci while (!str || !breakBefore(str, word, newNodesCrossed)) { 8511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Prepend this word, set the start cursor to the start of this word, and 8521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // update the returned list of nodes crossed to include ones we crossed 8531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // in reaching this word. 8541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (str) 8551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci str = ' ' + str; 8561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci str = word + str; 8571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nodesCrossed = nodesCrossed.concat(newNodesCrossed); 8581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci startCursor.copyFrom(wordStartCursor); 8591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciv 8601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci // Get the previous word and go back to the top of the loop. 8611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci newNodesCrossed = []; 8621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci word = TraverseUtil.getPreviousWord( 8631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci wordStartCursor, wordEndCursor, newNodesCrossed); 8641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (word == null) 8651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return str; 8661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 8671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 8681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return str; 8691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}; 870