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>