您不应该使用正则表达式-至少不是仅使用正则表达式。请使用适当的HTML DOM解析器,例如PHP的DOM库之一。然后,您可以迭代节点,检查它是否是文本节点,然后进行正则表达式搜索并适当地替换文本节点。
这样的事情应该做到:
$pattern = "~((?:http|https|ftp)://(?:\S*?\.\S*?))(?=\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\
$doc = new DOMDocument();
$doc->loadHTML($str);
// for every element in the document
foreach ($doc->getElementsByTagName('*') as $elem) {
// for every child node in each element
foreach ($elem->childNodes as $node) {
if ($node->nodeType === XML_TEXT_NODE) {
// split the text content to get an array of 1+2*n elements for n URLs in it
$parts = preg_split($pattern, $node->nodeValue, -1, PREG_SPLIT_DELIM_CAPTURE);
$n = count($parts);
if ($n > 1) {
$parentNode = $node->parentNode;
// insert for each pair of non-URL/URL parts one DOMText and DOMElement node before the original DOMText node
for ($i=1; $i
$a = $doc->createElement('a');
$a->setAttribute('href', $parts[$i]);
$a->setAttribute('target', '_blank');
$a->appendChild($doc->createTextNode($parts[$i]));
$parentNode->insertBefore($doc->createTextNode($parts[$i-1]), $node);
$parentNode->insertBefore($a, $node);
}
// insert the last part before the original DOMText node
$parentNode->insertBefore($doc->createTextNode($parts[$i-1]), $node);
// remove the original DOMText node
$node->parentNode->removeChild($node);
}
}
}
}
好了,因为的DOMNodeList小号的getElementsByTagName和childNodes是活的,在DOM的每一个变化反映到该列表中,因此你不能用foreach那会也迭代新加入的节点。取而代之的是,您需要使用for循环,并跟踪添加的元素以增加索引指针,并最好适当地预先计算数组边界。
但是由于在这种复杂的算法中这非常困难(三个for循环中的每个循环都需要一个索引指针和数组边界),因此使用递归算法会更方便:
function mapOntoTextNodes(DOMNode $node, $callback) {
if ($node->nodeType === XML_TEXT_NODE) {
return $callback($node);
}
for ($i=0, $n=count($node->childNodes); $i
$nodesChanged = 0;
switch ($node->childNodes->item($i)->nodeType) {
case XML_ELEMENT_NODE:
$nodesChanged = mapOntoTextNodes($node->childNodes->item($i), $callback);
break;
case XML_TEXT_NODE:
$nodesChanged = $callback($node->childNodes->item($i));
break;
}
if ($nodesChanged !== 0) {
$n += $nodesChanged;
$i += $nodesChanged;
}
}
}
function foo(DOMText $node) {
$pattern = "~((?:http|https|ftp)://(?:\S*?\.\S*?))(?=\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\
$parts = preg_split($pattern, $node->nodeValue, -1, PREG_SPLIT_DELIM_CAPTURE);
$n = count($parts);
if ($n > 1) {
$parentNode = $node->parentNode;
$doc = $node->ownerDocument;
for ($i=1; $i
$a = $doc->createElement('a');
$a->setAttribute('href', $parts[$i]);
$a->setAttribute('target', '_blank');
$a->appendChild($doc->createTextNode($parts[$i]));
$parentNode->insertBefore($doc->createTextNode($parts[$i-1]), $node);
$parentNode->insertBefore($a, $node);
}
$parentNode->insertBefore($doc->createTextNode($parts[$i-1]), $node);
$parentNode->removeChild($node);
}
return $n-1;
}
$str = '
![image.jpg](http//domain.com/image.jpg)
$doc = new DOMDocument();
$doc->loadHTML($str);
$elems = $doc->getElementsByTagName('body');
mapOntoTextNodes($elems->item(0), 'foo');
这里mapOntoTextNodes用于将给定的回调函数映射到DOM文档中的每个DOMText节点上。您可以传递整个DOMDocument节点,也可以仅传递特定的DOMNode(在这种情况下,仅BODY传递该节点)。
该函数foo然后被用于查找和替换在平原网址一个DOMText通过分裂节点的内容的内容字符串转换成非URL / URL用部件preg_split,同时捕获造成的1 + 2的阵列所使用的分隔符· Ñ项目。然后,将非URL部分替换为新的DOMText节点,然后将URL部分替换为新A元素,然后将其插入到原始DOMText节点之前,然后将其最后删除。由于此操作是mapOntoTextNodes递归的,因此只需在特定的DOMNode上调用该函数即可。