Подтвердить что ты не робот

Как заменить текстовые URL-адреса и исключать URL-адреса в тегах HTML?

Мне нужна помощь здесь.

Я хочу включить это:

sometext sometext http://www.somedomain.com/index.html sometext sometext

в

sometext sometext <a href="#" onclick="location.href='http://somedoamai.com/index.html'; return false;">www.somedomain.com/index.html</a> sometext sometext

Мне удалось это сделать с помощью этого регулярного выражения:

preg_replace("#((http|https|ftp)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)#ie", "'<a href=\"$1\" target=\"_blank\">$1</a>$4'", $text);

Проблема заключается в том, что он также заменяет URL img, например:

sometext sometext <img src="http//domain.com/image.jpg"> sometext sometext

превращается в:

sometext sometext <img src="<a href="#" onclick="location.href='http//domain.com/image.jpg'; return false;">domain.com/image.jpg</a>"> sometext sometext

Пожалуйста, помогите.

4b9b3361

Ответ 1

Оптимизированная версия Gumbo выше:

$html = <<< HTML
<html>
<body>
<p>
    This is a text with a <a href="http://example.com/1">link</a>
    and another <a href="http://example.com/2">http://example.com/2</a>
    and also another http://example.com with the latter being the
    only one that should be replaced. There is also images in this
    text, like <img src="http://example.com/foo"/> but these should
    not be replaced either. In fact, only URLs in text that is no
    a descendant of an anchor element should be converted to a link.
</p>
</body>
</html>
HTML;

Позвольте использовать XPath, который извлекает только те элементы, которые на самом деле являются текстовыми нотами, содержащими http://или https://или ftp://, и которые сами по себе не являются текстовыми элементами элементов привязки.

$dom = new DOMDocument;
$dom->loadHTML($html);
$xPath = new DOMXPath($dom);
$texts = $xPath->query(
    '/html/body//text()[
        not(ancestor::a) and (
        contains(.,"http://") or
        contains(.,"https://") or
        contains(.,"ftp://") )]'
);

В приведенном выше XPath мы дадим TextNode со следующими данными:

 and also another http://example.com with the latter being the
    only one that should be replaced. There is also images in this
    text, like 

С PHP5.3 мы также могли использовать PHP внутри XPath для использования шаблона Regex для выбора наших узлов вместо трех вызовов, которые должны содержать,

Вместо разделения текстовых разделов на стандартную совместимость, мы будем использовать фрагмент документа и просто заменим весь текст на фрагмент. Нестандартный в этом случае означает только метод, который мы будем использовать для этого, не является частью спецификация W3C API DOM.

foreach ($texts as $text) {
    $fragment = $dom->createDocumentFragment();
    $fragment->appendXML(
        preg_replace(
            "~((?:http|https|ftp)://(?:\S*?\.\S*?))(?=\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)~i",
            '<a href="$1">$1</a>',
            $text->data
        )
    );
    $text->parentNode->replaceChild($fragment, $text);
}
echo $dom->saveXML($dom->documentElement);

и тогда выдается:

<html><body>
<p>
    This is a text with a <a href="http://example.com/1">link</a>
    and another <a href="http://example.com/2">http://example.com/2</a>
    and also another <a href="http://example.com">http://example.com</a> with the latter being the
    only one that should be replaced. There is also images in this
    text, like <img src="http://example.com/foo"/> but these should
    not be replaced either. In fact, only URLs in text that is no
    a descendant of an anchor element should be converted to a link.
</p>
</body></html>

Ответ 2

Вы не должны делать это с регулярными выражениями - по крайней мере, не с регулярными выражениями. Вместо этого используйте правильный парсер HTML DOM, такой как PHP DOM library. Затем вы можете перебирать узлы, проверять, есть ли текст node и выполнять поиск по регулярному выражению и соответственно заменить текст node.

Что-то вроде этого должно это сделать:

$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 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 используется для сопоставления заданной функции обратного вызова на каждом DOMText node в документе DOM. Вы можете либо передать весь DOMDocument node, либо просто конкретный DOMNode (в данном случае только BODY node).

Затем функция foo используется для поиска и замены простых URL-адресов в содержимом DOMText node путем разделения строки содержимого на части, не содержащие URL/URL, с помощью preg_split при захвате используемого разделителя, в результате чего получается массив из 1 + 2 · n элементов. Затем части, не связанные с URL, заменяются новыми узлами DOMText, а части URL заменяются новыми элементами A, которые затем вставляются перед исходным DOMText node, который затем удаляется в конце. Поскольку этот mapOntoTextNodes идет рекурсивно, достаточно просто вызвать эту функцию на определенном DOMNode.

Ответ 3

спасибо за ответ, но он все еще работает. Я исправил эту функцию:

function livelinked ($text){
        preg_match_all("#((http|https|ftp)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)|^(jpg)#ie", $text, $ccs);
        foreach ($ccs[3] as $cc) {
           if (strpos($cc,"jpg")==false  && strpos($cc,"gif")==false && strpos($cc,"png")==false ) {
              $old[] = "http://".$cc;
              $new[] = '<a href="http://'.$cc.'" target="_blank">'.$cc.'</a>';
           }
        }
        return str_replace($old,$new,$text);
}

Ответ 4

Если вы хотите продолжать использовать регулярное выражение (и в этом случае регулярное выражение вполне уместно), вы можете иметь регулярное выражение только для URL-адресов, которые "автономны". Используя последовательность escape-последовательности слов (\b), вы можете иметь только регулярное выражение, где http сразу предшествует пробел или начало текста:

preg_replace("#\b((http|https|ftp)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)#ie", "'<a href=\"$1\" target=\"_blank\">$1</a>$4'", $text);
            // ^^ thar she blows

Таким образом, "http://..." не будет соответствовать, но http://, поскольку его собственное слово будет.

Ответ 5

DomDocument более зрелый и работает намного быстрее, поэтому это просто альтернатива, если кто-то хочет использовать PHP Simple HTML DOM Parser:

<?php
require_once('simple_html_dom.php');

$html = str_get_html('sometext sometext http://www.somedomain.com/index.html sometext sometext
<a href="http://www.somedomain.com/index.html">http://www.somedomain.com/index.html</a>
sometext sometext <img src="http//domain.com/image.jpg"> sometext sometext');

foreach ($html->find('text') as $element)
{
    // you can add any tag into the array to exclude from replace
    if (!in_array($element->parent()->tag, array('a')))
        $element->innertext = preg_replace("#((http|https|ftp)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\<|$|\.\s)#ie", "'<a href=\"$1\" target=\"_blank\">$1</a>$4'", $element->innertext);
}

echo $html;

Ответ 6

Вы можете попробовать мой код из этого вопроса:

echo preg_replace('/<a href="([^"]*)([^<\/]*)<\/a>/i', "$1", 'sometext sometext <img src="http//domain.com/image.jpg"> sometext sometext');

Если вы хотите повернуть некоторые другие теги - это достаточно легко:

echo preg_replace('/<img src="([^"]*)([^\/><]*)>/i', "$1", 'sometext sometext <img src="http//domain.com/image.jpg"> sometext sometext');

Ответ 7

сопоставить пробел (\ s) в начале и конце строки url, это гарантирует, что

"http://url.com" 

не соответствует

http://url.com