Как вы форматируете структуры DOM в PHP? - PullRequest
7 голосов
/ 03 ноября 2011

Моим первым предположением были классы PHP DOM (с параметром formatOutput ). Тем не менее, я не могу получить этот блок HTML для форматирования и вывода правильно. Как видите, отступ и выравнивание неверны.

$html = '
<html>
<body>
<div>

<div>

        <div>

                <p>My Last paragraph</p>
            <div>
                            This is another text block and some other stuff.<br><br>
                Again we will start a new paragraph
                            and some other stuff
                            <br>
        </div>
</div>
        <div>
                        <div>
                            <h1>Another Title</h1>
                                                    </div>
                        <p>Some text again <b>for sure</b></p>
                </div>
</div>
<div>
    <pre><code>
    <span>&lt;html&gt;</span>
        <span>&lt;head&gt;</span>
            <span>&lt;title&gt;</span>
                Page Title
            <span>&lt;/title&gt;</span>
            <span>&lt;/head&gt;</span>
    <span>&lt;/html&gt;</span>
    
'; заголовок ('Content-Type: text / plain'); libxml_use_internal_errors (TRUE); $ dom = новый DOMDocument; $ dom-> preserveWhiteSpace = false; $ dom-> formatOutput = true; $ Dom-> loadHTML ($ HTML); print $ dom-> saveHTML ();

Обновление: я добавил в пример предварительно отформатированный кодовый блок.

Ответы [ 2 ]

7 голосов
/ 17 июня 2013

Вот некоторые улучшения по сравнению с ответом @hijarian:

Ошибки LibXML

Если вы не вызовете libxml_use_internal_errors(true), PHP выведет все найденные ошибки HTML.Однако, если вы вызовете эту функцию, ошибки не будут подавлены, вместо этого они попадут в кучу, которую вы можете проверить, вызвав libxml_get_errors().Проблема в том, что он ест память, а DOMDocument, как известно, очень требователен.Если вы обрабатываете много файлов в пакетном режиме, вам в конечном итоге не хватит памяти.Для этого есть два решения:

if (libxml_use_internal_errors(true) === true)
{
    libxml_clear_errors();
}

Поскольку libxml_use_internal_errors(true) возвращает предыдущее значение этого параметра (по умолчанию false), это приводит к очистке только ошибок, если вы запускаете его более одного раза (как при пакетной обработке).

Другой вариант - передать флаги LIBXML_NOERROR | LIBXML_NOWARNING методу loadHTML().К сожалению, по причинам, которые мне неизвестны, это все же оставляет пару ошибок позади.

Учтите, что DOMDocument всегда будет выводить ошибку (даже при использовании внутренних ошибок libxml и установке флагов подавления)если вы передаете пустую (или blankish ) строку в методы load*().

Regex

Регулярное выражение />\s*</im не имеет большого смысла, лучше использовать ~>[[:space:]]++<~m, чтобы также поймать \v (вертикальные табуляции) и заменять, только если пробелы действительно существуют (+ вместо *) без возврата (++) - что быстрее - иотбрасывать накладные расходы без учета регистра (поскольку пробел не имеет регистра).

Возможно, вы также захотите нормализовать символы новой строки до \n и других управляющих символов (особенно, если происхождение HTML неизвестно), поскольку \r вернется как &#23; после saveXML(), например.

DOMDocument::$preserveWhitespace бесполезно и не нужно после выполнения приведенного выше регулярного выражения.

О, и я не вижу необходимостидля защиты бланка предварительноАйк теги здесь.Фрагменты только для пробелов бесполезны.

Дополнительные Флаги для loadHTML()

  • LIBXML_COMPACT - "это может ускорить работу вашего приложения без необходимости изменятькод "
  • LIBXML_NOBLANKS - необходимо выполнить больше тестов на этом
  • LIBXML_NOCDATA - необходимо запустить больше тестов на этом
  • LIBXML_NOXMLDECL - задокументировано, но не реализовано = (

ОБНОВЛЕНИЕ: Установка любого из этих параметров приведет к отсутствию форматированияoutput.

On saveXML()

Метод DOMDocument::saveXML() выведет декларацию XML. Нам нужно вручную удалить ее (поскольку LIBXML_NOXMLDECL не реализовано). Для этого, мы могли бы использовать комбинацию substr() + strpos() для поиска первого разрыва строки или даже использовать регулярное выражение для его очистки.

Другой вариант, который, кажется, имеет дополнительное преимущество :просто сделав:

$dom->saveXML($dom->documentElement);

Другое дело, если у вас есть встроенные теги пустые, такие как b, i или li в:

<b class="carret"></b>
<i class="icon-dashboard"></i> Dashboard
<li class="divider"></li>

Метод saveXML() будет серьезно искажать их (помещая следующий элемент в пустой), портя весь ваш HTML.У Tidy также есть похожая проблема, за исключением того, что он просто отбрасывает узел.

Чтобы исправить это, вы можете использовать флаг LIBXML_NOEMPTYTAG вместе с saveXML():

$dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);

Эта опцияпреобразует пустые (то есть самозакрывающиеся) теги во встроенные теги, а также разрешает использовать пустые встроенные теги.

Исправление HTML [5]

Со всем, что мы делали до сих пор, наш вывод HTMLтеперь есть две основные проблемы:

  1. без DOCTYPE (он был удален, когда мы использовали $dom->documentElement)
  2. пустые теги теперь являются встроенными тегами, то есть один <br /> превращается в два (<br></br>) и т. Д.

Исправить первый довольно легко, поскольку HTML5 довольно разрешительный:

"<!DOCTYPE html>\n" . $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG);

Чтобы вернуть наши пустые теги, а именно:

  • area
  • base
  • basefont ( устарело в HTML5 )
  • br
  • col
  • command
  • embed
  • frame ( устарело в HTML5 )
  • hr
  • img
  • input
  • keygen
  • link
  • meta
  • param
  • source
  • track
  • wbr

Мы можем использовать str_[i]replace в цикле:

foreach (explode('|', 'area|base|basefont|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr') as $tag)
{
    $html = str_ireplace('>/<' . $tag . '>', ' />', $html);
}

Или регулярное выражение:

$html = preg_replace('~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>\b~i', '/>', $html);

Это дорогостоящая операция,Я не тестировал их, поэтому не могу сказать, какая из них лучше, но я бы предположил preg_replace().Кроме того, я не уверен, нужна ли версия без учета регистра.У меня сложилось впечатление, что теги XML всегда в нижнем регистре. ОБНОВЛЕНИЕ: Теги всегда в нижнем регистре.

Вкл. <script> и <style> Теги

Эти теги всегда будут иметь свое содержимое (если оно существует), инкапсулированное в (без комментариев)Блоки CDATA, которые, вероятно, нарушат их значение.Вам придется заменить эти токены регулярным выражением.

Реализация

function DOM_Tidy($html)
{
    $dom = new \DOMDocument();

    if (libxml_use_internal_errors(true) === true)
    {
        libxml_clear_errors();
    }

    $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
    $html = preg_replace(array('~\R~u', '~>[[:space:]]++<~m'), array("\n", '><'), $html);

    if ((empty($html) !== true) && ($dom->loadHTML($html) === true))
    {
        $dom->formatOutput = true;

        if (($html = $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG)) !== false)
        {
            $regex = array
            (
                '~' . preg_quote('<![CDATA[', '~') . '~' => '',
                '~' . preg_quote(']]>', '~') . '~' => '',
                '~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>~' => ' />',
            );

            return '<!DOCTYPE html>' . "\n" . preg_replace(array_keys($regex), $regex, $html);
        }
    }

    return false;
}
5 голосов
/ 04 ноября 2011

Вот комментарий на php.net: http://ru2.php.net/manual/en/domdocument.save.php#88630

Похоже, когда вы загружаете HTML из строки (как вы это сделали), DOMDocument становится ленивым и ничего не форматирует в нем.

Вот рабочее решение вашей проблемы:

// Clean your HTML by hand first
$html = preg_replace('/>\s*</im', '><', $html);
$dom = new DOMDocument;
$dom->loadHTML($html);
$dom->formatOutput = true;
$dom->preserveWhitespace = false;
// Use saveXML(), not saveHTML()
print $dom->saveXML();

По сути, вы выбрасываете пробелы между тегами и используете saveXML () вместо saveHTML ().saveHTML () просто не работает в этой ситуации.Однако вы получаете объявление XML в первой строке текста.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...