为了账号安全,请及时绑定邮箱和手机立即绑定

如何替换文本URL并在HTML标签中排除URL?

/ 猿问

如何替换文本URL并在HTML标签中排除URL?

ITMISS 2019-11-20 10:54:36

我需要你的帮助。


我想转这个:


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

变成:


sometext sometext <a href="http://somedoamai.com/index.html">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);

问题在于它也在替换imgURL,例如:


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

变成:


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

请帮忙。


查看完整描述

3 回答

?
POPMUISE

上面的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,该XPath仅获取那些元素,这些元素实际上是包含http://或https://或ftp://的textnode,而本身不是锚元素的textnode。


$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开始,我们还可以在XPath中使用PHP来使用Regex模式来选择节点,而不是三个对contains的调用。


我们将使用文档片段,而不是用符合标准的方式将文本节点分开,而只用该片段替换整个文本节点。在这种情况下,非标准仅表示我们将为此使用的方法不属于DOM API的W3C规范。


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>


查看完整回答
反对 回复 2019-11-20
?
潇湘沐

您不应该使用正则表达式-至少不是仅使用正则表达式。请使用适当的HTML DOM解析器,例如PHP的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);

            }

        }

    }

}

好了,因为的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<$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 / URL用部件preg_split,同时捕获造成的1 + 2的阵列所使用的分隔符· Ñ项目。然后,将非URL部分替换为新的DOMText节点,然后将URL部分替换为新A元素,然后将其插入到原始DOMText节点之前,然后将其最后删除。由于此操作是mapOntoTextNodes递归的,因此只需在特定的DOMNode上调用该函数即可。


查看完整回答
反对 回复 2019-11-20
?
智慧大石

感谢您的答复,但仍然有效。我已经修复了使用此功能:


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);

}


查看完整回答
反对 回复 2019-11-20

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信