Моей первой предположения были классы 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><html></span> <span><head></span> <span><title></span> Page Title <span></title></span> <span></head></span> <span></html></span> </code></pre> </div> </div> </body> </html>'; header('Content-Type: text/plain'); libxml_use_internal_errors(TRUE); $dom = new DOMDocument; $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadHTML($html); print $dom->saveHTML();
Обновление: я добавил в пример предварительно форматированный блок кода.
Вот некоторые улучшения по поводу ответа @hijarian:
Если вы не вызываете 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() метода loadHTML() . К сожалению, по неизвестным мне причинам это все еще оставляет пару ошибок.
Не забывайте, что DOMDocument всегда выводит ошибку (даже при использовании внутренних ошибок libxml и установке libxml флагов), если вы передадите пустую (или пустую ) строку методам load*() .
Регулярное выражение />\s*</im не имеет большого смысла, лучше использовать ~>[[:space:]]++<~m также catch \v (вертикальные вкладки) и заменять только если на самом деле существуют пространства ( + вместо * ), не возвращая ( ++ ) – что быстрее – и отбрасывать служебные данные insensitve (так как пробелы не имеют случая).
Вы также можете нормализовать новые строки для \n и других управляющих символов (особенно если источник HTML неизвестен), так как a \r вернется как  после saveXML() например.
DOMDocument::$preserveWhitespace бесполезно и ненужно после запуска указанного выше регулярного выражения.
О, и я не вижу необходимости защищать пустые пре-подобные теги здесь. Простые пробелы являются бесполезными.
loadHTML() LIBXML_COMPACT – «это может ускорить ваше приложение без необходимости изменения кода» LIBXML_NOBLANKS – нужно выполнить больше тестов на этом LIBXML_NOCDATA – нужно выполнить больше тестов на этом LIBXML_NOXMLDECL – документально, но не реализовано = ( UPDATE: установка любого из этих параметров приведет к не форматированию вывода.
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 имеет две основные проблемы:
$dom->documentElement ) <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-теги всегда сглажены. UPDATE: Теги всегда имеют нижнее значение.
<script> и <style> Эти теги всегда будут иметь свой контент (если он существует), инкапсулированный в (uncommented) блоки 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; }
Вот комментарий на 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 в первой строке текста.