Twitter正则表达式仅在尚未成为链接时使用


Twitter regex only when not already a link

我知道这件事已经累死了。我已经找到了很多关于这个主题的话题,并听取了很多建议。但是,如果我有以下字符串:

@testaccount
<a href="http://twitter.com/testaccount">@testaccount</a>

显然,我不想将第二个转换为链接,因为它已经是一个了。我已经找到了第一封没有电子邮件的邮件(感谢这里已经有几个问题)。

这是我已经掌握的模式:

/(?<=^|(?<=[^a-zA-Z0-9-_'.]))@([A-Za-z]+[A-Za-z0-9_]+)/

这将完美地转换第一个,但第二个显然将成为"双重链接"。

所以我设法计算出我应该使用类似于(?!<'/a>)的东西。然而,这只删除了testaccount的最后一个t

从本质上讲,我需要找到一种方法来忽略整个匹配,而不是只删除一个字符。这可能吗?

我使用的语言是PHP。

感谢

您可以有效地使用(*SKIP)(*FAIL)回溯控制动词。

~<a[^<]*</a>(*SKIP)(*F)|@('w+)~

这个想法是跳过位于<a ..标签之间的任何内容。在alternation操作符的左侧,我们匹配我们不想要的子模式,使其失败,并迫使正则表达式引擎不重试子字符串。

实时演示

您需要在负前瞻中的<'/a>之前添加.*?。这样它就不会匹配已经锚定的@字符串。

(?<=^|(?<=[^a-zA-Z0-9-_'.]))@([A-Za-z0-9_]+)(?!.*?<'/a>)

演示

Regex,坏。解析,很好。

$dom = new DOMDocument();
$dom->loadHTML("<div>".$your_html_source_here."</div>",
                                      LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//text()[contains(.,'@')][not(ancestor::a)]");
foreach($nodes as $node) {
    // each of these nodes contains at least one @ to be processed
    // note that children of <a> tags are automatically ignored
    preg_match_all("/(?:^|(?<='s))@'w+/",$node->nodeValue,$matches,
                                           PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE);
    // work backwards - it's easier
    foreach(array_reverse($matches[0]) as $match) {
        list($text,$offset) = $match;
        $node->splitText($offset+mb_strlen($text));
        $middle = $node->splitText($offset);
        // now wrap the text in a link:
        $link = $dom->createElement('a');
        $link->setAttribute("href","http://twitter.com/".substr($text,1));
        $node->parentNode->insertBefore($link,$middle);
        $link->appendChild($middle);
    }
}
// output
$result = substr(trim($dom->saveHTML()),strlen("<div>"),-strlen("</div>"));

(注意:在内容周围添加<div>是为了确保有一个根元素,否则解析会遇到问题。)

此处演示