2017-02-21 58 views
0

给定父元素,文本偏移量和长度..我希望能够围绕以偏移量开始并转到偏移量+长度的文本打包元素。如果这个文本跨越了我们的子元素,如果这些元素是跨度的,并且取消了(不做任何更改),如果除了跨度之外还有其他内容,或者如果我们在父级中的空间用完,我希望它们被拆分。插入元素 - 必要时拆分其他元素

例如,给定:

<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”和“抄送”还是内.child元件虽然只是“BB”加入到.new

同样对于“dd ee”,或者如果有更多(更复杂的)子跨度嵌套,则可以进行各种选择。

我在试图包裹头部时遇到了一些麻烦,我该如何做到这一点,唯一的分裂就是分裂头痛。

我想一个好的函数签名会像splitInsert(parentElement, textOffset, length)

+2

和?你有什么尝试?请在这里看看[mcve] –

回答

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; 
} 
0

它看起来像你想在一个跨度给定位置包裹字符的长度。

下面是一个伪代码的过程来实现这一点:

  1. 找到DOM忽略任何元素标记中的文本开始和结束位置。
  2. 分割仅部分包含在范围内的任何元素。
  3. 为范围创建一个包含元素。

下面是针对此方法的代码示例的答案。它通过正则表达式匹配来选择范围,因此您需要将其更改为索引和长度,但这应该足以让您继续。

How to wrap part of a text in a node with JavaScript