我知道这件事已经累死了。我已经找到了很多关于这个主题的话题,并听取了很多建议。但是,如果我有以下字符串:
@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>
是为了确保有一个根元素,否则解析会遇到问题。)
此处演示