1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global Node, runtime, core, odf, ops*/
 26 
 27 /**
 28  * @constructor
 29  * @param {!function():!Node} getRootNode
 30  * @implements {core.PositionFilter}
 31  */
 32 ops.TextPositionFilter = function TextPositionFilter(getRootNode) {
 33     "use strict";
 34     var odfUtils = new odf.OdfUtils(),
 35         ELEMENT_NODE = Node.ELEMENT_NODE,
 36         TEXT_NODE = Node.TEXT_NODE,
 37         /**@const*/FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT,
 38         /**@const*/FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
 39 
 40     /**
 41      * @param {!Node} container
 42      * @param {?Node} leftNode
 43      * @param {?Node} rightNode
 44      * @return {!core.PositionFilter.FilterResult}
 45      */
 46     function checkLeftRight(container, leftNode, rightNode) {
 47         var r, firstPos, rightOfChar;
 48         // accept if there is a character immediately to the left
 49         if (leftNode) {
 50             if (odfUtils.isInlineRoot(leftNode) && odfUtils.isGroupingElement(rightNode)) {
 51                 // Move first position after inline root inside trailing grouping element (part 1)
 52                 // Disallow positions to the right of an inline root (like an annotation) and
 53                 // to the left of a grouping element (like an annotation highlight span)
 54                 return FILTER_REJECT;
 55             }
 56             r = odfUtils.lookLeftForCharacter(leftNode);
 57             if (r === 1) {// non-whitespace character or a character element
 58                 return FILTER_ACCEPT;
 59             }
 60             if (r === 2 && (odfUtils.scanRightForAnyCharacter(rightNode)
 61                 || odfUtils.scanRightForAnyCharacter(odfUtils.nextNode(container)))) {
 62                 // significant whitespace is ok, if not in trailing whitesp
 63                 return FILTER_ACCEPT;
 64             }
 65         } else {
 66             // Note, cant use OdfUtils.previousNode here as that function automatically dives to the previous
 67             // elements first child (if it has one)
 68             if (odfUtils.isInlineRoot(container.previousSibling) && odfUtils.isGroupingElement(container)) {
 69                 // Move first position after inline root inside trailing grouping element (part 2)
 70                 // Allow the first position inside the first grouping element trailing an annotation
 71                 return FILTER_ACCEPT;
 72             }
 73         }
 74         // at this point, we know that the position is not directly to the
 75         // right of a significant character or element. so the position is
 76         // only acceptable if it is the first in an empty p or h or if it
 77         // is to the left of the first significant character or element.
 78 
 79         // accept if this is the first position in p or h and there is no
 80         // character in the p or h
 81         firstPos = leftNode === null && odfUtils.isParagraph(container);
 82         rightOfChar = odfUtils.lookRightForCharacter(rightNode);
 83         if (firstPos) {
 84             if (rightOfChar) {
 85                 return FILTER_ACCEPT;
 86             }
 87             // position is first position in empty paragraph
 88             return odfUtils.scanRightForAnyCharacter(rightNode) ? FILTER_REJECT : FILTER_ACCEPT;
 89         }
 90         // if not directly to the right of a character, reject
 91         if (!rightOfChar) {
 92             return FILTER_REJECT;
 93         }
 94         // accept if there is no character to the left
 95         leftNode = leftNode || odfUtils.previousNode(container);
 96         return odfUtils.scanLeftForAnyCharacter(leftNode) ? FILTER_REJECT : FILTER_ACCEPT;
 97     }
 98 
 99     /**
100      * @param {!core.PositionIterator} iterator
101      * @return {!core.PositionFilter.FilterResult}
102      */
103     this.acceptPosition = function (iterator) {
104         var container = iterator.container(),
105             nodeType = container.nodeType,
106             /**@type{number}*/
107             offset,
108             /**@type{string}*/
109             text,
110             /**@type{string}*/
111             leftChar,
112             /**@type{string}*/
113             rightChar,
114             leftNode,
115             rightNode,
116             r;
117 
118         if (nodeType !== ELEMENT_NODE && nodeType !== TEXT_NODE) {
119             return FILTER_REJECT;
120         }
121         if (nodeType === TEXT_NODE) {
122             if (!odfUtils.isGroupingElement(container.parentNode)
123                     || odfUtils.isWithinTrackedChanges(
124                         container.parentNode,
125                         getRootNode()
126                     )) {
127                 return FILTER_REJECT;
128             }
129             // In a PositionIterator, the offset in a text node is never
130             // equal to the length of the text node.
131             offset = iterator.unfilteredDomOffset();
132             text = container.data;
133             runtime.assert(offset !== text.length, "Unexpected offset.");
134             if (offset > 0) {
135                 // The cursor may be placed to the right of a non-whitespace
136                 // character.
137                 leftChar = /**@type{string}*/(text[offset - 1]);
138                 if (!odfUtils.isODFWhitespace(leftChar)) {
139                     return FILTER_ACCEPT;
140                 }
141                 // A whitespace to the left is ok, if
142                 // * there is a non-whitespace character to the right and
143                 //   that is the first non-whitespace character or character
144                 //   element or
145                 // * there is not another whitespace character in front of
146                 //   it.
147                 if (offset > 1) {
148                     leftChar = /**@type{string}*/(text[offset - 2]);
149                     if (!odfUtils.isODFWhitespace(leftChar)) {
150                         r = FILTER_ACCEPT;
151                     } else if (!odfUtils.isODFWhitespace(text.substr(0, offset))) {
152                         // check if this can be leading paragraph space
153                         return FILTER_REJECT;
154                     }
155                 } else {
156                     // check if there is a non-whitespace character or
157                     // character element (other than text:s) in a preceding node
158                     leftNode = odfUtils.previousNode(container);
159                     if (odfUtils.scanLeftForNonSpace(leftNode)) {
160                         r = FILTER_ACCEPT;
161                     }
162                 }
163                 if (r === FILTER_ACCEPT) {
164                     return odfUtils.isTrailingWhitespace(
165                             /**@type{!Text}*/(container), offset)
166                         ? FILTER_REJECT : FILTER_ACCEPT;
167                 }
168                 rightChar = /**@type{string}*/(text[offset]);
169                 if (odfUtils.isODFWhitespace(rightChar)) {
170                     return FILTER_REJECT;
171                 }
172                 return odfUtils.scanLeftForAnyCharacter(odfUtils.previousNode(container))
173                     ? FILTER_REJECT : FILTER_ACCEPT;
174             }
175             leftNode = iterator.leftNode();
176             rightNode = container;
177             container = /**@type{!Node}*/(container.parentNode);
178             r = checkLeftRight(container, leftNode, rightNode);
179         } else if (!odfUtils.isGroupingElement(container)
180                 || odfUtils.isWithinTrackedChanges(container, getRootNode())) {
181             r = FILTER_REJECT;
182         } else {
183             leftNode = iterator.leftNode();
184             rightNode = iterator.rightNode();
185             r = checkLeftRight(container, leftNode, rightNode);
186         }
187         return r;
188     };
189 };
190