Преобразование файла HTML в файл формата XHTML

Существуют разные методы изучения языков программирования. Можно постепенно знакомить обучаемого с синтаксисом и семантикой операций, выражений, структур управления, причем процесс познания при таком подходе идет от простого ко все более сложному, а можно сразу же начать с решения реальных практических задач, объясняя по мере возникновения синтаксис и семантику основных языковых конструкций, так как учебный курс по какому-либо языку программирования, особенно специализированному, предполагает наличие у обучаемого знаний по основам программирования. Подобный подход достаточно широко распространен во многих учебных курсах типа «Perl в примерах и задачах», «Perl за 10 недель» и т. п.
И тот и другой подходы широко используются во многих учебных курсах, имея свои достоинства и недостатки: первый дает систематизированные знания, но слабую практическую направленность, тогда как второй может дать обучаемому рецепты решения большинства возникающих задач, но за этой «утилитарностью» скрыть стройную систему понятий языка и его идеологию.
В этом учебном курсе мы решили соединить оба упомянутых подхода: сначала решить одну практическую задачу, на примере которой показать преимущества использования языка Perl в задачах обработки текстовых файлов, а также представить типичную структуру Perl-программы и ввести ряд ключевых понятий, а в последующих главах систематически изложить основы языка по методу «от простого к сложному».

Преобразование файла HTML в файл формата XHTML

Приложение, с которого мы начнем изучение языка Perl, покажет эффективность его использования при работе с текстовыми файлами — именно для решения подобных задач, собственно говоря, и разрабатывался этот язык. Более того, мы разработали его таким образом, чтобы продемонстрировать и структуру программы, и основные понятия и конструкции языка Perl.
Итак, что будет делать наше первое приложение? Оно будет преобразовывать файл формата HTML в файл формата XHTML. Конечно, это слишком сильное утверждение. Приложение всего лишь будет отыскивать в файле теги HTML и заменять их содержимое строчными буквами, так как язык XHTML основан на языке XML, который чувствителен к регистру. Например, в нем теги (или элементы в соответствии с терминологией XML) <mg> и <IMG> являются двумя различными тегами. Поэтому одним из первых правил при переходе от HTML к XHTML является запись всех названий начальных и конечных тегов HTML строчными буквами. Конечно, следует проверить выполнение и еще целого ряда требований, но для нашего первого приложения на языке Perl уже и этого вполне достаточно.
ПРИМЕЧАНИЕ. В последнее время в сообществе пользователей Интернета большую популярность наряду с документами HTML стали приобретать документы XML. (размеченные с помощью расширяемого языка разметки XML). Они позволяют легко и быстро получить доступ к данным различных структурных элементов документа. Именно это свойство документов XML и делает их столь популярными, так как документы HTML не хранят свою структуру. В сочетании с XSLT (языком преобразования документов XML в документы других форматов, в том числе и в формат HTML) документы XML могут полностью изменить идеологию создания Web-приложений. Для облегчения перехода от повсеместного использования неструктурированных документов HTML к использованию структурированных документов XML специалистами консорциума W3C (организации, регулирующей стандарты в Интернете) и был разработан язык разметки XHTML, основанный на языке XML и в то же время сохраняющий черты традиционно применяемого в Интернете языка HTML.
Наше приложение будет читать содержимое файла HTML, отыскивать в нем все теги (последовательность символов, заключенную в угловые скобки <...>) и заменять все содержащиеся в них символы на строчные. Полный код приложения представлен в листинге 2.1.
Листинг 2.1. Преобразование файла формата HTML в формат XHTML
01 #!/usr/bin/perl-w
02
03 # Проверка переданного имени файла HTML
04 die "Не задан файл HTML!" unless @ARGV;
05
06 # Открытие, чтение и закрытие файла HTML
07 open(F1,  $ARGV[0]) or die "Ошибка открытия файла:\n $!";
08 @file = <F1>;
09 close(F1) or die $!;
10
11 # Создание пустого файла HTML с тем же именем
12 open(F1,">$ARGV[0]") or die "Ошибка открытия файла: $!";
13
14 # Цикл по всем строкам исходного файла и замена тегов
15 foreach $m (@file) {
16   print F1 replace($m);   # Запись строки в новый файл
17 }
18
19 # Закрытие нового файла HTML
20 close(F1) or die $!;
21
22 # Определение подпрограммы замены
23 sub replace {
24   my $string = $_[0];
25   $string =~ s/<(.+?)>/<\L$1\U>/g;
26   return $string:
27 }
ВНИМАНИЕ Для указания ссылок в тексте на операторы программы из листинга 2.1 все ее строки пронумерованы. При создании файла программы, естественно, номера набирать не следует.
Для того чтобы выполнить нашу первую программу, следует набрать ее текст в обычном текстовом редакторе и сохранить в файле с расширением pl. Программы Perl являются обычными текстовыми файлами, и для их создания можно использовать любой текстовый редактор: в UNIX, например, доступны vi и ed, а в Windows — Блокнот (notepad.exe), или текстовый редактор программы Far.
СОВЕТ. Кроме перечисленных редакторов текстовых файлов в операционных системах семейства Windows можно использовать различные свободно распространяемые или коммерческие текстовые редакторы со средствами разработки программ на языке Perl, Среди огромного их количества отметим Perl Builder (http://www.solutionsoft.com/perl.htm) — полноценную интегрированную среду разработки (IDE) и Programmer's File Editor (PFE) (http://www.lancs.ac.uk/people/cpaap/pfe/) — многофункциональный редактор файлов различных типов, включая файлы Perl, позволяющий подключать компиляторы разнообразных языков программирования.
Чтобы выполнить программу из листинга 2.1, сохраненную, например, в файле program1.pl, в любой операционной системе с командной строкой достаточно выполнить команду:
perl program1.pl t.html
Сразу же заметим, что для выполнения нашей программы ей необходимо передать параметр, представляющий имя файла, который она будет обрабатывать. В приведенном примере команды запуска нашего приложения таковым является t.html. Если этого не сделать, то программа отобразит на экране монитора или в окне DOS сообщение о неправильно набранной команде запуска.
Первая строка любого Perl-сценария может содержать любой оператор языка или специальный комментарий. Специальный комментарий начинается с последовательности символов #!, между которыми не должно быть никаких пробелов. Специальный комментарий несет в себе определенную информацию как для операционной системы, в которой выполняется сценарий Perl, так и для самого интерпретатора perl. Сразу же заметим, что обычный комментарий вводится в программу Perl с помощью одного символа # и распространяется до конца строки.
ВНИМАНИЕ Специальный комментарий должен быть самым первым оператором в программе, но допускается задание до ноги никаких обычных комментариев. Появление его в любом другом месте программы трактуется как обычный комментарий.
Специальный комментарий имеет значение в основном при работе в операционной системе UNIX. Основное его предназначение — указать местонахождение программы-интерпретатора perl. В этой операционной системе любой файл можно сделать выполняемым с помощью команды:
chmod +x programl.pl
Полученный после обработки командой chmod файл programl.pl может быть выполнен с помощью команды:
/program1.pl
При этом информация в строке специального комментария (первая строка программы) сопоставляет выполняемому файлу приложение, которое должно его обрабатывать. Более того, в специальном комментарии можно определить при необходимости параметры, или ключи, определяющие режим работы этого приложения, в нашем случае интерпретатора perl. Можно задать отображение предупреждающих сообщений или загрузку отладчика в случае обнаружения серьезной ошибки с помощью, например, следующей строки:
#!/bin/usr/perl -w -d
При работе в операционной системе Windows строка специального комментария, собственно говоря, не нужна, если только нет необходимости задавать режимы работы интерпретатора непосредственно в самом файле программы. При этом можно ограничиться простым заданием имени интерпретатора perl, не указывая полное имя каталога его расположения: #!perl -w -d
В этой же операционной системе с каждым файлом, имеющим определенное расширение, можно ассоциировать программу, которая будет вызываться при двойном щелчке мышью на любом файле с таким расширением. При установке интерпретатора языка Perl фирмы ActiveState Tool Corp., который использовался нами для выполнения Perl-сценариев в операционных системах семейства Windows, автоматически устанавливается соответствие файлов с расширением pl и интерпретатора perl.
Теперь, когда мы знаем, как можно выполнять программы, написанные на языке Perl, в двух наиболее распространенных операционных системах, мы кратко остановимся на деталях нашего сценария, код которого представлен в листинге 2.1. В последующих разделах этой главы при обсуждении структуры Perl-программы мы постоянно будем обращаться к этому сценарию. Итак, мы начинаем.
В строке 04 осуществляется проверка, было ли задано при запуске сценария имя HTML-файла, который сценарий должен модифицировать. В Perl используется большое число специальных переменных, в которых хранится информация, используемая различными операциями и операторами языка, а также определенные результаты их выполнения. Переменная @ARGV является одной из таких специальных переменных. Более того, она представляет массив, в элементах которого хранятся элементы списка переданных в сценарий параметров при его запуске из командной строки. В Perl первый символ идентификатора любой переменной определяет ее тип: имя скалярной переменной начинается с символа $, массива скаляров — с @, а ассоциативного, или хэш-массива, — с %. В скалярной переменной можно хранить числовые или строковые данные, в массиве скаляров — последовательность скалярных величин, а хэш-массив, или просто хэш, позволяет хранить данные вместе с определенными для них ключами.
Получить значение элемента массива скаляров можно с помощью целого индекса, заключенного в квадратные скобки, непосредственно следующие за именем переменной массива, причем нумерация элементов начинается с нуля. Так как элементы массива скаляров являются скалярными значениями, то при ссылке на элементы массива первый символ имени переменной массива @ заменяется на символ скалярной величины $. Мы можем увидеть, как это работает, посмотрев на ссылку на первый элемент специального массива @ARGV в параметрах функции open ( ) открытия файла в строке 07:
open(F1, $ARGV[0])
Относительно самого оператора строки 04 следует сказать, что он является простым оператором с модификатором unless.
Все операторы в Perl делятся на простые и составные. Простой оператор — это выражение, завершенное символом ;. Этот обязательный символ является признаком завершения любого оператора, но в некоторых случаях может быть опущен, например после блоков, которые представляют собой последовательность операторов, заключенную в фигурные скобки {...}, и могут ограничивать область видимости переменных. Составные операторы определяются в терминах блоков и представляют основные используемые в языке структуры управления: ветвление и циклы.
Выражение — это совокупность литералов и переменных, над которыми выполняются допустимые операции, результатом операций является некоторое значение. Язык Perl предоставляет программисту большое число операций над базовыми типами данных, имеющих общепринятый смысл: арифметические, логические, строковые и т. д. Кроме того, большинство его стандартных функций рассматриваются как списковые или унарные операции, воздействующие на свои параметры. Поэтому в операторе строки 04 конструкция
die "He задан файл HTML!"
является обычным выражением Perl с операцией-функцией die, завершающей выполнение сценария и печатающей на системном устройстве вывода, каковым является экран монитора компьютера, строку, к которой эта операция применяется. Модификатор unless имеет тот же смысл, что и само это слово в английском языке, то есть выражение, стоящее перед ним, выполняется, если только стоящее за ним выражение не ложно. У нас это выражение представлено просто идентификатором специального скалярного массива, хранящим список переданных в сценарий параметров. Каково же значение этого выражения? В данном булевом контексте оно будет равно «истина», если этот массив не пуст, а это означает, что пользователь при запуске сценария передал в него имя HTML-файла. Итак, суммируя сказанное, мы можем сформулировать семантику оператора строки 04:
die "He задан файл HTML!" unless @ARGV;
следующим образом: если массив @ARGV не пуст, что соответствует передаче параметров в сценарий при его запуске, то в сценарии ничего не делается, а управление передается следующему за этим оператору; в противном случае выполняется функция die, которая отображает сообщение и завершает сценарий.
Кроме рассмотренного модификатора unless в Perl можно использовать также следующие модификаторы: if, while, until и foreach. Их семантика полностью соответствует использованию этих слов в английском языке. Модификаторы простых операторов подробно будут рассмотрены в главе 5.
Следующие три оператора в строках 07-09 открывают HTML-файл, читают вес его содержимое в скалярный массив @file и закрывают файл.
Открыть файл в Perl можно функцией open, являющейся в то же время списковой операцией, передав в качестве параметров дескриптор файла, используемый в дальнейшем в операциях чтения/записи содержимого этого файла, и имя файла. Имя файла может быть передано со специальным префиксом, указывающим режим открытия файла: только чтение, только запись, чтение/запись и т. д. Если префикс режима открытия не задан, то по умолчанию файл открывается только для чтения. Дескриптор файла представляет собой правильный идентификатор Perl, который, как и в других языках программирования, является последовательностью алфавитно-цифровых символов и символов подчеркивания _, начинающейся с буквы или подчеркивания. Отметим, что язык Perl чувствителен к регистру, поэтому идентификаторы F1 и f1 являются совершенно разными. Общепринятой практикой в Perl является использование в дескрипторах файлов прописных букв.
ВНИМАНИЕ. Язык Perl чувствителен к регистру, поэтому следует быть внимательным при наборе идентификаторов переменных и дескрипторов файлов. Используйте для дескрипторов файлов прописные буквы.
Если в функции open задано только имя файла без указания каталога его расположения, то считается, что файл расположен в том же каталоге, в котором располагается и сам сценарий. При этом следует перед запуском сценария сделать этот каталог активным.
Оператор строки 07 не только открывает файл, но и обрабатывает ошибку его открытия. Для этого используется выражение с низкоприоритетной операцией or, являющейся операцией логического ИЛИ. Бинарные логические операции в Perl вычисляются по так называемой укороченной схеме: если после вычисления первого операнда ее результат ясен, то второй операнд вообще не вычисляется. В нашем случае сначала будет вычислено выражение, стоящее слева от символа операции or, и если файл будет открыт, то в контексте логической операции функция open возвратит значение «истина». Следовательно, при любом значении второго операнда уже ясно, что значением всего логического выражения будет «истина», И поэтому второй операнд не вычисляется. Если же файл по каким-то причинам открыть не удалось, то функция-операция open возвратит «ложь», и будет вычислен второй операнд, который приведет к завершению выполнения сценария с отображением сообщения о возникшей ошибке.
В строковом параметре функции die использованы также две возможности работы со строковыми литералами, заданными в двойных кавычках. Во-первых, использование специальных управляющих последовательностей, начинающихся с обратной наклонной черты \, и, во-вторых, подстановка значения скалярной переменной или скалярного массива в строку. Мы использовали управляющую последовательность \n, которая вставляет в строковый литерал символ перехода на новую строку и при выводе подобного литерала на экран монитора или на принтер часть стоящих после нее символов будет напечатана с новой строки.
Подстановка значения скалярной переменной, называемая некоторыми авторами «интерполяцией» (от английского термина «interpolation», используемого для обозначения указанной возможности), означает, что содержимое переменной, встретившейся в строковом литерале, преобразуется в последовательность символов и вставляется в этот строковый литерал в том месте, где появилась переменная. В параметре функции die используется специальная переменная $!, которая хранит результаты выполнения некоторых системных функций. В контексте строки эта переменная содержит текстовое описание возникшей ошибки; в контексте, где требуется числовое значение, она содержит числовой код ошибки. Мы подошли к очень интересной концепции «контекста» в Perl. Более подробное его изучение мы оставим до момента, когда мы будем изучать выражения в главе 4 «Операции и выражения», а сейчас только заметим, что все функции и операции Perl в зависимости от того, в каких они используются выражениях и какой тип значения требуется по смыслу вычисления этих выражений, могут возвращать либо числовые, либо строковые, либо логические значения. С возвращением логического значения мы уже столкнулись при выполнении операции открытия файла в этом же операторе.
Отмстим, что в Perl существует три типа строк: заданных в одинарных ('), двойных (") и обратных (`) кавычках, и они по-разному интерпретируются в языке. Мы будем подробно изучать все строковые литералы в главе 3.
Непосредственно в операторе строки 08 осуществляется чтение содержимого файла. Здесь использованы операции присваивания = и ввода <>. Параметром операции ввода является дескриптор F1 открытого файла, а в левой части операции присваивания используется скалярный массив @file, который «создает» списковый контекст. Это приводит к тому, что операция ввода читает все содержимое файла и последовательно помещает каждую его строку (вместе с символом перехода на новую строку) в элементы указанного массива. Таким образом, после выполнения оператора строки 08 первый элемент массива $file[0] будет содержать первую строку файла, второй элемент $fi1e[l]— вторую и т. д.
Для ввода в программу операцией <> информации пользователя, которую он набирает на клавиатуре, используется автоматически создаваемый интерпретатором perl файл стандартного устройства ввода с дескриптором STDIN. Например, чтобы сохранить ввод с клавиатуры в скалярной переменной, можно использовать следующий оператор
$fromKB = <STDIN>;
Этот оператор будет считывать и сохранять вводимые пользователем с клавиатуры символы, пока не встретит символ перехода на новую строку (нажатие клавиши Enter).
Закрывается файл функцией close с единственным параметром — дескриптором файла. Причем здесь мы также использовали уже известный нам подход для отслеживания возможных ошибок закрытия с помощью вычисления логической операции ИЛИ.
В строке 12 мы снова открываем тот же самый файл, но уже только для записи. Для этого в параметре функции open, представляющем имя файла, используется префикс >, означающий, что файл открывается для записи, причем предварительно уничтожается все его содержимое. И опять-таки здесь же мы использовали технологию подстановки содержимого скалярной переменной и отслеживания возможных ошибок открытия файла.
Основная работа по замене в названиях тегов HTML в файле прописных букв на строчные осуществляется в цикле foreach, являющемся составным оператором Perl и представленным в строках 15-17 нашей программы.
Цикл foreach осуществляет последовательный перебор элементов массива, заданного в круглых скобках (@file), причем на каждом шаге выполнения значение соответствующего элемента массива сохраняется в переменной цикла, задаваемой непосредственно после ключевого слова foreach (переменная $m). Тело цикла определено блоком в фигурных скобках. В нем присутствует единственный оператор записи print с двумя параметрами. Первый параметр — дескриптор файла, в который записывается информация, а второй параметр — записываемая строка, которая представляет результат выполнения функции replace, определяемой в строках 23-27 программы.
Оператором print можно осуществлять вывод на системное устройство вывода, которым обычно является экран монитора. Для этого достаточно в качестве дескриптора файла задать STDOUT, всегда создаваемый интерпретатором perl, или вообще не указывать никакого дескриптора, а просто задать список выводимых величин. Например, следующие два оператора отобразят на экране монитора значения переменных $m и $p:
print STDOUT $m, $р, "\n";
print $m, $p, "\n";
В Perl функции объявляются с помощью ключевого слова sub, за которым следует имя функции без каких-либо фиктивных параметров. Тело функции, как и в случае с оператором цикла, определяется в блоке, непосредственно следующем за объявлением имени функции. Наша функция replace должна получить в качестве параметра строку исходного файла, найти в ней все HTML-теги (последовательности символов, расположенные между открывающей < и закрывающей > угловыми скобками) и заменить в них все прописные буквы на строчные.
ВНИМАНИЕ Наша функции замены replace, а следовательно и весь сценарий, обладают некоторыми ограничениями. Во-первых, так как мы обрабатываем файл по строкам, то открывающие и закрывающие угловые скобки тегов должны обязательно располагаться в одной строке. Во-вторых, мы заменяем все прописные буквы на строчные в последовательности символов между угловыми скобками, не анализируя их содержимое. Это, естественно, может привести к тому, что при вызове функции JavaScript в каком-либо обработчике события (onclick, onmouseover и т. д.) тега прописные буквы в имени функции также будут заменены на строчные, а в результате сценарий JavaScript окажется неработоспособным, так как язык JavaScript чувствителен к регистру.
Для передачи параметров в функцию используется специальный массив @_, элементы которого содержат значения переданных при вызове функции фактических параметров. В нашей функции первый оператор тела (строка 24) сохраняет значение единственного фактического параметра в локальной переменной $string, которая определяется с помощью специальной функции my. Эта переменная существует только во время выполнения функции и уничтожается, когда выполнение функции заканчивается. Таким образом в Perl реализуется передача параметров по значению, так как на самом деле в элементах массива @_ хранятся ссылки на фактически переданные параметры, поэтому изменение содержимого какого-либо элемента этого массива приведет к изменению содержимого соответствующего фактического параметра. Подобный механизм передачи параметров в функцию или подпрограмму известен как передача параметров по ссылке.
Основная работа по замене тегов HTML в переданной в функцию строке выполняется оператором 25 программы. Для этого используются операция замены с регулярным выражением s/.../.../g и операция связывания =~ .
Операция замены s/.../.../g осуществляет поиск в строке, содержащейся в специальной переменной $_, подстроки, соответствующей шаблону, представленному регулярным выражением и заданному между первыми наклонными чертами, и в случае успешного поиска замену найденного фрагмента текстом, заданным между вторыми наклонными чертами.
Регулярное выражение представляет набор правил для описания фрагментов текстовых строк. Синтаксис этих правил использует сами символы и метасимволы, имеющие в регулярном выражении специальное значение. Систематическое изучение регулярных выражений мы отложим до главы 9, а пока ограничимся объяснением используемого нами регулярного выражения /<(.+?)>/.
Метасимвол . представляет любой одиночный символ, кроме символа новой строки. Множитель +, стоящий непосредственно за этим символом, означает, что этот метасимвол повторяется один или более раз. Таким образом, последовательность .+, встречающаяся в регулярном выражении, означает последовательность символов любой длины. Модификатор ? предписывает для поиска в строке фрагмента, соответствующего заданному шаблону, использовать «ленивый» алгоритм. Это означает, что будет найден минимальный фрагмент, соответствующий шаблону, так как поиск в этом случае начинается с пустой строки, к которой постепенно добавляется по одному символу из строки поиска, пока не будет найден подходящий фрагмент. Круглые скобки, в которые заключен шаблон .+?, не имеют никакого значения для формирования шаблона, но позволяют сослаться на найденный фрагмент в строке замены. Суммируя сказанное, мы можем сформулировать, что в нашей операции замены ищется фрагмент текста, заключенный в угловые скобки.
Найденный фрагмент заменяется на фрагмент, определяемый регулярным выражением /<\L$1\U>/. В нем символы угловых скобок представляют сами себя. Последовательность символов $1 является ссылкой на найденный фрагмент текста, удовлетворяющий шаблону, заданному в первых круглых скобках, а метапоследовательности \L и \U, в которые заключена эта ссылка, предписывают заменить в найденном фрагменте все прописные буквы строчными. Таким образом, мы получаем, что замещающий текст представляет собой тот же самый найденный фрагмент, в котором все прописные буквы, стоящие между угловыми скобками, заменяются строчными, что, собственно говоря, нам и требовалось.
Последнее, что следует объяснить в используемой нами операции замены, — это флаг g. По умолчанию операция замены выполняется только для первого найденного фрагмента текста. Флаг g предписывает продолжить операцию замены, пока в строке поиска не будут найдены и заменены все фрагменты, удовлетворяющие указанному первым шаблону.
Как уже говорилось, по умолчанию поиск осуществляется в строке, содержащейся в специальной переменной $_. Операцией связывания =~ мы просто назначаем другую строку поиска. В нашей функции это будет строка, переданная ей в качестве параметра ($string).
Оператором return мы явно определяем возвращаемое функцией replace значение. По умолчанию в отсутствие оператора return возвращаемым значением функции будет значение последнего выполненного оператора. Следует отметить, что в Perl любой оператор имеет возвращаемое значение.
Вернемся к оператору foreach нашего сценария. Теперь ясно, что на каждом шаге цикла в файл, представленный дескриптором F1, будет записываться строка исходного файла HTML, в которой все названия тегов будут записаны строчными буквами (с учетом описанных выше ограничений).
Наш сценарий преобразования файла формата HTML в файл формата XHTML, конечно, достаточно тривиален и не полон. Однако с его помощью мы смогли показать, как просто решается в Perl задача, которая в другом языке программирования могла бы потребовать длительного времени разработки, и в то же время нам удалось дать представление о структуре Perl-программы. В следующих разделах этой, по существу вводной, главы мы не только систематизируем полученные нами знания о структуре программы на языке Perl, но и несколько расширим их.

Следующая страница Содержание главы


Реклама