Insert element - splitting others if necessary
给定父元素、文本偏移量和长度。我希望能够将一个元素环绕在以偏移量开始的文本周围,并转到偏移量+长度。如果此文本跨越我们的out-of-child元素,我希望在元素跨越时拆分它们…如果除了跨度或父级空间不足之外还有其他内容,则取消(未做任何更改)。
例如,给定:
1 | Aa bb <span class='child'>cc dd</span> ee ff |
如果偏移量和长度是4和5(这意味着"bb cc"),我想以以下方式结束:
1 | Aa <span class='new'>bb <span class='child'>cc</span></span><span class='child'> dd</span> ee ff |
号
注意,
类似于"dd ee",或者如果有更多(更复杂)子跨度的嵌套,则有各种选择。
我在尝试如何解决这个问题时遇到了一些困难,唯一的分裂就是头痛。
我认为一个好的函数签名应该类似于
我设法把一些东西放在一起…比我原来想的要多一些。
我创建了以下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | /** * Find the text node and the index within that given a parent node and the index within that. * * @param parentNode * @param index * @returns {*} - object with 'target' property set to the text node at the index parameter within the * parentNode parameter and 'index' property set to the index of that point within 'target' */ findStartPoint = function(parentNode, index) { var nodeRight = 0; var nodeLeft = 0; var node = null; for(var i = 0; i < parentNode.childNodes.length; i++){ node = parentNode.childNodes.item(i); if(node.nodeType !== 7 && node.nodeType !== 8){ //not processing instruction or comment if(nodeRight <= index){ nodeLeft = nodeRight; nodeRight += node.text.length; if(nodeRight > index){ if (node.nodeType === 3) { return { target: node, index: index-nodeLeft }; } else { return this.findStartPoint( node, index-nodeLeft ); } } } } } return { target: null, index: null }; }; /** * * Inserts an element within a givin range, will split tags if necessary * * xx <bold>xx foo <italic> bar</italic></bold> baz xx * * If I selected 'foo bar baz' in the above: * - startPoint would be { target: the text node containing 'xx foo ', index: 4 } * - length would be 'foo bar baz'.length * - splittableEles could be ['BOLD', 'ITALIC'] * - insert ele could be <hello> * * Output would be: * xx <bold>xx </bold><hello><bold>foo <italic> bar</italic></bold> baz</hello> xx * * @param startPoint - an object containing target (text node at beginning of split) and index (index of beginning within this text node) * @param length - length of selection in characters * @param splittableEles - elements that we allow to be split * @param insertEle - element that we will wrap the split within and insert * @returns {*} */ splitInsert = function(startPoint, length, splittableEles, insertEle) { var target = startPoint.target; var index = startPoint.index; if (index == 0 && $(target.parentNode).text().length <= length) { //consume entire target parent target.parentNode.parentNode.insertBefore(insertEle, target.parentNode); insertEle.appendChild(target.parentNode); } else { //split and add right of index to insertEle var content = target.splitText(index); content.parentNode.insertBefore(insertEle, content); if (content.length > length) { //split off the end if content longer than selection content.splitText(length); } insertEle.appendChild(content); } while ( insertEle.text.length < length ) { if (insertEle.nextSibling) { if ( !this.consumeElementForInsert(insertEle, insertEle.nextSibling, length) ) { if ( insertEle.nextSibling.nodeType === 3 ) { this.splitTextForInsert(insertEle, insertEle.nextSibling, length) } else { this.splitElementForInsert(insertEle, insertEle.nextSibling, length, splittableEles) } } } else { //no next sibling... need to split parent. this would make parents next sibling for next iteration var parent = insertEle.parentNode; if (-1 == $.inArray(parent.nodeName.toUpperCase(), splittableEles)) { //selection would require splitting non-splittable element return { success: false }; } //wrap insertEle with empty clone of parent, then place after parent var clone = parent.cloneNode(false); while (insertEle.firstChild) { clone.appendChild(insertEle.firstChild); } insertEle.appendChild(clone); parent.parentNode.insertBefore(insertEle, parent.nextSibling); } } return { success: true, newElement: insertEle }; }; /** * Splits a textnode ('node'), text on the left will be appended to 'container' to make 'container' have * as many 'characters' as specified * * @param container * @param node * @param characters */ splitTextForInsert = function (container, node, characters) { var containerLength = $(container).text().length; if ( node.nodeValue.length + containerLength > characters ) { node.splitText(characters - containerLength); } container.appendChild(node); }; /** * Puts 'node' into 'container' as long as it can fit given that 'container' can only have so many 'characters' * * @param container * @param node * @param characters * * @returns {boolean} - true if can consume, false if can't. can't consume if element has more text than needed. */ consumeElementForInsert = function (container, node, characters) { if ( characters - $(container).text().length > $(node).text().length ) { container.appendChild(node); return true; } return false; } /** * Splits 'node' (recursively if necessary) the amount of 'characters' specified, adds left side into 'container' * * @param container - parent/container of node we are splitting * @param node - node we are splitting * @param characters - number of characters in markman selection * @param splittableEles - array of nodeTypes that can be split, upper case * @param originalContainer - original container (before recursive calls) * @returns {boolean} - true if we successfully split element or there is nothing to split, false otherwise. false will happen if we try to split * something not in splittableEles or if we run out of characters */ splitElementForInsert = function (container, node, characters, splittableEles, originalContainer) { originalContainer = originalContainer || container; if (-1 == $.inArray(node.nodeName.toUpperCase(), splittableEles)) { return false; } node.normalize(); var child = node.firstChild; if (!child) { return true; } else if (child.nodeType === 3) { var $container = $(originalContainer); if (characters - $container.text().length - child.nodeValue.length < 1 ) { //this portion is enough for the selected range var clone = node.cloneNode(false); child.splitText(characters - $container.text().length); clone.appendChild(child); container.appendChild(clone); return true; } else { //throw this text in the container and go on to the next as we still need more if (child.nextSibling) { var next = child.nextSibling; container.appendChild(child); return this.splitElementForInsert( container, next, characters, splittableEles, originalContainer ); } else { return true; } } } else if (child.nodeType === 1) { //child is an element, split that element var clone = node.cloneNode(false); container.appendChild(clone); return this.splitElementForInsert(clone, child, characters, splittableEles, originalContainer); } }; |
我可以这样打电话…
1 2 3 4 5 6 7 8 9 10 11 12 | var left = this.selectionInfo.left - paraIdOffset; var right = this.selectionInfo.right - paraIdOffset; var parentNode = this.selectionInfo.parentXmlNode; var someElement = this.xml.createElement(...); var splittableEles = ['SPAN']; var createHappened = false; var startPoint = findStartPoint(parentNode, left); var insert = splitInsert(startPoint, right-left, splittableEles, someElement ); if (insert.success) { createHappened = true; } |
号
看起来您希望在一个范围内的给定位置换行一段字符。
下面是一个实现这一点的伪代码过程:
下面是这个方法的代码示例的答案。它通过regex匹配来选择范围,因此您需要将其更改为按索引和长度,但这应该足以让您继续前进。
如何使用javascript在节点中包装部分文本