webew
Войти » Регистрация
 
PHP
HTML :: Инструментарий

PHP-инструментарий для автоматического выравнивания HTML-кода

17 августа 2009, 6:02

Веб-разработчикам часто приходится просматривать исходный HTML-код. Как правило, он оказывается довольно скверно оформлен, а потому трудно читаем. И хотя современные средства, такие как firebug, позволяют обойти эту проблему, иметь выровненный по вложенности и избавленный от каскадов пустых строк HTML-код всё же бывает полезно. В настоящей статье предлагается простое средство для автоматического выравнивания HTML-кода, реализованное на языке PHP.

Пример выравнивания можно увидеть, например, открыв исходный код любой страницы потрала webew.ru. Принципы выравнивания такие:

  • каждый тег переносится на новую строку, и перед ним добавляется необходимое количество отступов
  • если тег открывающий, то перед каждой строкой текста, идущего за ним, добавляется дополнительный отступ
  • если тег закрывающий, то перед ним и перед текстом, следующим за ним, добавляется на один отступ меньше
  • одиночный тег просто переносится на новую строку, не меняя текущее количество отступов
  • пустые строки (не содержащие символов, кроме пробельных) удаляются
  • переводы строки внутри самих тегов (внутри <...>) заменяются на пробелы
  • теги <textarea> и <pre> обрабатываются отдельно, т.к. содержимое этих тегов нельзя вообще никак изменять1 (оно, кстати, может порядком испортить вид результирующего HTML-кода, но с этим придется смириться)
  • тег <script> тоже стоит особняком от других: внутри него нельзя изменять содержимое строковых javascript-переменных; для простоты и ясности он обрабатыается аналогично <textarea> и <pre>

Есть возможность указать символ или последовательность символов, формирующую отступ, а также набор тегов, которые не следует обрабатывать (например, инлайновые теги, такие как <a>, <span> и др.)

Код выполнен в виде одного небольшого класса2, выровненный HTML можно получить при помощи нескольких строк:

$align = new alignedXHTML;
$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.

Далее приводится непосредственно сам код.

class alignedXHTML
    {
    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. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
NO USERPIC

msfs11

уже давно пользуюсь html tabifier
http://tools.arantius.com/tabifier
17.08.2009, 09:15
Ответить

1234ru

Писать такое на javascript'е - это жестко. PHP - язык медленный, а JS - я вообще молчу.. У меня эта штука браузер повесила на трех экранах кода. Что будет, если ей скормить десятки килобайт?
Вообще человек, конечно, молодец, что собрался и написал. Но решение практически немасштабируемо.

Плюс ко всему, не вполне очевидно, где у человека ссылка на исходный код. Как это встраивать в механизм работы своего сайта? Собирать по js-файлам из заголовков?
То, что не убивает нас, делает нас инвалидами.
17.08.2009, 09:57
Ответить

bur

Есть еще один момент, касающийся скриптов. Пример:
$html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>Untitled</title>
<script type="text/javascript">var html = "<div>DIV1</div>";</script>
</head><body></body></html>'
;

После вызова ->parse($html) получим:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>
            Untitled
        </title>
        <script type="text/javascript">
            var html = "
            <div>
                DIV1
            </div>
                ";
        </script>
    </head>
    <body>
    </body>
</html>

Текстовая переменная html в JavaScript-сценарии содержала html-код, который также был обработан, что привело к ошибке скрипта (unterminated string literal).
Лучше всего не парсить содержимое тэга script, также, как и содержимое textarea.
17.08.2009, 11:43
Ответить

1234ru

Да, я с таким сталкивался, пока не поставил фильтр на mimetype, чтобы выравнивались только те страницы, у которых text/html.
Вообще я надеялся, что прямо в тексте страницы javascript-кода не будет (считал, что надо переносить в отдельные файлы, а в текстах страницы только по-минимуму).
Впрочем, это ограничение искусственное, поэтому действительно сделаю, чтобы обрабатывалось отдельно.

Вместе с тем, в отличие от <textarea>, фактически нельзя трогать не всё содержимое тегов <script>, а только то, что в переменных. Так я и собираюсь сделать.
Поэтому у меня к тебе вопрос (а то я уже забыл и вообще нетвердо знаю): содержимое переменных - оно где? В двойных и одинарных кавычках? Есть еще где-нибудь?
То, что не убивает нас, делает нас инвалидами.
17.08.2009, 15:12
Ответить

bur

Надежнее всего не трогать всё содержимое SCRIPT, зачем тебе там что-то парсить?
Точнее, для содержимого тэга SCRIPT можно свой парсер написать, который по фигурным скобкам ходить будет, но это уже другая история.

1234кг
Поэтому у меня к тебе вопрос (а то я уже забыл и вообще нетвердо знаю): содержимое переменных - оно где? В двойных и одинарных кавычках?


Строковые переменные (другие тебя интересовать не должны) заключаются в одиночные или двойные кавычки. Внутри них могут быть переносы строк, экранированные слешом.
17.08.2009, 15:20
Ответить

alik

Цитата:
тег <textarea> обрабатываем отдельно: его содержимое нельзя никак изменять

Кроме <textarea>, нельзя также форматировать содержимое тега <pre>. Если Вы согласны, дополните, пожалуйста.
18.08.2009, 00:42
Ответить

1234ru

Цитата:
Надежнее всего не трогать всё содержимое SCRIPT, зачем тебе там что-то парсить?

Ну, не трогать - это неспортивно. :)

С другой стороны, как ни выкручивайся, все равно он теоретически может испортить вид кода. Поэтому действительно трогать его не буду (по крайней мере, пока) и присовокуплю его обработку к <textarea>.
В статью внес изменения.
То, что не убивает нас, делает нас инвалидами.
18.08.2009, 00:42
Ответить

1234ru

Спасибо, дополнил статью.
То, что не убивает нас, делает нас инвалидами.
18.08.2009, 01:22
Ответить
NO USERPIC

Zyklon

Подскажите плиз кто-нибудь!
У меня проблемка с сайтом http://vseimena.net/ дело в том, что хотел проверить код на валидность а он пишет типо проверить не возможно вот дословно Sorry! This document can not be checked.
Че делать не пойму как теперь ошибки искать ??
Если кто может помогите вот мой ящик forsakenfromhell@ukr.net
всем заранее спасибо
14.11.2010, 20:34
Ответить

1234ru

Вообще это к предмету обсуждения здесь не относится. В следующий раз заводите отдельную тему.

У вас код счетчика liveinternet сохранен в кодировке CP-1251. Сохраните его каким-либо способом в UTF-8 (например, откройте редактором и смените кодировку файла там).


То, что не убивает нас, делает нас инвалидами.
14.11.2010, 23:28
Ответить
NO USERPIC

Zyklon

Спасибо за помощь просто спрашивал уже на форуме так никто и не помог.
Сейчас попробую сделать!
15.11.2010, 09:58
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2008—2024 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100

Реклама: