你不應該用正則表達式來做到這一點 - 至少不是正則表達式。改爲使用適當的HTML DOM解析器,如PHP’s DOM library。然後,您可以迭代節點,檢查它是否是文本節點,並執行正則表達式搜索並適當地替換文本節點。
像這樣的東西應該這樣做:
$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);
}
}
}
}
好吧,既然getElementsByTagName
的DOMNodeLists和childNodes
是live,在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
然後被用於發現和通過分割內容串入非URL/URL份使用preg_split
同時捕捉所產生的用於分隔符替換一個DOMText節點的內容平原網址在1 + 2·n項目的數組中。然後非URL部分由新一個DOMText節點代替,URL部分由新A
元素然後原點一個DOMText節點,其隨後在端部除去之前插入替換。由於這個mapOntoTextNodes
遞歸地走,只需在特定的DOMNode上調用該函數就足夠了。
可能的重複[你能提供一些爲什麼很難用正則表達式解析XML和HTML的例子嗎?](http:// stackoverflow。com/questions/701166/can-you-provide-some-examples-of-why-it-is-hard-to-parse-xml-and-html-with-a-rege) – 2011-07-09 20:54:43
[RegEx match open標籤除XHTML自包含標籤](http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags) – 2011-09-15 14:15:12