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  * @implements {core.PositionFilter}
 30  */
 31 ops.TextPositionFilter = function TextPositionFilter() {
 32     "use strict";
 33     var odfUtils = odf.OdfUtils,
 34         ELEMENT_NODE = Node.ELEMENT_NODE,
 35         TEXT_NODE = Node.TEXT_NODE,
 36         /**@const*/FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT,
 37         /**@const*/FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
 38 
 39     /**
 40      * Find the previous sibling of the specified node that passes the node filter.
 41      * @param {?Node} node
 42      * @param {!function(?Node):!number} nodeFilter
 43      * @return {?Node}
 44      */
 45     function previousSibling(node, nodeFilter) {
 46         while (node && nodeFilter(node) !== FILTER_ACCEPT) {
 47             node = node.previousSibling;
 48         }
 49         return node;
 50     }
 51 
 52     /**
 53      * @param {!Node} container
 54      * @param {?Node} leftNode
 55      * @param {?Node} rightNode
 56      * @param {!function(?Node):!number} nodeFilter
 57      * @return {!core.PositionFilter.FilterResult}
 58      */
 59     function checkLeftRight(container, leftNode, rightNode, nodeFilter) {
 60         var r, firstPos, rightOfChar;
 61         // accept if there is a character immediately to the left
 62         if (leftNode) {
 63             if (odfUtils.isInlineRoot(leftNode) && odfUtils.isGroupingElement(rightNode)) {
 64                 // Move first position after inline root inside trailing grouping element (part 1)
 65                 // Disallow positions to the right of an inline root (like an annotation) and
 66                 // to the left of a grouping element (like an annotation highlight span)
 67                 return FILTER_REJECT;
 68             }
 69             r = odfUtils.lookLeftForCharacter(leftNode);
 70             if (r === 1) {// non-whitespace character or a character element
 71                 return FILTER_ACCEPT;
 72             }
 73             if (r === 2 && (odfUtils.scanRightForAnyCharacter(rightNode)
 74                 || odfUtils.scanRightForAnyCharacter(odfUtils.nextNode(container)))) {
 75                 // significant whitespace is ok, if not in trailing whitesp
 76                 return FILTER_ACCEPT;
 77             }
 78         } else {
 79             if (odfUtils.isGroupingElement(container) && odfUtils.isInlineRoot(previousSibling(container.previousSibling, nodeFilter))) {
 80                 // Move first position after inline root inside trailing grouping element (part 2)
 81                 // Allow the first position inside the first grouping element trailing an annotation
 82                 return FILTER_ACCEPT;
 83             }
 84         }
 85         // at this point, we know that the position is not directly to the
 86         // right of a significant character or element. so the position is
 87         // only acceptable if it is the first in an empty p or h or if it
 88         // is to the left of the first significant character or element.
 89 
 90         // accept if this is the first position in p or h and there is no
 91         // character in the p or h
 92         firstPos = leftNode === null && odfUtils.isParagraph(container);
 93         rightOfChar = odfUtils.lookRightForCharacter(rightNode);
 94         if (firstPos) {
 95             if (rightOfChar) {
 96                 return FILTER_ACCEPT;
 97             }
 98             // position is first position in empty paragraph
 99             return odfUtils.scanRightForAnyCharacter(rightNode) ? FILTER_REJECT : FILTER_ACCEPT;
100         }
101         // if not directly to the right of a character, reject
102         if (!rightOfChar) {
103             return FILTER_REJECT;
104         }
105         // accept if there is no character to the left
106         leftNode = leftNode || odfUtils.previousNode(container);
107         return odfUtils.scanLeftForAnyCharacter(leftNode) ? FILTER_REJECT : FILTER_ACCEPT;
108     }
109 
110     /**
111      * @param {!core.PositionIterator} iterator
112      * @return {!core.PositionFilter.FilterResult}
113      */
114     this.acceptPosition = function (iterator) {
115         var container = iterator.container(),
116             nodeType = container.nodeType,
117             /**@type{number}*/
118             offset,
119             /**@type{string}*/
120             text,
121             /**@type{string}*/
122             leftChar,
123             /**@type{string}*/
124             rightChar,
125             leftNode,
126             rightNode,
127             r;
128 
129         if (nodeType !== ELEMENT_NODE && nodeType !== TEXT_NODE) {
130             return FILTER_REJECT;
131         }
132         if (nodeType === TEXT_NODE) {
133             // In a PositionIterator, the offset in a text node is never
134             // equal to the length of the text node.
135             offset = iterator.unfilteredDomOffset();
136             text = container.data;
137             runtime.assert(offset !== text.length, "Unexpected offset.");
138             if (offset > 0) {
139                 // The cursor may be placed to the right of a non-whitespace
140                 // character.
141                 leftChar = /**@type{string}*/(text[offset - 1]);
142                 if (!odfUtils.isODFWhitespace(leftChar)) {
143                     return FILTER_ACCEPT;
144                 }
145                 // A whitespace to the left is ok, if
146                 // * there is a non-whitespace character to the right and
147                 //   that is the first non-whitespace character or character
148                 //   element or
149                 // * there is not another whitespace character in front of
150                 //   it.
151                 if (offset > 1) {
152                     leftChar = /**@type{string}*/(text[offset - 2]);
153                     if (!odfUtils.isODFWhitespace(leftChar)) {
154                         r = FILTER_ACCEPT;
155                     } else if (!odfUtils.isODFWhitespace(text.substr(0, offset))) {
156                         // check if this can be leading paragraph space
157                         return FILTER_REJECT;
158                     }
159                 } else {
160                     // check if there is a non-whitespace character or
161                     // character element (other than text:s) in a preceding node
162                     leftNode = odfUtils.previousNode(container);
163                     if (odfUtils.scanLeftForNonSpace(leftNode)) {
164                         r = FILTER_ACCEPT;
165                     }
166                 }
167                 if (r === FILTER_ACCEPT) {
168                     return odfUtils.isTrailingWhitespace(
169                             /**@type{!Text}*/(container), offset)
170                         ? FILTER_REJECT : FILTER_ACCEPT;
171                 }
172                 rightChar = /**@type{string}*/(text[offset]);
173                 if (odfUtils.isODFWhitespace(rightChar)) {
174                     return FILTER_REJECT;
175                 }
176                 return odfUtils.scanLeftForAnyCharacter(odfUtils.previousNode(container))
177                     ? FILTER_REJECT : FILTER_ACCEPT;
178             }
179             leftNode = iterator.leftNode();
180             rightNode = container;
181             container = /**@type{!Node}*/(container.parentNode);
182             r = checkLeftRight(container, leftNode, rightNode, iterator.getNodeFilter());
183         } else if (!odfUtils.isGroupingElement(container)) {
184             r = FILTER_REJECT;
185         } else {
186             leftNode = iterator.leftNode();
187             rightNode = iterator.rightNode();
188             r = checkLeftRight(container, leftNode, rightNode, iterator.getNodeFilter());
189         }
190         return r;
191     };
192 };
193