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