Добавить в избранное | Сделать стартовой страницей

Большая Linux библиотека для пользователей OS Linux и ПО для нее
Есть что сказать? Нужен совет? Посети наш форум.


При поддержке
Продвижение сайта
Создание сайта
Администрирование сервера
настройка сервера
Администрирование сервера
настройка сервера
Администрирование сервера
аренда сервера


Обработка 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(). В результате файл будет читаться построчно и очередная строка будет помещена в переменную $_. Почему так делает Perl? Дело в том, что в Perl есть служебная переменная INPUT_RECORD_SEPARATOR ($RS или $/), в которой определяется символ "\n" как конец строки. Если вы определите $/=">", то Perl будет считать ">" концом строки. Следующий однострочный Perl скрипт преобразует html текст таким образом, что каждая строка будет заканчиваться на ">":

perl -ne 'sub BEGIN{$/=">";} s/\s+/ /g; print "$_\n";' file.html

такой html файл

some text here

преобразуется к такому виду

some text here

Но это по-прежнему не очень читаемо. Для разработчика важно, чтобы данные передавались тэг за тэгом. Чтобы, например, можно было легко найти "
Замена переменной "$/" (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:
index.html:257:

 

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> ( $tagtype eq "title" ) и когда находим устанавливаем флаг ( $printnow ) и начинаем выводить заголовок пока не встретим .
Используем следующий вызов программы:

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 )
    # $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:
file.html:140:1:

Хотите еще пример? Напишем программу, которая вырежет все тэги и из 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=... в тэг
tr_xlnk и tr_staticssi полезны при копировании веб-сайта на CD. Веб сервер выдаст вам страницу http://www.linuxfocus.org/index.html даже если вы просто наберете http://www.linuxfocus.org/ ( без index.html ). Если в случае копирования сайта на диск вы просто переносите все файлы и потом обращаетесь к диску напрямую ( file:/mnt/cdrom ) то вы увидите просто список файлов каталога вместо index.html. Это произошло в первый раз при создании LinuxFocus 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
    откорректирует файл.