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

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

17 августа 2009, 6:02
Автор: Михаил Серов [1234ru]

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

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

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

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

$align = new alignedXHTML;
$align->SPACER = "  "; // устанавливаем отступ по два пробела
$align->SKIPTAGS = array('a', 'img', 'span', 'sup'); // эти теги трогать не будем
// допустим, ранее сформированный HTML-код хранится в переменной $html
$aligned_html = $align->parse($html); // получили выровненный HTML

Класс будет правильно работать при соблюдении следующих условий:

Последнее требование является частью стандарта 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.
Fastcoder.org — портал для JavaScrpt-программистов
17.08.2009, 11:43
Ответить

1234ru

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

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

bur

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

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


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

1234ru

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

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

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

alik

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

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

1234ru

Спасибо, дополнил статью.
То, что не убивает нас, делает нас инвалидами.
18.08.2009, 01:22
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2007—2010 webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100