关于从HTML中提取文字的大多数questions(即stripping标签)都使用:
jQuery( htmlString ).text();
虽然这会抽象浏览器不一致(例如innerText
与textContent
),但函数调用也会忽略块级元素的语义含义(例如li
)。
在各种浏览器中保留块级元素的换行符(即语义意图)需要不费力气,如Mike Wilcox describes。
一个看似更简单的解决方案是模拟将HTML内容粘贴到<textarea>
中,它会删除HTML,同时保留块级元素换行符。但是,当用户将内容粘贴到<textarea>
时,基于JavaScript的插入不会触发浏览器使用的相同HTML到文本例程。
我也尝试整合Mike Wilcox的JavaScript code。该代码适用于Chromium,但不适用于Firefox。
使用jQuery(或block-level elements)保留vanilla JavaScript的语义换行符时,从HTML中提取文本的最简单的跨浏览器方法是什么?
考虑:
textarea保留有序列表,标题,预格式文本等的换行符。这是我想要实现的结果。
进一步澄清,给定任何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>
你会如何生产:
Header Paragraph First Second Term Definition Div with span. After the break.
注意:缩进和非规范化的空格都不相关。
答案 0 :(得分:7)
考虑:
/**
* 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;
}
也就是说,除了一两个例外,迭代每个节点并打印其内容,让浏览器的计算样式告诉您何时插入换行符。
答案 1 :(得分:3)
这似乎(几乎)做你想做的事:
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('');
}
它只是递归地连接所有文本节点的值,并用换行符替换<br>
元素。
但是这里没有语义,完全依赖于原始HTML格式(前导和尾随空格似乎来自于jsFiddle如何嵌入HTML,但你可以轻松修剪它们)。例如,请注意它如何缩进定义术语。
如果你真的想在语义层面上这样做,你需要一个块级元素列表,递归迭代元素并相应地缩进它们。您可以根据缩进和围绕它们的换行处理不同的块元素。这应该不会太困难。
答案 2 :(得分:2)
基于https://stackoverflow.com/a/20384452/3338098
并修复为支持TEXT1<div>TEXT2</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';
}
}
检查相邻块以确定添加换行符的适当性。
答案 3 :(得分:1)
我想建议从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"))
答案 4 :(得分:0)
使用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>