PHP-инструментарий для автоматического выравнивания HTML-кода
Веб-разработчикам часто приходится просматривать исходный HTML-код. Как правило, он оказывается довольно скверно оформлен, а потому трудно читаем. И хотя современные средства, такие как firebug, позволяют обойти эту проблему, иметь выровненный по вложенности и избавленный от каскадов пустых строк HTML-код всё же бывает полезно. В настоящей статье предлагается простое средство для автоматического выравнивания HTML-кода, реализованное на языке PHP.
Пример выравнивания можно увидеть, например, открыв исходный код любой страницы потрала webew.ru. Принципы выравнивания такие:
- каждый тег переносится на новую строку, и перед ним добавляется необходимое количество отступов
- если тег открывающий, то перед каждой строкой текста, идущего за ним, добавляется дополнительный отступ
- если тег закрывающий, то перед ним и перед текстом, следующим за ним, добавляется на один отступ меньше
- одиночный тег просто переносится на новую строку, не меняя текущее количество отступов
- пустые строки (не содержащие символов, кроме пробельных) удаляются
- переводы строки внутри самих тегов (внутри <...>) заменяются на пробелы
- теги <textarea> и <pre> обрабатываются отдельно, т.к. содержимое этих тегов нельзя вообще никак изменять1 (оно, кстати, может порядком испортить вид результирующего HTML-кода, но с этим придется смириться)
- тег <script> тоже стоит особняком от других: внутри него нельзя изменять содержимое строковых javascript-переменных; для простоты и ясности он обрабатыается аналогично <textarea> и <pre>
Есть возможность указать символ или последовательность символов, формирующую отступ, а также набор тегов, которые не следует обрабатывать (например, инлайновые теги, такие как <a>, <span> и др.)
Код выполнен в виде одного небольшого класса2, выровненный HTML можно получить при помощи нескольких строк:
$align->SPACER = " "; // устанавливаем отступ по два пробела
$align->SKIPTAGS = array('a', 'img', 'span', 'sup'); // эти теги трогать не будем
// допустим, ранее сформированный HTML-код хранится в переменной $html
$aligned_html = $align->parse($html); // получили выровненный HTML
Класс будет правильно работать при соблюдении следующих условий:
- количество открывающих тегов равно количеству закрывающих (т.е. код корректный)
- одиночный тег всегда имеет слэш прямо перед закрывающей скобкой (<br/> или <br />, но не <br>)
Последнее требование является частью стандарта XHTML, поэтому в итоге класс получил название alignedXHTML.
Основным недостатком рассматриваемого способа является его сравнительно низкое быстродействие: обработка 100 Кб HTML-кода на сервере, обслуживающем webew.ru, занимает порядка 0.05 сек. Видимо, упирается здесь всё в низкую производительность PHP (сам код подвергался достаточно тщательной оптимизации), и тут уж ничего не поделаешь3.
Далее приводится непосредственно сам код.
{
public $SPACER;
public $OFFSET;
public $SKIPTAGS;
function parse($xhtml)
{
if (is_null($this->SPACER)) {$this->SPACER = "\t"; }
if (is_null($this->OFFSET)) {$this->OFFSET = 0; }
if (is_null($this->SKIPTAGS)) {
$this->SKIPTAGS = array('a', 'span', 'img', 'sup', 'sub');
}
/*
Теги <textarea>, <pre> и <script> - особенные, и с ними
придется попотеть.
Нужно защитить содержимое этих тегов от вмешательства
при выравнивании: убрать на время, во-первых, переводы строки,
во-вторых, HTML-теги, которые могут встретиться внутри
строковых переменных в скриптах
(точнее, не сами теги, а открывающие и закрывающие скобки).
*/
$xhtml = str_replace(array("\x01", "\x02", "\x03"), '', $xhtml);
$xhtml = preg_replace_callback(
'/
(<(textarea|script|pre)(?:[^>"\']*|"[^"]*"|\'[^\']*\')*>)
(.*?)
(<\/\2>)
/six',
// модификатор 's' не забываем: точка тут должна совпадать
// и с символом новой строки; модификатор 'x' позволяет добавлять в шаблон
// необрабатываемые пробелы и переводы строки, чтобы он лучше читался
create_function(
'$matches',
'$tagbody = $matches[3];
$tagbody = str_replace("\n", "\x01", $tagbody);
$tagbody = str_replace("<", "\x02", $tagbody);
$tagbody = str_replace(">", "\x03", $tagbody);
return $matches[1] . $tagbody . $matches[4];'
),
$xhtml);
// регулярное выражение для HTML-тега
// (модификатор s не нужен, т.к. точки в выражении нет)
$tagpattern = '/<(\/?)(\w+)(?:[^>"\']*|"[^"]*"|\'[^\']*\')*>/';
// убираем переводы строки внутри тегов (заменяем на пробелы)
$xhtml = preg_replace_callback(
$tagpattern,
create_function(
'$matches',
'return str_replace("\n", " ", $matches[0]);'
),
$xhtml);
// теперь обрабатыавем XHTML-код по одной строке
// (PHP это не умеет, поэтому пришлось вручную)
$start = 0;
$final_xhtml = '';
do
{
$end = strpos($xhtml, "\n", $start);
$line = ($end !== FALSE)
? substr($xhtml, $start, $end - ($start - 1) )
: substr($xhtml, $start);
$line = ltrim($line); // убираем ведущие пробелы, чтоб не мешали выравнивать
$final_xhtml .=
str_repeat($this->SPACER, $this->OFFSET) .
preg_replace_callback(
$tagpattern,
array($this, 'alignXHTMLtags'),
$line);
$start = $end + 1;
}
while ($end !== FALSE);
// убираем пустые строки
$final_xhtml = preg_replace('/\n\s*(?=\n)/m', '', $final_xhtml);
// возвращаем обратно содержимое <textarea>, <pre> и <script>
$final_xhtml = str_replace("\x01", "\n", $final_xhtml);
$final_xhtml = str_replace("\x02", "<", $final_xhtml);
$final_xhtml = str_replace("\x03", ">", $final_xhtml);
return $final_xhtml;
}
function alignXHTMLtags($matches)
{
$tag = $matches[0];
$tagname = $matches[2];
if (in_array($tagname, $this->SKIPTAGS)) return $tag;
$opening = FALSE;
if ($matches[1]) { $this->OFFSET -= 1; } // тег является закрывающим
elseif (substr($tag, -2, 1) == '/') { ; } // тег является одиночным
else { $opening = TRUE; } // если тег не является ни одиночным, ни закрывающим, значит, он открывающий
if ($tagname == 'textarea' OR $tagname == 'pre' OR $tagname == 'script')
{ // эти теги вообще не трогаем, просто перенесем их
// полностью (со всем содержимым) на новую строку
if ($opening) { $replacement = "\n" . $tag; }
else $replacement = $tag . "\n";
}
else
{
$replacement = "\n"
. str_repeat($this->SPACER, $this->OFFSET) . $tag . "\n"
. str_repeat($this->SPACER, $this->OFFSET + 1);
}
if ($opening) { $this->OFFSET += 1; }
return $replacement;
}
}
1.
Это касается случая, когда внутри тегов <textarea> имеются переводы строки. Если при обработке кода никак их не защитить, то в содержимое тега добавятся символы, используемые для выравнивания кода, что приведет к искажению данных (по этой же причине нельзя и просто отмахнуться от переводов строки, заменив их на пробелы — для этого все манипуляции с непечатаемыми символами).
С тегами <pre> и <script> ситуация еще сложнее: в их содержимом помимо переводов строки могут встретиться еще и HTML-теги, которые будут выравниваться наряду с остальным HTML-кодом, если это специально не предотвратить (например, заменив скобки тегов на что-нибудь на время выравнивания, а потом вернуть их обратно).
Эта проблема также решается с помощью непечатаемых символов.⇑
2. Писать через классы пришлось из-за того, что в callback-функцию, вызываемую в preg_replace_callback(), невозможно нормальным способом передать никакие аргументы, кроме собственно массива совпадений, и уж тем более никак нельзя получить их обратно. ⇑
3. Из этих 0.05 сек 60% составляет непосредственно выравнивание тегов, 35% — замена переводов строки на пробелы внутри тегов и около 5% — удаление пустых строк.
Если в коде встречаются <textarea>, <pre> и <script>, время работы возрастает еще на несколько процентов.
Работу кода можно ускорить почти в три раза, если использовать для внутренности тегов выражения /<(\/?)(\w+)(?:[^>"\']*|"[^"]*"|\'[^\']*\')*>/ более «грязное» — /<(\/?)(\w+)[^>]*>/. Дополнительный набор альтернатив требуется для случая, когда внутри одинарных или двойных кавычек в теге встретится закрывающая скоба (например, <img alt="3 > 2 - верно!">). В реальной жизни так практически никогда не бывает, и те, кто готов на это положиться, могут использовать второе выражение вместо первого.
⇑
© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.