使用正则表达式在不在锚点中的页面上查找 phone 数字

Use Regex to find a phone number on a page not in an anchor

我有这个正则表达式来搜索 phone 数字模式:

[(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4}

此格式匹配 phone 个数字:

123 456 7890
(123)456 7890
(123) 456 7890
(123)456-7890
(123) 456-7890
123.456.7890
123-456-7890

我想扫描整个页面(JavaScript)寻找这个匹配项,但不包括锚点中已经存在的这个匹配项。 找到匹配后,我想将 phone 号码转换为移动设备的点击呼叫 link:

(123) 456-7890 --> <a href="tel:1234567890">(123) 456-7890</a>

我很确定我需要进行否定查找。我已经试过了,但这似乎不是正确的想法:

(?!.*(\<a href.*?\>))[(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4}

使用这个作为你的正则表达式:

(<a href.*?>.*?([(]?(\d{3})[)]?[(\s)?.-](\d{3})[\s.-](\d{4})).*?<\/a>)|([(]?(\d{3})[)]?[(\s)?.-](\d{3})[\s.-](\d{4}))

将其用作替换字符串:

<a href="tel:">() -</a>

这会找到所有 phone 数字,包括 href 标签的外部和内部,但是,在所有情况下,它 returns phone 数字本身作为特定的正则表达式组。因此,您可以将在新 href 标签中找到的每个 phone 数字括起来,因为在它们存在的地方,您将替换原始的 href 标签。

正则表达式组或 "capture group" 捕获与整个正则表达式匹配的特定部分。它们是通过将正则表达式的一部分括在括号中创建的。这些组按照左括号的顺序从左到右编号,可以通过在 Javascript 中的数字前面放置 $ 来引用它们匹配的输入部分。其他实现使用 \ 来达到这个目的。这称为反向引用。反向引用可以稍后出现在您的正则表达式或替换字符串中(如本答案前面所做的那样)。更多信息:http://www.regular-expressions.info/backref.html

举一个更简单的例子,假设您有一个包含帐号和其他信息的文档。每个帐号都以单词 "account" 开头,您想将其更改为 "acct",但 "account" 出现在文档的其他位置,因此您不能简单地单独对其进行查找和替换。您可以使用 account ([0-9]+) 的正则表达式。在此正则表达式中,([0-9]+) 形成一个匹配实际帐号的组,我们可以在替换字符串中将其反向引用为 </code>,变为 <code>acct .

您可以在这里进行测试:http://regexr.com/

Don't use regular expressions to parse HTML。使用 HTML/DOM 解析器获取文本节点(浏览器可以为您过滤它,删除锚标记和所有文本太短而无法包含 phone 数字),您可以直接检查文本.

例如,使用 XPath(有点难看,但支持以大多数其他 DOM 方法不支持的方式直接处理文本节点):

// This query finds all text nodes with at least 12 non-whitespace characters
// who are not direct children of an anchor tag
// Letting XPath apply basic filters dramatically reduces the number of elements
// you need to process (there are tons of short and/or pure whitespace text nodes
// in most DOMs)
var xpr = document.evaluate('descendant-or-self::text()[not(parent::A) and string-length(normalize-space(self::text())) >= 12]',
                            document.body, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i=0, len=xpr.snapshotLength; i < len; ++i) {
    var txt = xpr.snapshotItem(i);
    // Splits with grouping to preserve the text split on
    var numbers = txt.data.split(/([(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4})/);
    // split will return at least three items on a hit, prefix, split match, and suffix
    if (numbers.length >= 3) {
        var parent = txt.parentNode; // Save parent before replacing child
        // Insert new elements before existing element; first element is just
        // text before first phone number
        parent.insertBefore(document.createTextNode(numbers[0]), txt);

        // Now explicitly create pairs of anchors and following text nodes
        for (var j = 1; j < numbers.length; j += 2) {
            // Operate in pairs; odd index is phone number, even is
            // text following that phone number
            var anc = document.createElement('a');
            anc.href = 'tel:' + numbers[j].replace(/\D+/g, '');
            anc.textContent = numbers[j];
            parent.insertBefore(anc, txt);
            parent.insertBefore(document.createTextNode(numbers[j+1]), txt);
        }
        // Remove original text node now that we've inserted all the
        // replacement elements and don't need it for positioning anymore
        parent.removeChild(txt);

        parent.normalize(); // Normalize whitespace after rebuilding
    }
}

郑重声明,基本过滤器在大多数页面上都有帮助很多。例如,在这个页面上,现在,正如我所看到的(将因用户、浏览器、浏览器扩展和脚本等而异)没有过滤器,查询 'descendant-or-self::text()' 的快照将有 1794 个项目。省略由锚标记作为父项的文本,'descendant-or-self::text()[not(parent::A)]' 将其减少到 1538,而完整查询,验证非空白内容至少为 12 个字符长,将其减少到 87 个项目。将正则表达式应用于 87 个项目是性能方面的小改动,并且您已经消除了使用不合适的工具解析 HTML 的需要。