PHP DOMDocument: ошибки при разборке неэкранированных строк
У меня проблема при разборе HTML с PHP DOMDocument.
Разбор HMTL, который я обрабатываю, имеет следующий тег script:
<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>
Этот фрагмент имеет две проблемы:
1) HTML внутри buttonWithCountTemplate
var не экранирован. DOMDocument управляет этим правильно, избегая символов при его разборе. Не проблема.
2) В конце есть тег img с неэкранированным закрывающим тегом:
<img src="$iconImg" />
/>
заставляет DOMDocument считать, что script закончен, но ему не хватает закрывающего тега. Если вы извлечете script с помощью 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-код для удаления тегов script (на основе ответа Голизаде):
<?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 принимает теги div внутри строки JS как html вместо простой строки JS.
Что я могу сделать, чтобы решить эту проблему? Помните, что изменение HTML не является вариантом, так как я разрабатываю общий парсер.
Ответы
Ответ 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 с регулярным выражением (см. ссылку для получения дополнительной информации)
но если ваша единственная цель - удалить только теги script, и вы можете убедиться, что в ней нет тега </script>
в виде строки. вы можете использовать это регулярное выражение:
$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-код. но я думаю, что лучше попробовать другие библиотеки, связанные здесь.
В противном случае, я думаю, вы должны написать специальный синтаксический анализатор, чтобы удалить тег script и позаботиться о одиночной кавычки и двойных кавычках внутри.
Ответ 2
Я предлагаю разные варианты вашей проблемы:
Моя цель - удалить все скрипты на этой странице
то вы можете удалить их с помощью функции preg_replace_callback и проанализировать html как DOM после этого. Вот рабочая демонстрация: демо
$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, вы можете загрузить html с помощью file_get_contents()
Если вы не понимаете, как удалить теги:
preg_replace_callback позволяет выполнять поиск совпадений с regexp и преобразовывать их. В этой ситуации удалите их (return '';)
Regexp ищет начальный тег с любыми атрибутами (. *) И любым контентом между конечным тегом
Модификаторы:
U → означает неровность (возможно кратчайшее совпадение)
i → нечувствительный к регистру (также будет сопоставлен)
s → whitespace включено. (точка) characted (новая строка не нарушит совпадение)
Надеюсь, это немного разъяснит это.
Ответ 3
Вы пытались установить libxml для использования внутренних ошибок?
$use_errors = libxml_use_internal_errors(true);
// your parsing code here
libxml_clear_errors();
libxml_use_internal_errors($use_errors);
Это может позволить документу dom продолжить синтаксический анализ (возможно).
Ответ 4
Разбор html-документов в основном касается его содержимого, а не скриптов.
Эссенциально используя эти script, не зная его поведения и происхождения, может быть опасным и рискованным.
Итак, когда дело доходит до содержимого html, вы можете опустить сценарии с таким подходом (который я уже указал в комментарии):
Как объединить DOMDocument PHP с шаблоном JavaScript
Чтобы быть конкретным с вашим примером:
<?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>
Вы также можете попробовать использовать некоторые регулярные выражения для удаления тегов script перед загрузкой в DOMDocument или проверить другие библиотеки разбора html.
Наконец, вы должны понять, что в некоторых случаях даже идеальное выражение будет ломаться, а парсер DOMDocument не так хорош, как настоящий движок браузера.
Все подходит для вашего разбора и поиска лучших решений для него.
PHP Простой пример DOM Parser HTML:
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>