【问题标题】:PHP 5.4.16 DOMDocument removes parts of JavascriptPHP 5.4.16 DOMDocument 删除了部分 Javascript
【发布时间】:2019-01-24 07:45:28
【问题描述】:

我尝试将 HTML 页面从远程服务器加载到 PHP 脚本中,该脚本应该使用 DOMDocument 类来操作 HTML。但我已经看到,DOMDocument 类删除了 HTML 页面附带的 Javascript 的某些部分。有一些类似的东西:

<script type="text/javascript">
//...
function printJSPage() {
    var printwin=window.open('','haha','top=100,left=100,width=800,height=600');
    printwin.document.writeln(' <table border="0" cellspacing="5" cellpadding="0" width="100%">');
    printwin.document.writeln(' <tr>');
    printwin.document.writeln(' <td align="left" valign="bottom">');
    //...
    printwin.document.writeln('</td>');
    //...
}
</script>

但是 DOMDocument 发生了变化,即行

printwin.document.writeln('</td>');

printwin.document.writeln(' ');

还有很多其他的东西(即最后一个脚本标签不再存在。结果我得到了一个完全被破坏的页面,我无法进一步发送。

所以我认为,DOMDocument 在 Javascript 代码中的 HTML 标记存在问题,并尝试更正代码,以生成格式良好的文档。我可以阻止 DOMDocument 中的 Javascript 解析吗?

PHP代码片段是:

$stdin = file_get_contents('php://stdin');
$dom = new \DOMDocument();
@$dom->loadHTML($stdin);
return $dom->saveHTML();   // will produce wrong HTML
//return $stdin;           // will produce correct HTML

我存储了两个 HTML 版本,并与 Meld 进行了比较。

我也测试过

@$dom->loadXML($stdin);
return $dom->saveHTML();

但我没有从对象中得到任何东西。

【问题讨论】:

  • 可以复制3v4l.org/O0iEf
  • 最初我认为这是 stackoverflow.com/questions/4029341/… 的副本,但在这个问题中似乎没有一个明智的解决方案,所以我将使用 DomDocument 无法处理脚本标签正确,大声说出来听起来很荒谬。我什至尝试将脚本内容包装在 &lt;![CDATA[...]]&gt; 中,但这仍然不起作用
  • 我也不能在解析之前包装脚本标签,也不能在页面上做任何事情。我从外部系统获取页面(我无法访问那里),但我需要一些 PHP 来对页面进行后处理,然后才能将它们传送到浏览器。
  • 可能不是DOMDocument的问题,而是底层libxml2的问题。我已经用xmllint --html --htmlout /tmp/mypage.html 测试了我的页面,我得到了很多解析器错误,正是在 DOMDocument 删除标签的位置。

标签: javascript php domdocument


【解决方案1】:

这里有一个可能有用的技巧。想法是将脚本内容替换为保证是有效 HTML 且唯一的字符串,然后将其替换回来。

它将脚本标签内的所有内容替换为这些内容的MD5,然后将它们替换回来。

$scriptContainer = [];
$str = preg_replace_callback ("#<script([^>]*)>(.*?)</script>#s", function ($matches) use (&$scriptContainer) {
     $scriptContainer[md5($matches[2])] = $matches[2];
        return "<script".$matches[1].">".md5($matches[2])."</script>";
    }, $str);
$dom = new \DOMDocument();
@$dom->loadHTML($str);
$final = strtr($dom->saveHTML(), $scriptContainer); 

这里的strtr 只是因为数组的格式化方式很方便,使用str_replace(array_keys($scriptContainer), $scriptContainer, $dom-&gt;saveHTML()) 也可以。

我发现 PHP 不能正确解析 HTML 内容非常令人惊讶。相反,它似乎正在解析 XML 内容(也是错误的,因为 CDATA 内容是被解析的,而不是被逐字处理)。然而它就是这样,如果你想要一个真正的文档解析器,那么你可能应该使用jsdom 来研究 Node.js 解决方案

【讨论】:

    【解决方案2】:

    如果您在&lt;script&gt; 中有&lt;script&gt;,则以下(不太聪明)解决方案将处理该问题。还有一个问题:如果&lt;script&gt;标签不平衡,解决方案就行不通。如果您的 Javascript 使用 String.fromCharCode 打印字符串 &lt;/script&gt;,则可能会发生这种情况。

    $scriptContainer = array();
    
    function getPosition($tag) {
        return $tag[0][1];
    }
    
    function getContent($tag) {
        return $tag[0][0];
    }
    
    function isStart($tag) {
        $x = getContent($tag);
        return ($x[0].$x[1] === "<s");
    }
    
    function isEnd($tag) {
        $x = getContent($tag);
        return ($x[0].$x[1] === "</");
    }
    
    function mask($str, $scripts) {
        global $scriptContainer;
    
        $res = "";
        $start = null;
        $stop = null;
        $idx = 0;
    
        $count = 0;
        foreach ($scripts as $tag) {
    
                if (isStart($tag)) {
                        $count++;
                        $start = ($start === null) ? $tag : $start;
                }
    
                if (isEnd($tag)) {
                        $count--;
                        $stop = ($count == 0) ? $tag : $stop;
                }
    
                if ($start !== null && $stop !== null) {
                        $res .= substr($str, $idx, getPosition($start) - $idx);
                        $res .= getContent($start);
                        $code = substr($str, getPosition($start) + strlen(getContent($start)), getPosition($stop) - getPosition($start) - strlen(getContent($start)));
                        $hash = md5($code);
                        $res .= $hash;
                        $res .= getContent($stop);
    
                        $scriptContainer[$hash] = $code;
    
                        $idx = getPosition($stop) + strlen(getContent($stop));
                        $start = null;
                        $stop = null;
                }
        }
    
        $res .= substr($str, $idx);
        return $res;
    }
    
    preg_match_all("#\<script[^\>]*\>|\<\/script\>#s", $html, $scripts, PREG_OFFSET_CAPTURE|PREG_SET_ORDER);
    $html = mask($html, $scripts);
    
    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    libxml_use_internal_errors(false);
    
    // handle some things within DOM
    
    echo strtr($dom->saveHTML(), $scriptContainer);
    

    如果您将preg_match_all 中的“script”字符串替换为“style”,您还可以屏蔽 CSS 样式,它也可以包含标签名称(即在 cmets 中)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-09
      • 2022-01-03
      相关资源
      最近更新 更多