【问题标题】:Extract text from HTML while preserving block-level element newlines从 HTML 中提取文本,同时保留块级元素换行符
【发布时间】:2013-12-20 08:31:19
【问题描述】:

背景

大多数questions关于从HTML中提取文本(即stripping标签)使用:

jQuery( htmlString ).text();

虽然这会抽象出浏览器的不一致(例如 innerTexttextContent),但函数调用也会忽略块级元素的语义含义(例如 li)。

问题

在各种浏览器中保留块级元素的换行符(即语义意图)需要不小的努力,如 Mike Wilcox describes

一个看似简单的解决方案是模拟将 HTML 内容粘贴到 <textarea> 中,这会在保留块级元素换行符的同时剥离 HTML。但是,当用户将内容粘贴到 <textarea> 时,基于 JavaScript 的插入不会触发浏览器使用的相同的 HTML 到文本例程。

我还尝试集成 Mike Wilcox 的 JavaScript code。该代码适用于 Chromium,但不适用于 Firefox。

问题

使用 jQuery(或 vanilla JavaScript)从 HTML 中提取文本同时保留 block-level elements 的语义换行符的最简单的跨浏览器方法是什么?

示例

考虑:

  1. 选择并复制整个问题。
  2. 打开textarea example page
  3. 将内容粘贴到文本区域。

文本区域为有序列表、标题、预格式化文本等保留换行符。这就是我想要达到的结果。

进一步澄清,给定任何 HTML 内容,例如:

   <h1>Header</h1>
   <p>Paragraph</p>
   <ul>
     <li>First</li>
     <li>Second</li>
   </ul>
   <dl>
     <dt>Term</dt>
       <dd>Definition</dd>
   </dl>
   <div>Div with <span>span</span>.<br />After the <a href="...">break</a>.</div>

你将如何生产:

标题 段落 第一的 第二 学期 定义 带跨度的 div。 休息后。

注意:缩进和非标准化空格都不相关。

【问题讨论】:

  • 这听起来是个有趣的问题。我期望一种迭代元素和节点的方法,在它进入或离开具有块计算样式的元素的任何地方插入新行(但如果没有中间文本节点,则不会加倍)或导致新行的元素(也许BR,人力资源,TR)。处理表格是另一个问题。
  • 我得到了一些工作,但它依赖于 HTML 格式:jsfiddle.net/jLpCT。如果您真的想根据块级事件获得缩进文本,则必须检查每个元素并根据元素是否为块级做出决定(并且可能还以不同的方式对待不同的块级元素)。如您所见,我发布了一个答案但删除了它,因为我不确定这是否是您想要的方向。

标签: javascript jquery html text strip


【解决方案1】:

考虑:

/**
 * Returns the style for a node.
 *
 * @param n The node to check.
 * @param p The property to retrieve (usually 'display').
 * @link http://www.quirksmode.org/dom/getstyles.html
 */
this.getStyle = function( n, p ) {
  return n.currentStyle ?
    n.currentStyle[p] :
    document.defaultView.getComputedStyle(n, null).getPropertyValue(p);
}

/**
 * Converts HTML to text, preserving semantic newlines for block-level
 * elements.
 *
 * @param node - The HTML node to perform text extraction.
 */
this.toText = function( node ) {
  var result = '';

  if( node.nodeType == document.TEXT_NODE ) {
    // Replace repeated spaces, newlines, and tabs with a single space.
    result = node.nodeValue.replace( /\s+/g, ' ' );
  }
  else {
    for( var i = 0, j = node.childNodes.length; i < j; i++ ) {
      result += _this.toText( node.childNodes[i] );
    }

    var d = _this.getStyle( node, 'display' );

    if( d.match( /^block/ ) || d.match( /list/ ) || d.match( /row/ ) ||
        node.tagName == 'BR' || node.tagName == 'HR' ) {
      result += '\n';
    }
  }

  return result;
}

http://jsfiddle.net/3mzrV/2/

也就是说,除了一两个例外,遍历每个节点并打印其内容,让浏览器的计算样式告诉您何时插入换行符。

【讨论】:

  • getStyle 总是返回 '' 所以我假设这意味着“node”必须在 DOM 中处于活动状态才能工作?
  • 改为支持TEXT1&lt;div&gt;TEXT2&lt;/div&gt;=>TEXT1\nTEXT2stackoverflow.com/a/29706729/3338098
  • 小提琴使用了答案中显示的代码的简化版本。我最终使用了小提琴版本,通过.replace(/\n{3,}/g,'\n\n') 运行结果删除了多余的换行符。
  • 创建了这个 fork (jsfiddle.net/14rpmyn3/2) 以使用 &lt;p&gt; 包装的块而不是 \n,这更适合插入到 DOM 中。
【解决方案2】:

这似乎(几乎)在做你想做的事:

function getText($node) {
    return $node.contents().map(function () {
        if (this.nodeName === 'BR') {
            return '\n';
        } else if (this.nodeType === 3) {
            return this.nodeValue;
        } else {
            return getText($(this));
        }
    }).get().join('');
}

DEMO

它只是递归地连接所有文本节点的值,并用换行符替换&lt;br&gt; 元素。

但这没有语义,它完全依赖于原始的 HTML 格式(前导和尾随空格似乎来自 jsFiddle 如何嵌入 HTML,但您可以轻松地修剪它们)。例如,注意它是如何缩进定义术语的。

如果您真的想在语义级别上执行此操作,您需要一个块级元素列表,递归迭代元素并相应地缩进它们。对于不同的块元素,您对它们周围的缩进和换行符进行不同的处理。这应该不会太难。

【讨论】:

    【解决方案3】:

    基于https://stackoverflow.com/a/20384452/3338098 并已修复以支持 TEXT1&lt;div&gt;TEXT2&lt;/div&gt;=>TEXT1\nTEXT2 并允许非 DOM 节点

    /**
     * Returns the style for a node.
     *
     * @param n The node to check.
     * @param p The property to retrieve (usually 'display').
     * @link http://www.quirksmode.org/dom/getstyles.html
     */
    function getNodeStyle( n, p ) {
      return n.currentStyle ?
        n.currentStyle[p] :
        document.defaultView.getComputedStyle(n, null).getPropertyValue(p);
    }
    
    //IF THE NODE IS NOT ACTUALLY IN THE DOM then this won't take into account <div style="display: inline;">text</div>
    //however for simple things like `contenteditable` this is sufficient, however for arbitrary html this will not work
    function isNodeBlock(node) {
      if (node.nodeType == document.TEXT_NODE) {return false;}
      var d = getNodeStyle( node, 'display' );//this is irrelevant if the node isn't currently in the current DOM.
      if (d.match( /^block/ ) || d.match( /list/ ) || d.match( /row/ ) ||
          node.tagName == 'BR' || node.tagName == 'HR' ||
          node.tagName == 'DIV' // div,p,... add as needed to support non-DOM nodes
         ) {
        return true;
      }
      return false;
    }
    
    /**
     * Converts HTML to text, preserving semantic newlines for block-level
     * elements.
     *
     * @param node - The HTML node to perform text extraction.
     */
    function htmlToText( htmlOrNode, isNode ) {
      var node = htmlOrNode;
      if (!isNode) {node = jQuery("<span>"+htmlOrNode+"</span>")[0];}
      //TODO: inject "unsafe" HTML into current DOM while guaranteeing that it won't
      //      change the visible DOM so that `isNodeBlock` will work reliably
      var result = '';
      if( node.nodeType == document.TEXT_NODE ) {
        // Replace repeated spaces, newlines, and tabs with a single space.
        result = node.nodeValue.replace( /\s+/g, ' ' );
      } else {
        for( var i = 0, j = node.childNodes.length; i < j; i++ ) {
          result += htmlToText( node.childNodes[i], true );
          if (i < j-1) {
            if (isNodeBlock(node.childNodes[i])) {
              result += '\n';
            } else if (isNodeBlock(node.childNodes[i+1]) &&
                       node.childNodes[i+1].tagName != 'BR' &&
                       node.childNodes[i+1].tagName != 'HR') {
              result += '\n';
            }
          }
        }
      }
      return result;
    }
    

    主要变化是

          if (i < j-1) {
            if (isNodeBlock(node.childNodes[i])) {
              result += '\n';
            } else if (isNodeBlock(node.childNodes[i+1]) &&
                       node.childNodes[i+1].tagName != 'BR' &&
                       node.childNodes[i+1].tagName != 'HR') {
              result += '\n';
            }
          }
    

    检查相邻块以确定添加换行符的适当性。

    【讨论】:

      【解决方案4】:

      我想建议对 svidgen 的代码进行一点修改:

      function getText(n, isInnerNode) {
        var rv = '';
        if (n.nodeType == 3) {
            rv = n.nodeValue;
        } else {
            var partial = "";
            var d = getComputedStyle(n).getPropertyValue('display');
            if (isInnerNode && d.match(/^block/) || d.match(/list/) || n.tagName == 'BR') {
                partial += "\n";
            }
      
            for (var i = 0; i < n.childNodes.length; i++) {
                partial += getText(n.childNodes[i], true);
            }
            rv = partial;
        }
        return rv;
       };

      我只是在for循环之前添加了换行符,这样我们在块之前有一个换行符,还有一个变量来避免根元素的换行符。

      应该调用的代码:

      getText(document.getElementById("divElement"))

      【讨论】:

        【解决方案5】:

        使用element.innerText 这不会返回从 contenteditable 元素添加的额外节点。 如果您使用element.innerHTML,文本将包含额外的标记,但 innerText 将返回您在元素内容中看到的内容。

        <div id="txt" contenteditable="true"></div>
        
        <script>
          var txt=document.getElementById("txt");
          var withMarkup=txt.innerHTML;
          var textOnly=txt.innerText;
          console.log(withMarkup);
          console.log(textOnly);
        </script>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-01-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-11
          • 2014-12-21
          • 1970-01-01
          相关资源
          最近更新 更多