автор Guido Socher (homepage)
Об авторе:
Guido нравится Perl за его мощь и скорость. Девиз Perl
"Есть более одного способа сделать это" - полностью отражает
возможности тех, кто пользуется свободно распространяемым
программным обеспечением.
Перевод на Русский:
Pukhlyakov Kirill <kirill(at)linuxfocus.org>
Содержание:
|
Обработка HTML кода на Perl, HTML::TagReader
Резюме:
Если ваш сайт достаточно объемен, то рано или поздно приходит мысль
об автоматизации управления им, то есть о поиске какого-либо
инструмента или приложения.
Большинство приложений работают с файлами построчно или посимвольно.
К сожалению строки не имеют никакого смысла в SGML/XML/HTML файлах.
Эти файлы основаны на тэгах. Модуль, о котором пойдет речь
в этой заметке, обрабатывает файлы по тэгам.
Надеюсь, что вы знакомы с Perl достаточно хорошо. В любом случае
советую взглянуть на мои предыдущие заметки об этом
языке (Январь 2000).
_________________ _________________ _________________
Вступление
Обычно файлы имеют строковую структуру - например конфигурационные
файлы Unix - /etc/hosts, /etc/passwd ... Даже есть старые ОС,
в которых вы можете найти функции для построчного чтения/записи данных.
Интересующие нас SGML/XML/HTML файлы основаны на тэгах, строки не имеют
никакого значения в них, но текстовые редакторы и люди до сих пор
ориентируются на строки.
Например большие HTML файлы, возвращаемые сервером могут иметь большую
длину строк и тут мы можем сделать их более читаемыми например таким
инструментом как "Tidy". Мы используем строковую структуру, несмотря на
то, что HTML основан на тэгах. Вы можете сравнить это с С-кодом -
теоретически можно писать программу в одну строку, но так никто не делает -
код будет нечитаемым.
Но с другой стороны любой инструмент для проверки HTML кода выдаст вам
сообщение об ошибке на основе отсчета строк, но не тэгов - вы наверное
не видели никогда такое сообщение как "ОШИБКА после тэга 4123". Это потому,
что ваш текстовый редактор легко перейдет на заданную вами строку.
Идеальным инструментом был бы тот, который
обрабатывает HTML файл тэг
за тэгом и при этом запоминает еще номера строк.
Возможное решение
Обычно для чтения файла в Perl используют оператор
while(<FILEHANDLE>). В результате файл будет читаться
построчно и очередная строка будет помещена в переменную $_.
Почему так делает Perl? Дело в том, что в Perl есть служебная переменная
INPUT_RECORD_SEPARATOR ($RS или $/), в которой определяется символ "\n"
как конец строки. Если вы определите $/=">", то Perl будет считать
">" концом строки. Следующий однострочный Perl скрипт преобразует
html текст таким образом, что каждая строка будет заканчиваться на ">":
perl -ne 'sub BEGIN{$/=">";} s/\s+/ /g; print
"$_\n";' file.html
такой html файл
<html><p>some text here</p></html>
преобразуется к такому виду
<html>
<p>
some text here</p>
</html>
Но это по-прежнему не очень читаемо. Для разработчика важно, чтобы
данные передавались тэг за тэгом. Чтобы, например, можно было легко
найти "<a href= ..." даже если "a" и "href" расположены на разных
строках.
Замена переменной "$/" (INPUT_RECORD_SEPARATOR) не замедлит работу
приложения. Также можно воспользоваться регулярными выражениями -
это немного сложнее и может привести к замедлению приложения, но этот
способ широко используется.
В чем проблема?? В заголовке заметки говорится что-то про
HTML::TagReader, но я почему-то до сих пор говорю о каких-то других
способах без привлечения дополнительных модулей. Посмотрим где тут
может возникнуть проблема:
- Можно сказать, что почти все HTML файлы в мире некорректны.
Есть достаточно много файлов с примерами на "С", которые выглядят
на HTML уровне так
if ( limit > 3) ....
вместо
if ( limit > 3) ....
В HTML открывающий тэг должен быть "<", а закрывающий ">".
Ни один из них не должен появляться как есть в тексте. Хотя
многие браузеры отобразят их корректно, но ошибка от этого не
исчезнет.
- Замена "$/" конечно отразится на всей программе. И если вы
разберете такой файл, то возникнут проблемы.
Так что делаем вывод, что только в некоторых случаях можно использовать
переменную "$/" (INPUT_RECORD_SEPARATOR).
У меня есть одна программка, которая использует то, что мы уже так
долго обсуждаем. В ней меняется "$/" на "<". Браузеры не так хорошо
отслеживают некорректный "<", как ">", поэтому гораздо меньше
некорректных страниц с "<", чем с ">".
Программа называется tr_tagcontentgrep
( посмотреть ), вы можете по коду посмотреть как запоминать номер
строки. Это программа может быть использована для "grep'а" строки
( например "img" ) в тэге даже если тэг расположен на разных строках,
например:
tr_tagcontentgrep -l img file.html
index.html:53: <IMG src="../images/transpix.gif" alt="">
index.html:257: <IMG SRC="../Logo.gif" width=128
height=53>
HTML::TagReader
HTML::TagReader решает проблему с заменой INPUT_RECORD_SEPARATOR, а
также предлагает более красивый способ отделения текста от тэгов.
Он не так тяжеловесен как HTML::Parser и предлагает именно то, что
надо при обработке html кода - возможность чтения тэг за тэгом.
Достаточно разговоров. Посмотрим как использовать модуль в реальной
жизни. Сначала объявляем его:
use HTML::TagReader;
Затем:
my $p=new HTML::TagReader "filename";
чтобы открыть файл и получить объектную ссылку на него в $p. Теперь
используя $p->gettag(0) или $p->getbytoken(0) получаем доступ
к следующему тэгу. gettag возвращает только названия тэгов, т.е. то,
что находится между < и >, а getbytoken также возвращает текст,
который присутствует между тэгами и сообщает вам, что это - текст или
тэг. Используя эти функции очень легко обрабатывать html файлы, что вообщем
и необходимо при поддержке больших сайтов. Полное описание синтаксиса
вы можете найти здесь (man страница
HTML::TagReader.
Приведем реальный пример использования модуля - выведем заголовки
некоторого количества документов:
#!/usr/bin/perl -w
use strict;
use HTML::TagReader;
#
die "USAGE: htmltitle file.html [file2.html...]\n" unless($ARGV[0]);
my $printnow=0;
my ($tagOrText,$tagtype,$linenumber,$column);
#
for my $file (@ARGV){
my $p=new HTML::TagReader "$file";
# read the file with getbytoken:
while(($tagOrText,$tagtype,$linenumber,$column) = $p->getbytoken(0)){
if ($tagtype eq "title"){
$printnow=1;
print "${file}:${linenumber}:${column}: ";
next;
}
next unless($printnow);
if ($tagtype eq "/title" || $tagtype eq "/head" ){
$printnow=0;
print "\n";
next;
}
$tagOrText=~s/\s+/ /; #kill newline, double space and tabs
print $tagOrText;
}
}
# vim: set sw=4 ts=4 si et:
Как это работает? Читая html файл тэг за тэгом при помощи
$p->getbytoken(0) мы ищем <title> или <Title>
или <TITLE> ( $tagtype eq "title" ) и когда находим
устанавливаем флаг ( $printnow ) и начинаем выводить заголовок пока
не встретим </title>.
Используем следующий вызов программы:
htmltitle file.html somedir/index.html
file.html:4: the cool perl page
somedir/index.html:9: joe's homepage
Конечно можно использовать и tr_tagcontentgrep вместе с
HTML::TagReader. Тогда получится немного короче и проще:
#!/usr/bin/perl -w
use HTML::TagReader;
die "USAGE: taggrep.pl searchexpr file.html\n" unless ($ARGV[1]);
my $expression = shift;
my @tag;
for my $file (@ARGV){
my $p=new HTML::TagReader "$file";
while(@tag = $p->gettag(0)){
# $tag[0] is the tag (e.g <a href=...>)
# $tag[1]=linenumber $tag[2]=column
if ($tag[0]=~/$expression/io){
print "$file:$tag[1]:$tag[2]: $tag[0]\n";
}
}
}
Скрипт получился достаточно коротким, без избытка обработки ошибок,
но он полностью функционален. Чтобы найти тэги с вхождением строки
"gif" используем его следующим образом:
taggrep.pl gif file.html
file.html:135:15: <img src="images/2doc.gif" width=34
height=22>
file.html:140:1: <img src="images/tst.gif" height="164"
width="173">
Хотите еще пример? Напишем программу, которая вырежет все тэги
<font...> и </font> из html файла. Эти тэги ( font )
используются в огромном количестве некачественными приложениями
для верстки html кода и потом возникают проблемы при просмотре
таких файлов браузерами. Это упрощенная версия программы - она
удаляет все тэги font, но вы можете немного усовершенствовать ее
и удалять только аттрибуты fontface или size, а color оставлять.
#!/usr/bin/perl -w
use strict;
use HTML::TagReader;
# strip all font tags from html code but leave the rest of the
# code un-changed.
die "USAGE: delfont file.html > newfile.html\n" unless ($ARGV[0]);
my $file = $ARGV[0];
my ($tagOrText,$tagtype,$linenumber,$column);
#
my $p=new HTML::TagReader "$file";
# read the file with getbytoken:
while(($tagOrText,$tagtype,$linenumber,$column) = $p->getbytoken(0)){
if ($tagtype eq "font" || $tagtype eq "/font"){
print STDERR "${file}:${linenumber}:${column}: deleting $tagtype\n";
next;
}
print $tagOrText;
}
# vim: set sw=4 ts=4 si et:
Как видите достаточно легко написать полезную программу всего в
несколько строк.
В пакете с исходниками HTML::TagReader ( см. ссылки ) приведены
несколько примеров использования модуля.
- tr_blck -- поиск некорректных относительных ссылок в HTML страницах
- tr_llnk -- вывод списка ссылок в HTML файлах
- tr_xlnk -- замена ссылок на каталоги ссылками на index файлы
- tr_mvlnk -- замена тэгов в HTML файлах командами perl.
- tr_staticssi -- замена SSI директив #include virtual и
#exec cmd на статические html страницы.
- tr_imgaddsize -- добавление width=... и height=... в тэг <img
src=...>
tr_xlnk и tr_staticssi полезны при копировании веб-сайта на CD. Веб сервер
выдаст вам страницу http://www.linuxfocus.org/index.html даже если вы просто
наберете http://www.linuxfocus.org/ ( без index.html ). Если в случае копирования
сайта на диск вы просто переносите все файлы и потом обращаетесь к
диску напрямую ( file:/mnt/cdrom ) то вы увидите просто список файлов
каталога вместо index.html. Это произошло в первый раз при создании
Linux
Focus CD - компания выполнявшая заказ так и сделала и пользоваться
диском стало очень неудобно. Теперь они используют tr_xlnk и все ОК.
Уверен, что HTML::TagReader будет полезен для вас. Удачного
программирования!
Ссылки
- man страница
HTML::TagReader
- Руководство по Perl: Perl
III (January 2000)
- Программа, использующая tr_tagcontentgrep ( без
HTML::TagReader): tr_tagcontentgrep
(txt) or tr_tagcontentgrep
(html)
- Исходники HTML:TagReader:
http://cpan.org/authors/id/G/GU/GUS/
или
http://main.linuxfocus.org/~guido/
- Tidy вам необходим если вы занимаетесь веб дизайном : tidy - утилита для проверки
корректности html кода
Как использовать tidy? Легко:
tidy -e file.html
просто выведет список ошибок
tidy -im -raw file.html
откорректирует файл.