【问题标题】:DOM TreeWalker to return all text nodesDOM TreeWalker 返回所有文本节点
【发布时间】:2015-11-04 13:48:24
【问题描述】:

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

TreeWalker 似乎是这项工作的 API,但我发现它非常不直观。在我看来,specMDN reference(通常非常擅长解释神秘的 DOM API)都不是不言而喻的。

我的第一个假设是,我只需要传递正确的过滤器作为第二个参数 - 类似于 document.createTreeWalker( element, NodeFilter.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 & other enquiries.

所以我认为这是使用TreeWalker 的第三个过滤器参数以及NodeFilter 上的额外属性的机会。如果过滤器方法的有效返回值是FILTER_ACCEPTFILTER_REJECTFILTER_SKIP,那么我推断通过在第二个参数中接受元素节点以及文本节点,我可以指定应该接受文本节点,其余的跳过。但这似乎给出了相同的结果 - 在锚点内或锚点之后没有拾取文本节点:

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 & other enquiries.

到目前为止,我确信使用 DOM1 方法递归遍历树会更容易,就像在这个 sn-p 中一样:

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 & other enquiries.

我错过了什么?

【问题讨论】:

  • 我猜node.parentNode.removeChild( node ) 是问题所在 - 您正在从 DOM 中删除当前节点,因此 walker 将在分离状态下找不到 .nextNode()
  • @Bergi 这听起来像是我正在寻找的答案!

标签: javascript dom


【解决方案1】:

我错过了什么?

node.parentNode.removeChild(node) 是问题所在 - 您正在从 DOM 中删除当前节点,因此 walker 将不会从那里找到 .nextNode()

您应该在移除节点之前推进 walker,或者不移除它而是缩小其内容(当您移除所有单词时剩余的内容)。

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 & other enquiries.

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

【讨论】:

  • 太棒了,谢谢 - 使用 match 的迭代特性是一种非常好的方法。
  • @Barney:在这种情况下,我实际上使用text = text.slice(…) 进行迭代。但是,您可以在设置全局标志的情况下完成 var re = /^(\s*)(\S+)/g; while (m = text.match(re)) …
  • 啊,我明白了。低级 JS API 副作用的诡计总是让我感到困惑。理论上更喜欢匹配副作用,因为迭代机制处于循环条件中,但匹配行为可以说更加模糊。
猜你喜欢
  • 2016-09-07
  • 2011-09-15
  • 1970-01-01
  • 2013-03-27
  • 2019-03-04
  • 2014-01-03
  • 1970-01-01
  • 1970-01-01
  • 2016-05-07
相关资源
最近更新 更多