我尝试将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();
但是我没有从物体上得到任何东西。
答案 0 :(得分: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->saveHTML())
也可以。
我非常惊讶PHP无法正确解析HTML内容。它似乎是在解析XML内容(这也是错误的,因为CDATA内容是解析的,而不是按字面值处理)。但是就是这样,如果您想要一个真正的文档解析器,则可能应该使用jsdom
来研究Node.js解决方案。答案 1 :(得分:0)
如果您在<script>
中有一个<script>
,可以使用以下(不太聪明)的解决方案。仍然存在一个问题:如果<script>
标签不平衡,则解决方案将无法正常工作。如果您的Javascript使用String.fromCharCode
打印字符串</script>
,则可能会发生这种情况。
$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
中的“脚本”字符串替换为“样式”,则还可以屏蔽CSS样式,该样式也可以包含标记名称(即,在注释中)。