给定一个父元素,文本偏移量和长度..我希望能够在偏移量和偏移量+长度之间的文本周围包裹一个元素。如果这个文本跨越我们的子元素,我希望它们被拆分,如果元素是跨度...并且取消(没有做出更改)除了跨度之外的任何其他内容或者如果我们的父节点空间不足。
例如,给定:
<div id='parent'>Aa bb <span class='child'>cc dd</span> ee ff</div>
如果偏移和长度为4和5(这意味着“bb cc”),我最终会得到:
<div id='parent'>Aa <span class='new'>bb <span class='child'>cc</span></span><span class='child'> dd</span> ee ff</div>
请注意,.child
元素已被拆分,因此'bb'和'cc'仍位于.child
个元素内,尽管只有'bb'被添加到.new
类似于“dd ee”,或者如果存在进一步(更复杂)的子跨度嵌套,则进行各种选择。
我在试图解决如何做到这一点时遇到了一些麻烦,我遇到的唯一分裂就是头痛欲绝。
我认为一个好的函数签名就像splitInsert(parentElement, textOffset, length)
答案 0 :(得分:0)
看起来你想要在一个跨度的给定位置包装一长串字符。
这是实现此目的的伪代码过程:
以下是此方法的代码示例的答案。它通过正则表达式匹配来选择范围,因此您需要将其更改为索引和长度,但这应该足以让您前进。
答案 1 :(得分:0)
我设法把东西放在一起......比我原先想的要多一些。
我创建了以下功能:
/**
* 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);
}
};
然后我可以用这样的东西打电话......
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;
}