不应该只使用正则表达式来完成此操作,而应该使用像
PHP DOM库这样的适当的HTML DOM解析器。然后,您可以迭代节点,检查它是否为文本节点,并进行正则表达式搜索和替换文本节点。以下类似代码可实现此目的:
$pattern = "~((?:http|https|ftp)://(?:\S*?\.\S*?))(?=\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)~i";
$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<$n; $i+=2) {
$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);
}
}
}
}
好的,由于
DOMNodeLists的
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<$n; ++$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|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)~i";
$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<$n; $i+=2) {
$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 = '<div>sometext http://www.somedomain.com/index.html sometext <img src="http//domain.com/image.jpg"> sometext sometext</div>';
$doc = new DOMDocument();
$doc->loadHTML($str);
$elems = $doc->getElementsByTagName('body');
mapOntoTextNodes($elems->item(0), 'foo');
在这里,使用mapOntoTextNodes
将给定的回调函数映射到DOM文档中的每个DOMText节点。您可以传递整个DOMDocument节点或只是特定的DOMNode(在这种情况下只有BODY
节点)。
然后使用foo
函数查找并替换DOMText节点内容中的纯URL,方法是使用preg_split将内容字符串分割成非URL / URL部分,并捕获使用的分隔符,从而得到1 + 2·n个项目的数组。然后用新的DOMText节点替换非URL部分,用新的A
元素替换URL部分,然后在结尾处删除原来的DOMText节点。由于此mapOntoTextNodes
递归地遍历,因此只需在特定DOMNode上调用该函数即可。