DOMDocument->saveHTML() vs urlencode with commercial at s


DOMDocument->saveHTML() vs urlencode with commercial at symbol (@)

使用DOMDocument(),我正在替换$message中的链接并添加一些东西,例如[@MERGEID]。当我用$dom_document->saveHTML()保存更改时,链接被"排序"URL编码。 [@MERGEID]变得%5B@MERGEID%5D.

稍后在我的代码中,我需要将[@MERGEID]替换为 ID。所以我搜索urlencode('[@MERGEID]') - 但是,urlencode()将商业符号 (@( 更改为 %40,而 saveHTML(( 则保留了它。所以没有匹配 - '%5B@MERGEID%5D' != '%5B%40MERGEID%5D'

现在,我知道可以运行str_replace('%40', '@', urlencode('[@MERGEID]'))来获取在$message中找到合并变量所需的内容。

我的问题是,DOMDocument使用什么RFC规范,为什么它与urlencode甚至rawurlencode不同?我能做些什么来拯救str_replace吗?

演示代码:

$message = '<a href="http://www.google.com?ref=abc" data-tag="thebottomlink">Google</a>';
$dom_document = new 'DOMDocument();
libxml_use_internal_errors(true); //Supress content errors
$dom_document->loadHTML(mb_convert_encoding($message, 'HTML-ENTITIES', 'UTF-8'));       
$elements = $dom_document->getElementsByTagName('a');
foreach($elements as $element) {    
    $link = $element->getAttribute('href'); //http://www.google.com?ref=abc
    $tag = $element->getAttribute('data-tag'); //thebottomlink
    if ($link) {
        $newlink = 'http://www.example.com/click/[@MERGEID]?url=' . $link;
        if ($tag) {
            $newlink .= '&tag=' . $tag;
        } 
        $element->setAttribute('href', $newlink);
    }
}
$message = $dom_document->saveHTML();
$urlencodedmerge = urlencode('[@MERGEID]');
die($message . ' and url encoded version: ' . $urlencodedmerge); 
//<a data-tag="thebottomlink" href="http://www.example.com/click/%5B@MERGEID%5D?url=http://www.google.com?ref=abc&amp;tag=thebottomlink">Google</a> and url encoded version: %5B%40MERGEID%5D
我相信

这两种编码有不同的目的。 urlencode()编码"要在URL的查询部分中使用的字符串",而$element->setAttribute('href', $newlink);编码要用作URL的完整URL。

例如:

urlencode('http://www.google.com'); // -> http%3A%2F%2Fwww.google.com

这对于对查询部分进行编码很方便,但不能用于<a href='...'>

然而:

$element->setAttribute('href', $newlink); // -> http://www.google.com

将正确编码字符串,使其仍可用于href .它无法编码的原因@因为它无法判断@是查询的一部分还是userinfoemail url 的一部分(例如:mailto:invisal@google.cominvisal@127.0.0.1 (


溶液

  1. 您可以使用 @@MERGEID@@ 代替[@MERGEID] 。然后,稍后将其替换为您的 ID。此解决方案甚至不需要您使用 urlencode .

  2. 如果你坚持使用urlencode,你可以只用%40代替@。所以,你的代码将是这样的$newlink = 'http://www.example.com/click/[%40MERGEID]?url=' . $link;

  3. 你也可以做一些类似$newlink = 'http://www.example.com/click/' . urlencode('[@MERGEID]') . '?url=' . $link;

urlencode函数和rawurlencode大多基于RFC 1738。但是,自 2005 年以来,当前用于 URI 标准的 RFC 是 RFC 3986

另一方面,DOM扩展使用基于RFC 3629的UTF-8编码。使用 utf8_encode(( 和 utf8_decode(( 处理 ISO-8859-1 编码的文本或 Iconv 用于其他编码。

通用 URI 语法要求新的 URI 方案提供实际上,URI 中字符数据的表示形式必须表示未保留集合中的字符,无需翻译,以及应根据 UTF-8 将所有其他字符转换为字节,并且然后对这些值进行百分比编码。

这是一个根据 RFC 3986 解码 URL 的功能。

<?php
    function myUrlEncode($string) {
       $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%2B', '%24', '%2C', '%2F', '%3F', '%25', '%23', '%5B', '%5D');
       $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "+", "$", ",", "/", "?", "%", "#", "[", "]");
       return str_replace($entities, $replacements, urldecode($string));
    }
?>

PHP小提琴。

<小时 />

更新:

由于 UTF8 已被用于编码$message

$dom_document->loadHTML(mb_convert_encoding($message, 'HTML-ENTITIES', 'UTF-8'))

返回不带百分比的 URL 时,请使用urldecode($message)

die(urldecode($message) . ' and url encoded version: ' . $urlencodedmerge); 

从技术角度来看,问题的根本原因已经得到了很好的解释。

然而,在我看来,你的方法有一个概念上的缺陷,它造成了你现在试图解决的情况。

通过 DomDocument 对象处理输入$message,您已经移动到更高的抽象级别。将已被"提升"为HTML流的唯一纯字符串进行操作是错误的。

与其尝试重现 DomDocument 的行为,不如使用库本身来定位、提取和替换感兴趣的值:

$token = 'blah blah [@MERGEID]';
$message = '<a id="' . $token . '" href="' . $token . '"></a>';
$dom = new DOMDocument();
$dom->loadHTML($message);
echo $dom->saveHTML(); // now we have an abstract HTML document
// extract a raw value
$rawstring = $dom->getElementsByTagName('a')->item(0)->getAttribute('href');
// do the low-level fiddling
$newstring = str_replace($token, 'replaced', $rawstring);
// push the new value back into the abstract black box.
$dom->getElementsByTagName('a')->item(0)->setAttribute('href', $newstring);
// less code written, but works all the time
$rawstring = $dom->getElementsByTagName('a')->item(0)->getAttribute('id');
$newstring = str_replace($token, 'replaced', $rawstring);
$dom->getElementsByTagName('a')->item(0)->setAttribute('id', $newstring);
echo $dom->saveHTML();

如上所示,今天我们试图解决您的令牌位于 href 中时的问题,但有一天我们可能想要搜索并替换文档中其他地方的标签。为了解释这种情况,不要费心让你的低级代码成为HTML感知。

(另一种选择是在完成所有低级替换之前不加载 DomDocument,但我猜这是不切实际的(


完整的概念验证:

function searchAndReplace(DOMNode $node, $search, $replace) {
    if($node->hasAttributes()) {
        foreach ($node->attributes as $attribute) {
            $input = $attribute->nodeValue;
            $output = str_replace($search, $replace, $input);
            $attribute->nodeValue = $output;
        }
    }
    if(!$node instanceof DOMElement) { // this test needs double-checking
        $input = $node->nodeValue;
        $output = str_replace($search, $replace, $input);
        $node->nodeValue = $output;
    }
    if($node->hasChildNodes()) {
        foreach ($node->childNodes as $child) {
            searchAndReplace($child, $search, $replace);
        }
    }
}
$token = '<>&;[@MERGEID]';
$message = '<a/>';
$dom = new DOMDocument();
$dom->loadHTML($message);
$dom->getElementsByTagName('a')->item(0)->setAttribute('id', "foo$token");
$dom->getElementsByTagName('a')->item(0)->setAttribute('href', "http://foo@$token");
$textNode = new DOMText("foo$token");
$dom->getElementsByTagName('a')->item(0)->appendchild($textNode);
echo $dom->saveHTML();
searchAndReplace($dom, $token, '*replaced*');
echo $dom->saveHTML();
如果你

使用saveXML()它不会像saveHTML()那样弄乱编码:

.PHP

//your code...
$message = $dom_document->saveXML();

编辑:同时删除XML标记:

//this will add an xml tag, so just remove it
$message=preg_replace("/'<'?xml(.*?)'?'>/","",$message);
echo $message;

输出

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><a href="http://www.example.com/click/[@MERGEID]?url=http://www.google.com?ref=abc&amp;tag=thebottomlink" data-tag="thebottomlink">Google</a></body></html>

请注意,两者都仍然正确地将&转换为&amp;

首先只对

原始 [@mergeid] 进行编码保存它是否有意义?然后,您的搜索应该匹配而无需str_replace?

$newlink = 'http://www.example.com/click/'.urlencode('[@MERGEID]').'?url=' . $link;

我知道这不能回答问题的第一篇文章,但据我所知,您不能在评论中发布代码。