我正在尝试使用Javascript来修改现有的HTML文档,这样我就可以使用span
标记来包围网页中的每个文字。这是一个非常具体的问题,所以我将提供一个示例案例:
<body><p>hello, <br>
change this</p>
<img src="lorempixel.com/200/200> <br></body></html>
这应该改为:
<body><p><span id="1">hello,</span>
<br> <span id="2"> change</span><span id="3"> this</span> </p>
<br> <img src="lorempixel.com/200/200> <br></body></html>
我正在思考或regex
解决方案,但它们变得非常复杂,我不确定如何忽略标记并更改文本而不会完全破坏页面。
任何想法都赞赏!
答案 0 :(得分:7)
不要在原始HTML上使用正则表达式。仅在文本上使用它。这是因为正则表达式是一个无上下文解析器,但HTML是一种递归语言。您需要一个递归下降解析器来正确处理HTML。
首先是DOM的一些有用功能:
document.body
是DOM的根childNodes
数组(甚至是注释,文本和属性)<span>
或<h>
等元素节点不包含文本,而是包含包含文本的文本节点。nodeType
属性,文本节点类型为3
。nodeValue
属性,根据节点的类型,它具有不同的含义。对于文本节点nodeValue
包含实际文本。因此,使用上面的信息,我们可以用跨度包围所有单词。
首先是一个简单的实用函数,它允许我们处理DOM:
// First a simple implementation of recursive descent,
// visit all nodes in the DOM and process it with a callback:
function walkDOM (node,callback) {
if (node.nodeName != 'SCRIPT') { // ignore javascript
callback(node);
for (var i=0; i<node.childNodes.length; i++) {
walkDOM(node.childNodes[i],callback);
}
}
}
现在我们可以遍历DOM并找到文本节点:
var textNodes = [];
walkDOM(document.body,function(n){
if (n.nodeType == 3) {
textNodes.push(n);
}
});
请注意,我这两步都是为了避免包含两次单词。
现在我们可以处理文本节点:
// simple utility functions to avoid a lot of typing:
function insertBefore (new_element, element) {
element.parentNode.insertBefore(new_element,element);
}
function removeElement (element) {
element.parentNode.removeChild(element);
}
function makeSpan (txt, attrs) {
var s = document.createElement('span');
for (var i in attrs) {
if (attrs.hasOwnProperty(i)) s[i] = attrs[i];
}
s.appendChild(makeText(txt));
return s;
}
function makeText (txt) {return document.createTextNode(txt)}
var id_count = 1;
for (var i=0; i<textNodes.length; i++) {
var n = textNodes[i];
var txt = n.nodeValue;
var words = txt.split(' ');
// Insert span surrounded words:
insertBefore(makeSpan(words[0],{id:id_count++}),n);
for (var j=1; j<words.length; j++) {
insertBefore(makeText(' '),n); // join the words with spaces
insertBefore(makeSpan(words[j],{id:id_count++}),n);
}
// Now remove the original text node:
removeElement(n);
}
你有它。它很麻烦但是100%安全 - 它永远不会破坏你网页中其他javascript标签。我上面的很多实用功能都可以用你选择的库替换。但是,不要采取将整个文档视为巨型innerHTML
字符串的捷径。除非你愿意用纯JavaScript编写HTML解析器。
答案 1 :(得分:1)
这种处理总是比你想象的要复杂得多。以下将包装与\S+
匹配的字符序列(非空白序列),而不包装与\s+
(空格)匹配的序列。
它还允许跳过某些元素的内容,例如脚本,输入,按钮,选择等。请注意, childNodes 返回的实时集合必须转换为静态数组,否则会受到添加的新节点的影响。另一种方法是使用element.querySelectorAll()
,但 childNodes 有更广泛的支持。
// Copy numeric properties of Obj from 0 to length
// to an array
function toArray(obj) {
var arr = [];
for (var i=0, iLen=obj.length; i<iLen; i++) {
arr.push(obj[i]);
}
return arr;
}
// Wrap the words of an element and child elements in a span
// Recurs over child elements, add an ID and class to the wrapping span
// Does not affect elements with no content, or those to be excluded
var wrapContent = (function() {
var count = 0;
return function(el) {
// If element provided, start there, otherwise use the body
el = el && el.parentNode? el : document.body;
// Get all child nodes as a static array
var node, nodes = toArray(el.childNodes);
var frag, parent, text;
var re = /\S+/;
var sp, span = document.createElement('span');
// Tag names of elements to skip, there are more to add
var skip = {'script':'', 'button':'', 'input':'', 'select':'',
'textarea':'', 'option':''};
// For each child node...
for (var i=0, iLen=nodes.length; i<iLen; i++) {
node = nodes[i];
// If it's an element, call wrapContent
if (node.nodeType == 1 && !(node.tagName.toLowerCase() in skip)) {
wrapContent(node);
// If it's a text node, wrap words
} else if (node.nodeType == 3) {
// Match sequences of whitespace and non-whitespace
text = node.data.match(/\s+|\S+/g);
if (text) {
// Create a fragment, handy suckers these
frag = document.createDocumentFragment();
for (var j=0, jLen=text.length; j<jLen; j++) {
// If not whitespace, wrap it and append to the fragment
if (re.test(text[j])) {
sp = span.cloneNode(false);
sp.id = count++;
sp.className = 'foo';
sp.appendChild(document.createTextNode(text[j]));
frag.appendChild(sp);
// Otherwise, just append it to the fragment
} else {
frag.appendChild(document.createTextNode(text[j]));
}
}
}
// Replace the original node with the fragment
node.parentNode.replaceChild(frag, node);
}
}
}
}());
window.onload = wrapContent;
以上仅针对最常见的情况,需要更多的工作和彻底的测试。