DOM TreeWalker返回所有文本节点 [英] DOM TreeWalker to return all text nodes

查看:26
本文介绍了DOM TreeWalker返回所有文本节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试访问给定元素内的所有文本节点,以便我可以隔离单词并将它们包装成跨度.

I'm trying to get access to all text nodes within a given element, so that I can isolate words and wrap them in spans.

TreeWalker 似乎是这项工作的API,但我发现它非常不直观.规范 MDN参考(通常非常擅长解释奥秘的DOM API)是自在我眼中很明显.

TreeWalker seems to be the API for the job, but I'm finding it to be extremely unintuitive. Neither the spec nor the MDN reference (usually so good at explaining arcane DOM APIs) are self-evident to my eyes.

我的第一个假设是,我需要通过的只是正确的过滤器作为第二个参数-类似于 document.createTreeWalker(element,NodeFilter.TEXT_NODE).但这似乎会在遇到非文本节点后立即停止:

My first assumption was that all I needed to pass was the right filter as second argument - something like document.createTreeWalker( element, NodeFilter.TEXT_NODE ). But this seems to stop as soon as it encounters a non-text node:

wordWrap( document.body )

function wordWrap( element ){
  var nodes = document.createTreeWalker( element, NodeFilter.TEXT_NODE )
  var node
  var text
  var word

  while( node = nodes.nextNode() ){
    text = node.nodeValue.replace( /(^\s+|\s+$)/, '' ).split( /\s+/g )

    while( text.length ){
      word = document.createElement( 'span' )

      word.className = 'word'

      word.innerText = text.shift()

      node.parentNode.insertBefore( word, node )

      if( text.length )
        node.parentNode.insertBefore( document.createTextNode( ' ' ), node )
        }

    node.parentNode.removeChild( node )
  }
}

.word {
  background: #fee;
  padding: 0 .5em 0 0;
}

Contact us at <a href="mailto:email@example.com">email@example.com</a> for submissions &#38; other enquiries.

因此,我认为这是使用 TreeWalker 的第三个过滤器参数以及 NodeFilter 上额外属性的机会.如果过滤器方法的有效返回值为 FILTER_ACCEPT ,则 FILTER_REJECT & FILTER_SKIP ,然后我通过在第二个参数中接受元素节点和文本节点来说明,我可以指定应接受文本节点,其余的应跳过.但这似乎给出了相同的结果-在锚点之内或之后都没有拾取文本节点:

So I assume this is an opportunity to use the third filter argument of the TreeWalker, as well as the extra properties on NodeFilter. If a filter method's valid return values are FILTER_ACCEPT, FILTER_REJECT & FILTER_SKIP, then I reason that by accepting element nodes as well as text nodes in the second argument, I can specify that text nodes should be accepted and the rest skipped. But this seems to give the same results - no text nodes are picked up within or after the anchor:

wordWrap( document.body )

function wordWrap( element ){
  var nodes = document.createTreeWalker(
    element,
    NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
    { acceptNode : function( node ){
      if( node.nodeType === node.TEXT_NODE )
        return NodeFilter.FILTER_ACCEPT
      else 
        return NodeFilter.FILTER_SKIP
    } } 
  )
  var node
  var text
  var word

  while( node = nodes.nextNode() ){
    text = node.nodeValue.replace( /(^\s+|\s+$)/, '' ).split( /\s+/g )

    while( text.length ){
      word = document.createElement( 'span' )

      word.className = 'word'

      word.innerText = text.shift()

      node.parentNode.insertBefore( word, node )

      if( text.length )
        node.parentNode.insertBefore( document.createTextNode( ' ' ), node )
        }

    node.parentNode.removeChild( node )
  }
}

.word {
  background: #fee;
  padding: 0 .5em 0 0;
}

Contact us at <a href="mailto:email@example.com">email@example.com</a> for submissions &#38; other enquiries.

到这一点,我相信使用DOM1方法以递归方式遍历树将更容易,如以下代码片段所示:

By this point I'm convinced recursively iterating through the tree using DOM1 methods would be easier, like in this snippet:

wordWrap( document.body )

function wordWrap( element ){
  textNodes( element ).forEach( function( node ){
    var text = node.nodeValue.split( /\s+/g )
    var word

    while( text.length ){
      word = document.createElement( 'span' )

      word.className = 'word'

      word.innerText = text.shift()

      node.parentNode.insertBefore( word, node )

      if( text.length )
        node.parentNode.insertBefore( document.createTextNode( ' ' ), node )
        }

    node.parentNode.removeChild( node )
  } )
}

function textNodes( element ){
  var nodes = []

  Array.prototype.forEach.call( element.childNodes, function( child ){
    if( child.nodeType === child.TEXT_NODE )
      nodes = nodes.concat( child )
      else if( child.nodeType === child.ELEMENT_NODE )
        nodes = nodes.concat( textNodes( child ) )
        } )

  return nodes
}

.word {
  background: #fee;
  padding: 0 .5em 0 0;
}

Contact us at <a href="mailto:email@example.com">email@example.com</a> for submissions &#38; other enquiries.

我想念什么?

推荐答案

我想念什么?

问题是

node.parentNode.removeChild(node)-您正在从DOM中删除当前节点,因此行者将找不到 .nextNode()从那里.

node.parentNode.removeChild(node) is the problem - you're removing the current node from the DOM, so the walker will find no .nextNode() from the there.

您应该在删除节点之前推进walker,或者只是不删除它,而是缩小其内容(移出所有单词后剩下的内容).

You should either advance the walker before removing the node, or just not remove it and instead shrink its content (to what is left over when you've moved out all words).

wordWrap(document.body);

function wordWrap( element ){
  var nodes = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, null);
  var node;
  while (node = nodes.nextNode()) {
    var p = node.parentNode;
    var text = node.nodeValue;
    var m;
    while(m = text.match(/^(\s*)(\S+)/)) {
      text = text.slice(m[0].length);
      p.insertBefore(document.createTextNode(m[1]), node);
      var word = p.insertBefore(document.createElement('span'), node);
      word.appendChild(document.createTextNode(m[2]));
      word.className = 'word';
    }
    node.nodeValue = text;
  }
}

.word {
  background: #faa;
  padding: 0 .5em 0 0;
}

Contact us at <a href="mailto:email@example.com">email @ example.com</a> for submissions &#38; other enquiries.

请注意,正确的过滤器是 NodeFilter.SHOW_TEXT ,而不是 .TEXT_NODE ,并且在较旧的浏览器中,这四个参数不是可选的.

Notice that the correct filter is NodeFilter.SHOW_TEXT, not .TEXT_NODE, and that in older browser the four arguments are not optional.

这篇关于DOM TreeWalker返回所有文本节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆