Моей первой предположения были классы 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 в первой строке текста.