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