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