Javascript 使用正则表达式在页面上查找不在锚中的电话号码

Javascript 使用正则表达式在页面上查找不在锚中的电话号码,javascript,regex,negative-lookahead,Javascript,Regex,Negative Lookahead,我有一个搜索电话号码模式的正则表达式: [(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4} 这与以下格式的电话号码匹配: 123 456 7890 (123)456 7890 (123) 456 7890 (123)456-7890 (123) 456-7890 123.456.7890 123-456-7890 我想扫描整个页面(使用JavaScript)来查找此匹配,但不包括锚内已经存在的此匹配。 找到匹配项后,我想将电话号码转换为移动设备的点击呼叫链接: (1

我有一个搜索电话号码模式的正则表达式:

[(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4}
这与以下格式的电话号码匹配:

123 456 7890
(123)456 7890
(123) 456 7890
(123)456-7890
(123) 456-7890
123.456.7890
123-456-7890
我想扫描整个页面(使用JavaScript)来查找此匹配,但不包括锚内已经存在的此匹配。 找到匹配项后,我想将电话号码转换为移动设备的点击呼叫链接:

(123) 456-7890 --> <a href="tel:1234567890">(123) 456-7890</a>
(123)456-7890-->
我很确定我需要做一个否定的查找。我已经试过了,但这似乎不是正确的想法:

(?!.*(\<a href.*?\>))[(]?\d{3}[)]?[(\s)?.-]\d{3}[\s.-]\d{4}
(?!*(\)[(]?\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}))
(
这将查找href标记外部和内部的所有电话号码,但是,在所有情况下,它都会将电话号码本身作为特定的正则表达式组返回。因此,您可以将在新href标记中找到的每个电话号码括起来,因为如果它们存在,您将替换原始的href标记

正则表达式组或“捕获组”捕获与整个正则表达式匹配的特定部分。它们是通过将正则表达式的一部分括在括号中创建的。这些组按其左括号的顺序从左到右编号,并且可以通过在Javascript中的该数字前面放置一个
$
来引用它们匹配的输入部分。其他im实现为此目的使用
\
。这称为反向引用。反向引用可以稍后出现在正则表达式或替换字符串中(如本答案前面所述)。详细信息:

举一个简单的例子,假设您有一个包含账号和其他信息的文档。每个账号都以“account”一词开头,您希望将其更改为“acct”,但“account”出现在文档的其他位置,因此您不能仅对其进行查找和替换。您可以使用
account([0-9]+)的正则表达式
。在这个正则表达式中,
([0-9]+)
形成一个组,该组将与实际账号匹配,我们可以在替换字符串中反向引用为
$1
,它将成为
账户$1

您可以在这里进行测试:

。使用HTML/DOM解析器获取文本节点(浏览器可以为您过滤它,删除锚定标记和所有太短而无法包含电话号码的文本),您可以直接检查文本

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

//此查询查找至少包含12个非空白字符的所有文本节点
//谁不是锚定标签的直接子女
//让XPath应用基本过滤器可以显著减少元素的数量
//您需要处理(存在大量的短文本和/或纯空白文本节点)
//在大多数DOM中)
var xpr=document.evaluate('sundant-or-self::text()[not(parent::A)和字符串长度(normalize space(self::text())>=12]),
document.body,null,XPathResult.UNORDERED\节点\快照\类型,null);
对于(变量i=0,len=xpr.snapshotLength;i=3){
var parent=txt.parentNode;//在替换子节点之前保存父节点
//在现有元素之前插入新元素;第一个元素仅为
//第一个电话号码前的文字
parent.insertBefore(document.createTextNode(数字[0]),txt);
//现在显式创建锚定对和以下文本节点
对于(变量j=1;j

对于记录,基本过滤器在大多数页面上都有很大的帮助。例如,在我看来,在这个页面上(根据用户、浏览器、浏览器扩展和脚本等而有所不同),没有过滤器的情况下,查询的快照
'substant-or-self::text()“
将有1794个项。忽略锚定标记作为父项的文本,
的后代或自我::text()[不是(父项::A)]“
将其缩减到1538,验证非空白内容长度至少为12个字符的完整查询将其缩减到87个项目。将正则表达式应用到87个项目是愚蠢的更改,性能方面是明智的,并且您已经不再需要使用不合适的工具解析HTML。

您不想在href中查找电话号码还是仅在ey已经有电话链接了吗?我想排除锚中已经存在的任何匹配项。很不幸,因为您似乎不知道
a
标记的确切外观,您不能使用lookback,因为
+
*
是不允许的。如本SO答案所示,我可能会通过查找锚定标签中的所有电话号码,并使它们与查找电话号码的第二个正则表达式不匹配。像这样?@Tim您可以在正则表达式前面加上以下负前瞻前缀:^(?!谢谢。你能解释一下正则表达式组是如何工作的吗?@Tim我刚刚在我的答案中添加了一个正则表达式组的解释。谢谢@ShadowRanger。这看起来很棒。你有一个正则表达式组的例子吗
<a href="tel:$3$7$4$8$5$9">($3$7) $4$8-$5$9</a>
// 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
    }
}