【问题标题】:DOMDocument->saveHTML() vs urlencode with commercial at symbol (@)DOMDocument->saveHTML() 与带有商业符号 (@) 的 urlencode
【发布时间】:2015-02-02 19:53:26
【问题描述】:

使用DOMDocument(),我将替换$message 中的链接并添加一些内容,例如[@MERGEID]。当我使用$dom_document->saveHTML() 保存更改时,链接会得到“某种”url 编码。 [@MERGEID] 变为 %5B@MERGEID%5D

稍后在我的代码中,我需要用一个 ID 替换 [@MERGEID]。所以我搜索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

【问题讨论】:

  • 这也是我感兴趣的。您是否尝试过按照手册使用 utf8_encode/decode 或 iconv?
  • @Kkinsey 在什么上运行 utf8_encode/decode 或 iconv?
  • 没关系。我认为是我的错误。我再看看。
  • 解释问题:为什么在使用DOMDocument::saveHTMLDOMAttribute 节点的值中没有对字符@ 进行百分比编码?
  • 一开始就对原始的 [@mergeid] 进行 urlencode 编码难道没有意义吗?然后您的搜索应该匹配而不需要 str_replace? $newlink = 'example.com/click/…' 。 $链接;

标签: php domdocument encode urlencode rfc


【解决方案1】:

我相信这两种编码有不同的用途。 urlencode() 编码"a string to be used in a query part of a URL",而$element-&gt;setAttribute('href', $newlink); 编码一个完整的 URL 以用作 URL。

例如:

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

这便于编码查询部分,但不能用于&lt;a href='...'&gt;

但是:

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

【讨论】:

  • 这如何回答这个问题?
  • @Phil,他问为什么这两个在编码[@MERGEID]时会产生不同的结果@
  • @invisal 我明白你在说什么。所以,鉴于这种解释,真的没有办法绕过我额外的str_replace
  • @John,是和否,请查看我的解决方案部分。
  • 1、2和3都是我问题中提出的问题的可能解决方案,谢谢!不幸的是,它们都不适用于我自己的现实世界问题 - 合并变量是由前端用户插入的,并且已经有一段时间了,所以现在不可能完全改变他们所知道的。 2 和 3 不起作用,因为许多链接已经在 $message 内部创建,所以我无法像 $newlink 那样将它们拆分。
【解决方案2】:

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 Fiddle.


更新:

由于UTF8已被用于编码$message

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

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

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

【讨论】:

  • 为什么urlencoderawurlencode 没有更新为使用url 标准RFC?
  • 过去似乎还有其他公认的 URL 编码方式。
  • 但是,运行 urldecode 的资源或时间与运行 str_replace 有什么不同吗?
  • 据我了解,他们都使用正则表达式。快速测试显示没有相关差异。
【解决方案3】:

从技术角度来看问题的根本原因has been very well explained

然而,在我看来,您的方法存在概念上的缺陷,它造成了您现在正试图解决的情况。

通过 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();

【讨论】:

    【解决方案4】:

    如果您使用saveXML(),它不会像saveHTML() 那样混淆编码:

    PHP

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

    EDIT:同时删除 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;amp; 转换为&amp;amp;

    【讨论】:

    • 既然它实际上是一个 HTML 文档,那么运行 saveXML 会有什么后果?
    • saveXML 将输出一个 xhtml 文档,并将&lt;?xml 行放在顶部。此外,它将对saveHTML 可能使用实体编码的某些字符使用十六进制编码,例如,&amp;copy; 变为 &amp;#xA9;
    • 听起来这不是一个真正的解决方案,然后是热 HTML 文档
    • 我不明白你的意思
    • 然后是 HTML 文档* - 抱歉,错字。将 HTML 保存为 XML 并添加 XML 声明标记根本无效 - 将您的输出通过 w3c validator 并查看输出。所以,这根本不是解决方案。
    【解决方案5】:

    首先对原始的 [@mergeid] 进行 urlencode 并保存它是否没有意义?然后您的搜索应该匹配而不需要 str_replace?

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

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

    【讨论】:

    • 您可以在 cmets 中发布代码 - 添加评论时,文本区域的右下角是 a "help" link :) 只需使用反引号。
    猜你喜欢
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2013-10-06
    • 2019-12-02
    • 2021-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多