【问题标题】:PHP DOMDocument: Errors while parsing unescaped stringsPHP DOMDocument:解析非转义字符串时出错
【发布时间】:2017-04-03 20:28:24
【问题描述】:

我在使用 PHP 的 DOMDocument 解析 HTML 时遇到问题。

我正在解析的 HMTL 具有以下 脚本标记

<script type="text/javascript">
    var showShareBarUI_params_e81 =
    {
        buttonWithCountTemplate: '<div class="sBtnWrap"><a href="#" onclick="$onClick"><div class="sBtn">$text<img src="$iconImg" /></div><div class="sCountBox">$count</div></a></div>',
    }
</script>

这个sn-p有两个问题:

1) buttonWithCountTemplate 变量中的 HTML 没有被转义。 DOMDocument 正确管理它,在解析时转义字符。没问题。

2) 在接近结尾处,有一个带有未转义结束标签的 img 标签:

<img src="$iconImg" />

/&gt; 使 DOMDocument 认为脚本已完成,但 它缺少结束标记。如果您使用 getElementByTagName 提取脚本,您将在此 img 标记处关闭标记,其余部分将显示为 HTML 上的文本

我的目标是删除此页面中的所有脚本,因此如果我在此标记上执行removeChild(),则该标记将被删除,但以下部分在呈现页面时显示为文本:

</div><div class="sCountBox">$count</div></a></div>',
        }
    </script>

修复 HTML 不是解决方案,因为我正在开发一个通用解析器并且需要处理所有类型的 HTML。

我的问题是我是否应该在将 HTML 提供给 DOMDocument 之前进行任何清理,或者是否有一个选项可以在 DOMDocument 上启用以避免触发此问题,或者即使我可以在加载 HTML 之前删除所有标签。

有什么想法吗?


编辑

经过一番研究,我发现了 DOMDocument 解析器的真正问题。考虑以下 HTML:

<div> <!-- Offending div without closing tag -->
<script type="text/javascript">
       var test = '</div>';
       // I should not appear on the result
</script>

使用以下php代码删除脚本标签(based on Gholizadeh's answer):

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$dom = new DOMDocument;
$dom->preserveWhiteSpace = false;
libxml_use_internal_errors(true);
$dom->loadHTML(file_get_contents('js.html'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
//@$dom->loadHTMLFile('script.html'); //fix tags if not exist

while($nodes = $dom->getElementsByTagName("script")) {
    if($nodes->length == 0) break;
    $script = $nodes->item(0);
    $script->parentNode->removeChild($script);
}

//return $dom->saveHTML();
$final = $dom->saveHTML();
echo $final;

结果如下:

<div> <!-- Offending div without closing tag -->
<p>';
       // I should not appear on the result
</p></div>

问题是第一个div标签没有关闭,似乎DOMDocument将JS字符串中的div标签作为html而不是简单的JS字符串。

我能做些什么来解决这个问题?请记住,修改 HTML 不是一种选择,因为我正在开发通用解析器。

【问题讨论】:

  • 有趣的问题。我厌倦了反对旧的 &lt;script type="text/javascript"&gt;&lt;!--...//--&gt;&lt;/script&gt; 语法的拥护者声称“所有浏览器都理解 JavaScript”,这最多是对的。
  • 真的是img元素的错吗?我的猜测是下面的&lt;/div&gt;,因为&lt;/ 的第一次出现会隐式地结束脚本元素的内容并关闭它。 // 恕我直言,您不能只让 DOM 解析器在任何损坏的 HTML 代码上松动,并期望得到正确的结果。如果您真的需要像这样解析混乱的 HTML,您可能需要在将其提供给 DOM 解析器之前对其进行一些“预处理” - 可能类似于 htmlpurifier.org
  • @RafałR 使用 loadXML 不是解决方案。如果您的 HTML 不是 100% 有效,则不会加载任何节点。尝试加载我的编辑,您会看到结果为空。

标签: php html domdocument


【解决方案1】:

我在这样的 html 文件上测试了以下代码:

<p>some text 1</p>
<img src="http://www.example.com/images/some_image_1.jpg">
<p>some text 2</p>
<p>some text 3</p>
<img src="http://www.example.com/images/some_image_2.jpg">

<script type="text/javascript">
    var showShareBarUI_params_e81 =
    {
        buttonWithCountTemplate: '<div class="sBtnWrap"><a href="#" onclick="$onClick"><div class="sBtn">$text<img src="$iconImg" /></div><div class="sCountBox">$count</div></a></div>',
    }
</script>

<p>some text 4</p>
<p>some text 5</p>
<img src="http://www.example.com/images/some_image_3.jpg">

php代码是:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

    $dom = new DOMDocument;
    $dom->preserveWhiteSpace = false;
    @$dom->loadHTML(file_get_contents('script.html'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    //@$dom->loadHTMLFile('script.html'); //fix tags if not exist 

    $nodes = $dom->getElementsByTagName("script");

    foreach($nodes as $i => $node){
        $script = $nodes->item($i);
        $script->parentNode->removeChild($script);
    }

    //return $dom->saveHTML();
    $dom->saveHtmlFile('script.html');

它适用于给定的示例,我认为您应该使用我在加载 html 代码时使用的选项。

根据上次问题更新编辑:

实际上,您无法使用正则表达式解析 [X]HTML(阅读此link 了解更多信息) 但是如果您的唯一目的是仅删除脚本标签,并且您可以确保它之间没有&lt;/script&gt; 标签作为字符串。你可以使用这个正则表达式:

$html = mb_convert_encoding(file_get_contents('script2.html'), 'HTML-ENTITIES', 'UTF-8');
$new_html = preg_replace('/<script(.*?)>(.*?)<\/script>/si', '', $html);
file_put_contents('script-result.html', $new_html);

坦率地说,问题在于您可能没有标准的 HTML 代码。但我认为最好尝试链接here的其他库。

否则我猜你应该写一个特殊的解析器来删除脚本标签并处理里面的单引号和双引号。

【讨论】:

  • 请检查编辑,我发现了真正的问题,您的解决方案不再适用。谢谢!
【解决方案2】:

我正在为您的问题提供不同的方法:

我的目标是删除此页面中的所有脚本

然后您可以使用 preg_replace_callback 函数删除它们,然后将 html 解析为 DOM。这是工作演示:demo

$htmlWithScript = "<html><body><div>something></div><script type=\"text/javascript\">
var showShareBarUI_params_e81 =
{
    buttonWithCountTemplate: '<div class=\"sBtnWrap\"><a href=\"#\" onclick=\"\$onClick\"><div class=\"sBtn\">\$text<img src=\"\$iconImg\" /></div><div class=\"sCountBox\">\$count</div></a></div>',
}
</script></body></html>";



$htmlWithoutScript = preg_replace_callback('~<script.*>.*</script>~Uis', function($matches){
return '';
}, $htmlWithScript);

编辑

但是不召唤克苏鲁怎么办呢?

不错的评论,但我不知道你在问什么 :) 如果它正在加载 html,那么您可以使用 file_get_contents() 加载 html

如果你不明白它将如何删除标签: preg_replace_callback 允许您根据正则表达式搜索匹配项并对其进行转换。在这种情况下删除它们(返回'';) 正则表达式正在寻找具有任何属性 (.*) 的起始标记和结束标记之间的任何内容

修饰符:

U -> 表示不贪心(可能的最短匹配)

i -> 不区分大小写(也会被匹配)

s -> 空格包含在 . (点)字符(换行不会破坏匹配)

我希望这能澄清一点..

【讨论】:

  • 但是不召唤克苏鲁怎么办呢?
  • 这发生在后端。你没有执行任何东西。充其量,您只是在看着克苏鲁并拔掉牙齿。
  • @Tschallacka 这很有用,但不一定相关。没有人建议删除解析器。相反,这是用有限的正则表达式补充解析器。上面的链接处理使用正则表达式作为解析器,这是不同的。其他答案似乎反映了这种混乱,因为许多人说可以在正则表达式的帮助下构建解析器(与制作正则表达式进行解析相比)
【解决方案3】:

您是否尝试过将 libxml 设置为使用内部错误?

$use_errors = libxml_use_internal_errors(true);
// your parsing code here
libxml_clear_errors();
libxml_use_internal_errors($use_errors);

它可能允许 dom 文档继续解析(也许)。

【讨论】:

  • 这与@ 没有区别。它不会阻止 HTML 被错误解析。
  • 是的,但我遇到过启用内部错误时 domdocument 可以解析的情况。这就是为什么我把它放在这里。
  • 正如@Tschallacka 所说,这只是隐藏了错误,解析问题仍然存在。
  • @ÁlvaroGonzález Using libxml_use_internal_errors is@ 的不同之处在于它只会抑制源自底层 libxml 而不是 any 错误的错误.但是,它对 OP 的问题没有帮助。
  • @Gordon 当然,我只是说它只是一种隐藏错误消息的方法(尽管libxml_use_internal_errors() 并没有真正抑制它们)。
【解决方案4】:

解析 html 文档主要是关于它的内容,而不是脚本。 特别是在不知道其行为和来源的情况下使用这些脚本可能是危险的。

因此,当涉及到 html 内容时,您可以使用这种方法省略脚本(我已经在评论中指出): How to combine PHP's DOMDocument with a JavaScript template

具体来说你的例子:

<?php
$html = <<<END
<!DOCTYPE html>
<html><body><h1>Hey now</h1>
<script type="text/javascript">
    var showShareBarUI_params_e81 =
    {
        buttonWithCountTemplate: '<div class="sBtnWrap"><a href="#" onclick="onClick"><div class="sBtn">text<img src="iconImg" /></div><div class="sCountBox">count</div></a></div>'
    }
</script>
</body></html>
END;

$dom = new DOMDocument();
$dom->preserveWhiteSpace = true; // needs to be before loading, to have any effect
$dom->loadXML($html);
    while (($r = $dom->getElementsByTagName("script")) && $r->length) {
        $r->item(0)->parentNode->removeChild($r->item(0));
    }
$dom->formatOutput = false;
print $dom->saveHTML();

//Outputs
//<!DOCTYPE html><html><head></head><body><h1>Hey now</h1></body></html>

您还可以尝试使用一些正则表达式在加载到 DOMDocument 之前删除脚本标签或检查其他 html 解析库。 最后你必须意识到,在某些情况下,即使是完美的表达也会被破坏,并且 DOMDocument 解析器不如真正的浏览器引擎。 一切都是为了您的解析和寻找最佳解决方案的目的。

PHP 简单 HTML DOM 解析器示例:

http://simplehtmldom.sourceforge.net/manual.htm

require_once 'libs/simplehtmldom_1_5/simple_html_dom.php';
$html = <<<END
<div> <!-- Offending div without closing tag -->
<script type="text/javascript">
       var test = '</div>';
       // I should not appear on the result
</script>
END;

$dom = str_get_html($html);
echo $dom;

//outputs with no error or warnings
//<div> <!-- Offending div without closing tag --><script type="text/javascript">var test = '</div>';// I should not appear on the result  </script>

【讨论】:

  • 使用 loadXML 不是解决方案。如果您的 HTML 不是 100% 有效,则不会加载任何节点。尝试加载我的编辑,您会看到结果为空。
  • 我在链接 [link]simplehtmldom.sourceforge.net/[/link] 下尝试了 PHP Simple HTML DOM Parser,输出看起来不错。
  • 而且它真的很容易使用 code require_once 'libs/simplehtmldom_1_5/simple_html_dom.php'; $html =
    '; // 我不应该出现在结果中 END; $dom = str_get_html($html);回声 $dom;
  • 对于糟糕的评论格式感到抱歉。我是第一次这样做 :) 正确的 [link]simplehtmldom.sourceforge.net
  • XHTML 应该是 XML(实际上,没人关心)。常规 HTML 不是 XML,即使它是完全有效的 HTML。
  • 猜你喜欢
    • 2018-11-21
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 2012-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多