Javascript Regex 替换不在 html 属性中的文本

IT技术 javascript regex
2021-01-16 01:42:01

我想要一个 Javascript 正则表达式在给定的开始 ( <span>) 和结束标记 ( ie </span>) 中包装给定的单词列表,但前提是单词实际上是页面上的“可见文本”,而不是在 html 属性内(例如链接的标题标签,或<script></script>块内。

我已经创建了一个基本设置的 JS Fiddle:http : //jsfiddle.net/4YCR6/1/

2个回答

HTML 太复杂,无法使用正则表达式进行可靠的解析。

如果您希望在客户端执行此操作,您可以创建一个文档片段和/或断开连接的 DOM 节点(它们都不会显示在任何地方)并使用您的 HTML 字符串对其进行初始化,然后遍历生成的 DOM 树并处理文本节点。(或者使用一个库来帮助你做到这一点,虽然它实际上很简单。)

这是一个 DOM 行走示例。这个例子比你的问题稍微简单,因为它只是更新文本,它不会向结构添加新元素(在spans 中包装部分文本涉及更新结构),但它应该让你开始。关于最后需要更改的内容的注释。

var html =
    "<p>This is a test.</p>" +
    "<form><input type='text' value='test value'></form>" +
    "<p class='testing test'>Testing here too</p>";
var frag = document.createDocumentFragment();
var body = document.createElement('body');
var node, next;

// Turn the HTML string into a DOM tree
body.innerHTML = html;

// Walk the dom looking for the given text in text nodes
walk(body);

// Insert the result into the current document via a fragment
node = body.firstChild;
while (node) {
  next = node.nextSibling;
  frag.appendChild(node);
  node = next;
}
document.body.appendChild(frag);

// Our walker function
function walk(node) {
  var child, next;

  switch (node.nodeType) {
    case 1:  // Element
    case 9:  // Document
    case 11: // Document fragment
      child = node.firstChild;
      while (child) {
        next = child.nextSibling;
        walk(child);
        child = next;
      }
      break;
    case 3: // Text node
      handleText(node);
      break;
  }
}

function handleText(textNode) {
  textNode.nodeValue = textNode.nodeValue.replace(/test/gi, "TEST");
}

活生生的例子

您需要进行的更改将在handleText. 具体来说,nodeValue您需要:

  • 查找nodeValue字符串中每个单词开头的索引
  • 使用Node#splitText该文本节点分成最多三个文本节点(您匹配的文本之前的部分,该部分你匹配的文本,并按照您的匹配文本的部分)。
  • 使用document.createElement以创建新的span(这是真的只是span = document.createElement('span'))。
  • 用于在第三个文本节点(包含匹配文本之后的文本的节点)前面Node#insertBefore插入新的span如果您不需要创建第三个节点也没关系,因为匹配的文本位于文本节点的末尾,只需null作为refChild.
  • 使用Node#appendChild所述第二文本节点(具有匹配的文本)移入span(无需先将其从其父项中删除;appendChild为您执行此操作。)
有趣的事实:近五年后,他们在使用该代码的Drumpfinator Chrome扩展连接到上周今晚与约翰·奥利弗。好笑!
2021-03-24 01:42:01
@brace110:一位非常漂亮的年轻女士看了源代码给我发了一封电子邮件。:-)
2021-03-25 01:42:01
我自己将它用于类似的事情,当然要适当归因:)
2021-04-09 01:42:01
你也发现了?哦,等等,这是你的答案?你被咨询过还是像我一样在扩展中查看?
2021-04-11 01:42:01

TJ Crowder 的回答是正确的。我在代码方面更进一步:这是一个适用于所有主要浏览器的完整示例。我之前已经在 Stack Overflow 上发布了此代码的变体(例如,这里这里),并使其变得美观和通用,因此我(或其他任何人)不必对其进行太多更改即可重用它。

jsFiddle 示例:http : //jsfiddle.net/7Vf5J/38/

代码:

// Reusable generic function
function surroundInElement(el, regex, surrounderCreateFunc) {
    // script and style elements are left alone
    if (!/^(script|style)$/.test(el.tagName)) {
        var child = el.lastChild;
        while (child) {
            if (child.nodeType == 1) {
                surroundInElement(child, regex, surrounderCreateFunc);
            } else if (child.nodeType == 3) {
                surroundMatchingText(child, regex, surrounderCreateFunc);
            }
            child = child.previousSibling;
        }
    }
}

// Reusable generic function
function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
    var parent = textNode.parentNode;
    var result, surroundingNode, matchedTextNode, matchLength, matchedText;
    while ( textNode && (result = regex.exec(textNode.data)) ) {
        matchedTextNode = textNode.splitText(result.index);
        matchedText = result[0];
        matchLength = matchedText.length;
        textNode = (matchedTextNode.length > matchLength) ?
            matchedTextNode.splitText(matchLength) : null;
        // Ensure searching starts at the beginning of the text node
        regex.lastIndex = 0;
        surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
        parent.insertBefore(surroundingNode, matchedTextNode);
        parent.removeChild(matchedTextNode);
    }
}

// This function does the surrounding for every matched piece of text
// and can be customized  to do what you like
function createSpan(matchedTextNode) {
    var el = document.createElement("span");
    el.style.color = "red";
    el.appendChild(matchedTextNode);
    return el;
}

// The main function
function wrapWords(container, words) {
    // Replace the words one at a time to ensure "test2" gets matched
    for (var i = 0, len = words.length; i < len; ++i) {
        surroundInElement(container, new RegExp(words[i]), createSpan);
    }
}

wrapWords(document.getElementById("container"), ["test2", "test"]);
上帝啊,这太简单了,我真的应该学习正则表达式。谢谢蒂姆
2021-03-18 01:42:01
@MikeMellor:每个人都应该学习正则表达式:)
2021-03-23 01:42:01
@TimDown:感谢您的代码。但是,必须指出它有一个错误:它跳过了一些匹配项。要修复它,regex.lastIndex = 0;必须textNode = ...在surroundMatchingText 中之后添加
2021-03-29 01:42:01
@MikeMellor:更改new RegExp(words[i], "g")new RegExp(words[i], "gi").
2021-04-03 01:42:01
这正是我正在寻找的,但我怎么能让这个完全忽略案例呢?
2021-04-07 01:42:01