#php #html #dom
#php #HTML #dom
Вопрос:
Моим первым предположением были классы 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>amp;<htmlamp;></span>
<span>amp;<headamp;></span>
<span>amp;<titleamp;></span>
Page Title
<span>amp;</titleamp;></span>
<span>amp;</headamp;></span>
<span>amp;</htmlamp;></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();
Обновление: я добавил в пример предварительно отформатированный блок кода.
Комментарии:
1. Для таких крупных блоков текста, как этот, вы должны использовать HEREDOC вместо многострочной строки.
2. bugs.php.net/bug.php?id=48509
3. @MarcB, если вы посмотрите на редакции post, вы увидите, что сначала это был HEREDOC. Однако markdown не может форматировать строки HEREDOC. Итак, для вашего удобства теперь это многострочная строка. Как будто у кого-то все равно будет длинная строка HTML в их PHP-файле … : P
4. Удалите все отступы и ненужные закрывающие теги. Наличие красивого html с отступами абсолютно бесполезно для конечного пользователя (на самом деле это плохо, потому что вы тратите впустую пропускную способность). Просто проверьте источник в Google или Facebook.
5. При вызове
loadHTML
вы также должны использоватьLIBXML_NOERROR | LIBXML_NOWARNING
флаги, чтобы избежать заполнения стека ошибок и потребления вашей оперативной памяти. Либо это, либо вызовитеlibxml_clear_errors()
после.
Ответ №1:
Вот некоторые улучшения по сравнению с ответом @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
ошибок и установке флагов подавления), если вы передаете пустую (или пустую) строку load*()
методам.
Регулярное выражение
Регулярное выражение />s*</im
не имеет большого смысла, его лучше использовать ~>[[:space:]] <~m
также для перехвата v
(вертикальные вкладки) и замены только в том случае, если пробелы действительно существуют (
вместо *
), не возвращая (
) — что быстрее — и отбрасывать накладные расходы без учета регистра (поскольку пробелы не имеют регистра).
Вы также можете захотеть нормализовать новые строки n
и другие управляющие символы (особенно, если происхождение HTML неизвестно), поскольку a r
вернется amp;#23;
saveXML()
, например, как after .
DOMDocument::$preserveWhitespace
бесполезно и не нужно после выполнения приведенного выше регулярного выражения.
О, и я не вижу необходимости защищать здесь пустые теги pre-like. Фрагменты, содержащие только пробелы, бесполезны.
Дополнительные флаги для loadHTML()
LIBXML_COMPACT
— «это может ускорить работу вашего приложения без необходимости изменения кода»LIBXML_NOBLANKS
— нужно запустить больше тестов на этомLIBXML_NOCDATA
— нужно запустить больше тестов на этомLIBXML_NOXMLDECL
— документировано, но не реализовано = (
ОБНОВЛЕНИЕ: установка любого из этих параметров приведет к тому, что выходные данные не будут форматироваться.
На 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 теперь имеет две основные проблемы:
- нет DOCTYPE (он был удален при использовании
$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 всегда в нижнем регистре. ОБНОВЛЕНИЕ: теги всегда в нижнем регистре.
On <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) amp;amp; ($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;
}
Комментарии:
1. Просто интересно, касается ли ваш ответ XML или HTML или обоих? Вы пишете об обработке XML-файлов и последующем преобразовании их в HTML? Так ли это должно быть сделано?
Ответ №2:
Вот комментарий на 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-объявление в первой строке текста.
Комментарии:
1. Это хорошее начало, использование вывода XML действительно помогает. Однако это уничтожает отступ, используемый в предварительно отформатированном блоке кода. Тем не менее, возможно, я могу просто изменить ваше регулярное выражение, чтобы не включать
<span>
теги, которые обычно размещаются вокруг кода в<pre>
блоках. Тем не менее, я хотел бы знать, почему библиотека PHP DOM не работает. Редактировать: Да,'/>s*<(?!span)/i'
кажется, работает нормально.2. @Xeoncross: Дэвид,
>s*<
регулярное выражение должно обрезаться только в том случае, если блок кода pre-like полностью состоит из символов-интервалов. Если это так, я не понимаю, зачем вам нужно сохранять избыточное пустое пространство. Чего мне здесь не хватает?3. Почему formatOutput больше не
saveHTML()
работает? Работает ли это с файлами?