Работа с текстом, или Философия Perl::Журнал СА 1.2002
www.samag.ru
     
Поиск  
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Сетевой агент
О журнале
Журнал «БИТ»
Информация для ВАК
Звезды «СА»
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Форум
Вакансии
Спроси юриста
Игры
Контакты
   
Слайд шоу  
Представляем работы Виктора Чумачева
Виктор Чумачев – известный московский художник, который сотрудничает с «Системным администратором» уже несколько лет. Именно его забавные и воздушные, как ИТ, иллюстрации украшают многие серьезные статьи в журнале. Работы Виктора Чумачева хорошо знакомы читателям в России («Комсомольская правда», «Известия», «Московские новости», Коммерсант и др.) и за рубежом (США, Германия). Каждый раз, получая новый рисунок Виктора, мы в редакции улыбаемся. А улыбка, как известно, смягчает душу. Поэтому смотрите на его рисунки – и пусть у вас будет хорошее настроение!
1001 и 1 книга  
22.11.2018г.
Просмотров: 163
Комментарии: 0
MySQL 8 для больших данных

 Читать далее...

22.11.2018г.
Просмотров: 111
Комментарии: 0
Осваиваем C++17 STL

 Читать далее...

22.11.2018г.
Просмотров: 145
Комментарии: 0
Решение задач на современном C++

 Читать далее...

22.11.2018г.
Просмотров: 107
Комментарии: 0
Программируй на Haskell

 Читать далее...

29.10.2018г.
Просмотров: 426
Комментарии: 0
Информатика. Учебник, 4-е издание, цветное, переработанное и дополненное

 Читать далее...

Дискуссии  
17.09.2014г.
Просмотров: 19922
Комментарии: 3
Красть или не красть? О пиратском ПО как о российском феномене

Тема контрафактного ПО и защиты авторских прав сегодня актуальна как никогда. Мы представляем ...

 Читать далее...

03.03.2014г.
Просмотров: 22086
Комментарии: 1
Жизнь под дамокловым мечом

Политические события как катализатор возникновения уязвимости Законодательная инициатива Государственной Думы и силовых структур, ...

 Читать далее...

23.01.2014г.
Просмотров: 30706
Комментарии: 3
ИТ-специалист будущего. Кто он?

Так уж устроен человек, что взгляд его обращен чаще всего в Будущее, ...

 Читать далее...


  Опросы

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 Работа с текстом, или Философия Perl

Архив номеров / 2002 / Выпуск №1 (1) / Работа с текстом, или Философия Perl

Рубрика: Программирование /  Анализ данных

ЕВГЕНИЙ КОНОВАЛОВ

Работа с текстом, или Философия Perl

Есть два основных аспекта, которые легли в основу этой статьи. Первый – это периодически возникающая перед большинством веб-программистов проблема переноса тех или иных документов в базу данных (БД), используемую, например, при генерации страниц скриптами. Проблема эта из разряда концептуальных и решается по-разному: в зависимости от рабочей среды, окружающей разработчика плотным кольцом всевозможных требований, условий и согласований. Вторая идея, попавшая в фокус обсуждения – использование Perl для решения любопытной задачи, связанной с упомянутой выше проблемой. Собственно об этом и пойдет речь в дальнейшем. (Примечание: данная статья, пожалуй, рассматривается именно как посылка для обсуждения – иные подходы не имеют и половины такой же привлекательности.)

Вместо лирического вступления

На всякий случай автор искренне просит не рассматривать эту статью как очередное «УРА» в честь наиболее развитого и удобного, свободного, попросту приятного и даже замечательного языка для работы с текстом и программирования в Web (разумеется, речь идет о Perl).

Сильным духом программистам посвящается...

«Если ты едешь тише – дольше ехать тебе. Если поспешишь, то не разглядишь идей»

Следуя этой нехитрой идее, хочется без излишних подробностей, но, тем не менее, вдумчиво изложить уважаемому читателю идеи, которые, вполне возможно, пригодятся в работе или просто послужат поводом для размышлений.

Сначала несколько слов об истоках рассматриваемой проблемы. Сегодня адаптация документов различных форматов для внесения их в БД веб-сайта – занятие привычное во многих организациях, имеющих собственную «точку присутствия» в сети Интернет. Зачастую это связано с возможностью простой реализации полнотекстового поиска в таблицах БД и простотой реализации механизма доступа к информации.

Так или иначе, все выглядит достаточно просто и понятно, когда речь заходит о небольших объемах данных, особенно, если они подготавливаются централизовано одним-двумя сотрудниками. Сложности возникают при работе с документами больших размеров[1]. Что также может осложняться острым желанием предоставить администратору БД и многочисленным контент-менеджерам удобную методику для работы с данными.

Одновременно специфика Web, конечно же, делает предпочтительной разбивку крупных документов на отдельные составляющие (например, главы), и предоставление пользователю возможности их раздельного просмотра. Это связано с сегодняшней философией жизни в Интернет, например, с «диалапными» скоростями доступа (впрочем, не только с этим).

Подытоживая сказанное, вашему вниманию представляется задача, решенная в рамках разработки подсистемы информационного наполнения БД.

Исходные данные:

  • тип вносимой информации – HTML-код значительного объема;
  • весь HTML-код заключен в едином документе;
  • документ необходимо разбить на составные части, которые размещаются в БД (с тем же успехом их можно сохранить в файл).

Основная трудность решения: в результате разбиения документа необходимо получить фрагменты HTML-кода, имеющие корректную HTML-структуру. Такого рода корректность подразумевает:

  • соответствие каждому начальному HTML-тегу завершающего HTML-тега (для HTML-элементов, обязательно обозначаемых именно парой тегов);
  • переработку внутренних гиперссылок (для случаев, когда сама ссылка оказывается в одном фрагменте, а то место в оригинальном документе, на которое она ссылается, – в другом фрагменте).

Отправная точка: исходные данные в подробностях

«Начинать нужно с чего-то...», – и с этим трудно не согласиться.

Чтобы стало понятным все, о чем пойдет речь в дальнейшем, необходимо внести большую ясность в то, что касается исходных данных. На вход описанного ниже программного модуля, поступают должным образом подготовленные оглавление и текст HTML-документа. Далее приведено описание правил, по которым осуществляется такого рода подготовка документа.

Говоря коротко, необходимо иметь HTML-документ с гипертекстовым оглавлением (в оглавлении должны использоваться внутренние гиперссылки – то есть те, значения атрибутов href которых начинаются со знака решетки «#»). Такой подход достаточно удобен. Теперь о том же самом, но уже более подробно.

Дабы исключить путаницу в терминах введем несколько понятий.

«Внутренняя гиперссылка» – ссылка, при нажатии на которую выполняется переход на анкер (см. ниже), расположенный в том же HTML-документе, в теле которого находится и сама ссылка; значение атрибута HREF такой гиперссылки начинается со знака решетки #. Например:

<A HREF=»#Part1">1. Introduction</A>

«Анкер» – тег, размещаемый в том месте HTML-документа, куда необходимо осуществить перемещение по нажатию на внутреннюю гиперссылку. Например:

<A NAME=»Part1">

Примечание: связь между анкером и внутренней гиперссылкой реализуется через значения атрибута HREF гиперссылки и атрибута NAME анкера, при этом значение NAME соответствует значению HREF с точностью до знака решетки. Например:

<A HREF=»#Part1">1. Introduction</A>

является внутренней ссылкой на анкер

<A NAME=»Part1">

«Оглавление» гипертекстовое оглавление документа, состоящее из внутренних гиперссылок.

«Пункт оглавления»внутренняя гиперссылка, внутри тега которой заключен текст одного из пунктов содержания документа.

«Глава документа» или «Глава»часть HTML-документа, сопоставляемая одному из пунктов оглавления.

«Специальный анкер разметки» или «Анкер разметки»анкер, на который ссылается один из пунктов оглавления. (Такой анкер разметки располагается непосредственно перед главой (обычно перед заглавием главы), которая соответствует пункту оглавления, ссылающемуся на данный анкер разметки.)

«Документ» текст HTML-документа (без оглавления).

Далее, рассмотрим в качестве примера документа обзор по материнским платам.

Вообще говоря, в рассматриваемом случае оглавление в традиционном понимании имеет следующий вид:

  1. Introduction
  2. CPUs and CPU Sockets
  3. Supported Memory types and speed. Chipset features
  4. Built-in video and audio cards
  5. Hard drive support. USB ports and extension slots

Назовем это оглавление «Исходным оглавлением».

Однако для корректного разбиения документа на главы оглавление должно быть представлено, например, следующим образом:

<A HREF=»#Part1">1. Introduction</A>

<A HREF=»#Part2">2. CPUs and CPU Sockets</A>

<A HREF=»#Part3">3. Supported Memory types and speed. Chipset features</A>

<A HREF=»#Part4">4. Built-in video and audio cards</A>

<A HREF=»#Part5">5. Hard drive support. USB ports and extension slots</A>

В данном представлении обязательным является использование для каждого пункта исходного оглавления (см. выше) синтаксической конструкции вида:

<A HREF=»#Unique_ID»>Текст пункта оглавления</A>

где Unique_ID – произвольный идентификатор, который должен быть уникальным применительно ко всем значениям атрибутов HREF всех пунктов оглавления.

Ниже приведен соответствующий документ – это фрагмент HTML-кода из пяти глав (содержательная часть, разумеется, беспощадно урезана – буквально до потери смысла).

Примечание: при программном анализе документа заголовочная часть (вплоть до тега <BODY>) и концевые теги </HTML> и </BODY> не рассматриваются.

<HTML>

<HEADER>Motherboard Description</HEADER>

<BODY>

<A NAME=»Part1"></A>

<TABLE>

<TR><TD>

<B><H3>Introduction.</H3></B><BR><BR ><FONT COLOR=»#000099">Mainboard is the most vital component of every PC. If we compare a PC with a living organism, the processor can be compared to a heart, and the mainboard is like a system of artherias and veins which deliver information from a processor to other parts of PC.</FONT>

<A NAME=»Part2"></A>

<P><B><H3>CPUs and CPU Sockets.</H3></B>

<P><FONT COLOR=»#000099">So, it is time for you to buy a new PC. We will not discuss the modern processors in this section, as it is revealed in our </FONT><B><FONT COLOR=»#CC0000">processors section</FONT></B><FONT COLOR=»#000099">, but will concentrate on the parts responsible to connecting CPU (Central Processor Unit) to the mainboard, the sockets, since there is a great variety of them nowadays.</FONT>

<A NAME=»Part3"></A>

<P><B><H3>Supported Memory types and speed. Chipset features.</H3></B>

<P><FONT COLOR=»#000099">Not only the CPU is responsible for overall performance of your desktop (or notebook) system. Even the extremely speedy processor will be found slowed by less performing RAM (Random Ac

ss Memory).</FONT>

<A NAME=»Part4"></A><P>

<P><B><H3>Built-in video and audio cards.</H3></B>

<P><FONT COLOR=»#000099">As you see, the latest chipsets have built-in audio cardsnd video adapters.

<A NAME=»Part5"></A>

<P><B><H3>Hard drive support. USB ports and extension slots.</H3></B>

<P><FONT COLOR=»#000099">The times when you had to install multiple intput-output cards can be forgotten and now you have all drive (hard and floppy) support on the mainboard.

</TD></TR>

</TABLE>

</BODY>

</HTML>

Замечание 1: в тексте документа не может быть более одного анкера разметки с одним и тем же значением атрибута «NAME» (при этом количество анкеров разметки равно числу пунктов оглавления).

Замечание 2: очевидно, что при разбиении на фрагменты, первая глава будет содержать стартовые теги <TABLE>, <TR> и <TD> без концевых (завершающих) тегов. Это явное нарушение HTML-структуры, которое может быть особенно неприятным при использовании SSI (если HTML-код главы будет включаться в состав основной HTML-страницы средствами веб-сервера).

Программирование: идея, заложенная в основу

 «Идея – базис алгоритма», – и так повсюду.

Фактически Manual_Parser позволяет выполнить потеговый анализ документа. Все операции базируются на использовании нескольких довольно сложных структур данных (см. далее), позволяющих описать документ с точки зрения типов тегов, содержащихся в нем, и их количества. На основе такой количественной статистики производится анализ корректности HTML-структуры всего документа и глав, на которые он будет разделен в дальнейшем.

Идея проста: анализ сводится к подсчету количества стартовых и завершающих тегов и сравнении полученных величин, исправление обнаруженных ошибок заключается в добавлении недостающих стартовых и завершающих тегов.

Остановимся на этом чуть подробнее. Суть заключается в том, что первоначально для каждой главы из тех, на которые разбивается документ подсчитывается число начальных и завершающих тегов (назовем их «Величина-1» и «Величина-2»). Затем при обнаружении несоответствия между этими значениями начинается итерационный поиск недостающего парного тега: последовательно перебираются последующие главы и для каждой такой главы к величине-1 и величине-2 соответственно прибавляются числа начальных и завершающих тегов этой главы. Условием успешного окончания поиска недостающего парного тега является равенство величины-1 и величины-2.

Если назвать главу, в которой обнаружилось несоответствие, стартовой (Главой итерационного поиска); главу в которой был найден недостающий парный концевой тег – заключительной, а все главы между ними – промежуточными, то процесс коррекции HTML-структуры глав можно описать достаточно просто. Необходимо дополнить стартовую главу концевым HTML-тегом, заключительную главу – стартовым HTML-тегом, промежуточные главы – стартовым и концевым HTML-тегами.

Программирование: иерархия классов

При работе упор был сделан на методику объектно-ориентированного программирования. Именно это позволило использовать уже существующие классы HTML::Parser, предоставляющий методы для разбора SGML-документов (так же используется HTML::Filter – дочерний класс HTML::Parser).

Было разработано два класса: Manual_Cont_Parser и Manual_Parser.

Manual_Cont_Parser наследуется от HTML::Filter, класс занимается обработкой оглавления (см. выше), разбирая его попунктно. Предоставляет методы:

Get_Links_Lines – возвращает массив, каждый элемент которого содержит текст пункта оглавления (без тегов);

Get_Links_Hrefs – возвращает массив, каждый элемент которого содержит значение атрибута HREF пункта оглавления.

Manual_Parser наследуется от HTML::Parser. Отвечает за разбор документа и его разбиение на главы. Именно этому классу и будет уделено основное внимание.

Класс HTML::Parser обладает рядом методов. Ключевыми являются методы parse($string) или parse_file($file), вызов одного из них инициирует работу по анализу документа.

Еще два используемых метода – start($tag, $attr, $attrseq, $origtext) и end($tag, $origtext). Первый из них вызывается при обнаружении начального HTML-тега, передаваемые ему параметры: $tag – имя тега, $attr – хэш с именем атрибута тега в качестве ключа и значением атрибута в качестве значения хэша, $attrseq – массив имен атрибутов тега, $origtext – HTML-код тега. Второй метод выполняется при обнаружении завершающего HTML-тега: $tag – имя тега, $origtext – HTML-код тега.

В программе методы start и end перегружаются (замещаются). Именно в рамках этих процедур производится потеговый анализ документа и собирается упомянутая выше статистика, характеризующая его HTML-структуру.

Программирование: основные структуры данных

«Структура структурной структуризации структурирует структурную типизацию», –  и все это не случайно.

Речь идет о структурах данных объектов класса Manual_Parser – в них накапливается статистика, позволяющая проанализировать HTML-структуру документа.

Существует три основные структуры данных: $Doc_Info, $Work_Container и массив @Pages_Info. Сразу же следует заметить, что речь идет о сложных, вложенных структурах данных. Их понимание играет ключевую роль.

Каждый элемент массива @Pages_Info представляет собой структуру данных, с элементами в виде скаляров, списков и хэшей, описывающих HTML-структуру одной главы (замечание в рамках борьбы с несозвучностью названий и терминологии: под «Page» понимается HTML-страница, соответствующая главе).

@Pages_Info =

 {

 # Хэш структур (см. ниже)

  Tags => {%Tags},

 # Хэш структур (см. ниже)

  Tags_Seq_Page => {%Tags_Seq_Page},

 # Общее количество стартовых тегов в пределах одной главы

  Num_Of_Tags => $Num_Of_Tags,

 # Массив, каждое значение – порядковый номер тега в Документе, для которого  в Главе необходимо добавить стартовый тег.

  Start_Tags_To_Add => {@Start_Tags_To_Add},

 # Массив, каждое значение – порядковый номер тега в Документе, для которого в Главе необходимо добавить  завершающий тег.

  End_Tags_To_Add => {@End_Tags_To_Add},

 # Хэш, ключ хэша – значение атрибута href Внутренней Гиперссылки, значение хэша – номер Главы

  Local_Links => %Local_Links,

 # Хэш, ключ хэша – значение атрибута href Внутренней Гиперссылки на одну из Глав, значение хэша – номер Главы

  Local_Links_To_Pages => %Local_Links_To_Pages

 };

# Хэш структур, ключ хэша – порядковый номер тега в текущей Главе

 %Tags_Seq_Page =

 {

 # Имя тега

  Tag_Name => $Tag_Name,

 # Порядковый номер тега во всем Документе

  Tag_Num_In_Doc => $Tag_Num_In_Doc

 };

# Хэш структур, ключ хэша – имя (тип) тега

 %Tags =

 {

 # Количество начальных HTML-тегов данного типа в Главе

  Start_Tag_Num => $Start_Tag_Num,

 # Количество заключительных HTML-тегов данного типа в Главе

  End_Tag_Num => $End_Tag_Num

 };

$Doc_Info представляет собой структуру данных, с элементами в виде скаляров, списков и хэшей, описывающих документ вцелом.

$Doc_Info =

 {

 # Хэш структур (см. ниже)

  Tags_Seq_Doc => {%Tags_Seq_Doc},

# Хэш структур (см. ниже)

  Tags => {%Tags},

 # Общее количество стартовых тегов в пределах Документа

  All_Start_Tags_Num => $All_Start_Tags_Num,

 # Общее количество завершающих тегов в пределах Документа

  All_End_Tags_Num => $All_End_Tags_Num,

 # Хеш, ключ хэша – имя анкера, значение хеша – номер Главы, в которой расположен анкер

  Local_Anchors => %Local_Anchors

 };

# Хэш структур, ключ хэша – порядковый номер тега в Документе

 %Tags_Seq_Doc =

 {

 # Имя тега

  Tag_Name => $Tag_Name,

 # HTML-код тега

  Tag_Orig_Text => $Tag_Orig_Text,

 # Номер тега в Главе

  Tag_Num_In_Page => $Tag_Num_In_Page,

 # Номер Главы, в которой найден данный тег

  Tag_Page_Num => $Tag_Page_Num

 };

$Work_Container – структура, представляющая собой контейнер «рабочих» данных, она используется для временного хранения данных в процессе анализа.

$Work_Container

 {

 # В эту переменную заносится номер Главы, в которой недостает завершающего тега. Глава становится стартовой для итерационного поиска

 # той Главы, в которой содержится недостающий завершающий тег

  Start_Page_Num => $Start_Page_Num,

 # Имя анализируемого тега

  Tag_Name => $Tag_Name,

 # Номер анализируемого тега в Документе

  Tag_Num_In_Doc => $Tag_Num_In_Doc,

 # Номер анализируемого тега в Главе

  Tag_Num_In_Page => $Tag_Num_In_Page,

 # Переменная для подсчета общего количества стартовых HTML-тегов в Документе

  Start_Tag_Num_Total => $Start_Tag_Num_Total,

 # Переменная для подсчета общего количества завершающих HTML-тегов в Документе

  End_Tag_Num_Total => $End_Tag_Num_Total

 };

Программирование: алгоритмы работы и программный код

«Алгоритм – это, как минимум, четкая, осмысленная последовательность действий», – редкая женщина обладает даром алгоритма.

Ниже приведены прокомментированные строки кода, указываемые в приложении, использующем классы Manual_Cont_Parser и Manual_Parser.

Создание объекта класса Manual_Cont_Parser:

my $Man_Cont_Parser = Manual_Cont_Parser->new();

Подготовка к анализу документа: разбор оглавления документа, получение списка анкеров разметки:

$Man_Cont_Parser->parse($contents);

my @Contents_Hrefs = $Man_Cont_Parser->Get_Links_Hrefs;

Создание объекта класса Manual_Parser:

$Man_Parser->parse($Document_Text);

Анализ документа, разбор, корректировка и разбиение на главы:

my $Man_Parser = Manual_Parser->new(@Contents_Hrefs);

Использование полученных результатов: внесение полученных глав в БД Web-подсистемы:

$Man_Parser->Insert_Pages_In_DB();

Подготовка к анализу HTML-документа

Первоначально объекту класса Manual _Cont_Parser передается оглавление. Вызываются методы parse($text) или parse_file($file). Затем с помощью метода Get_Links_Hrefs получается результирующий список строк, каждая из которых содержит преобразованное значение атрибута href одного из пунктов оглавления (преобразование заключается в удалении символа «#», с которого начинается значение данного атрибута). Затем строки полученного списка будут использоваться объектом класса Manual_Parser для нахождения анкеров разметки в документе.

Анализ HTML-документа: тег за тегом

Далее подробно комментируется программная реализация решения описанной выше задачи. Приведенный наже программный код относится к классу Manual_Parser.

@Array – массив (инициализируется в конструкторе при создании объекта), в котором содержатся имена анкеров. Разметки, полученные в виде результирующего списка на стадии подготовки к анализу документа (см. ранее).

Процедура start() – замещает процедуру start() класса HTML::Parser, вызывается при нахождении стартового (начального) HTML-тега. Процедура работает со всем текстом документа, «физического» разбиения на главы на данном этапе не произодится – каждая глава рассматривается как составная часть документа.

sub start

{

 my $self = shift;

# Получение параметров тега (описание см. ранее)

 my ($tag, $attr, $attrseq, $origtext) = @_;

 my  @Temp_Cont_HREFs = @{$self->{Contents_HREFs}};

# Проверка: входит ли найденный стартовый тег в список тех тегов, для которых не обязательно указывать парный концевой тег

 if (!($self->Tag_Is_Exception_Check($tag)))

 {

 # Проверка: является ли найденный тег гиперссылкой или     

    анкером

  if ($tag eq “a”)

  {

   my $Last_Tag_Is_Cont_Href=0;

# Для каждого имени Анкера Разметки

   foreach $Temp (@Array)    

{

   # Проверка: найденный тег –  Анкер Разметки

    if ($attr->{“name”} eq $Temp && $attr->{“name”} ne “”)

   # Выставление флагов, изменения счетчиков

    {

    # Счетчик числа тегов в Главе (инициализируется в конструкторе при создании объекта) – значение равно порядковому номеру (в Главе)

    # анализируемого тега

     $self->{Tag_Num_In_Curr_Page}=0;

 # Счетчик числа страниц (инициализируется в конструкторе при создании объекта) – значение равно порядковому номеру текущей

 # анализируемой Главы

     $self->{Current_Page}++;

     $Last_Tag_Is_Cont_Href=1;

     last;

    }

   # Проверка: найденный тег – гиперссылка на одну из Глав

    elsif ($attr->{“href”} eq join(“”,’#’,$Temp))

    {

    # Сохранить в структуре данные о найденной Внутренней Гиперссылке на одну из Глав

     my $Temp2 = $attr->{“href”};

     $Temp2 =~ s/#//;

     $self->{Pages_Info}[$self->{Current_Page}]->{Local_Links_To_Pages}{$Temp2}=$self->{Current_Page};

     $Last_Tag_Is_Cont_Href=1;

    }

   }

  # Проверка: последний найденный тег – не Анкер Разметки и не Внутренняя Гиперссылка на одну из Глав

   if ($Last_Tag_Is_Cont_Href==0)

   {

   # Проверка: найденный тег –  анкер

    if ($attr->{“name”} ne “”)

    {

     $self->{Doc_Info}->{Local_Anchors}{$attr->{“name”}}=$self->{Current_Page};

    }

   # Проверка: найденный тег – Внутренняя Гиперссылка

    if ($attr->{“href”} =~ /^#/ )

    {

     my $Temp2 = $attr->{“href”};

     $Temp2 =~ s/#//;

     $self->{Pages_Info}[$self->{Current_Page}]->{Local_Links}{$Temp2}=$self->{Current_Page};

    }

   }

  }

 # Обновление статистики по найденному тегу в структуре данных по текущей Главе

  $self->{Pages_Info}[$self->{Current_Page}]->{Num_Of_Tags}++;

  $self->{Pages_Info}[$self->{Current_Page}]->{Tags}{«$tag»}->{Start_Tag_Num}++;

  $self->{Pages_Info}[$self->{Current_Page}]->{Tags_Seq_Page}{$self->{Tag_Num_In_Curr_Page}}->{Tag_Name}=$tag;

  $self->{Pages_Info}[$self->{Current_Page}]->{Tags_Seq_Page}{$self->{Tag_Num_In_Curr_Page}}->{Tag_Num_In_Doc} = $self->{Tag_Num_In_Doc};

 # Обновление статистики по найденному тегу в структуре данных по Документу

  $self->{Doc_Info}->{Tags}{«$tag»}->{Start_Tag_Num}++;

  $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}->{Tag_Name}=$tag;

  $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}->{Tag_Orig_Text}=$origtext;

  $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}->{Tag_Num_In_Page}=$self->{Tag_Num_In_Curr_Page};

  $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}->{Tag_Page_Num}=$self->{Current_Page};

  $self->{Doc_Info}->{All_Start_Tags_Num}++;

  $self->{Tag_Num_In_Curr_Page}++;

 # Счетчик числа тегов вдокументе (инициализируется в конструкторе при создании объекта)

  $self->{Tag_Num_In_Doc}++;

 }

}

Процедура end() – замещает процедуру end() класса HTML::Parser, вызывается при нахождении стартового (начального) HTML-тега. Процедура работает со всем текстом документа, «физического» разбиения на главы на данном этапе не произодится – каждая глава рассматривается как составная часть документа.

sub end

{

 my $self = shift;

# Получение параметров тега (описание см. ранее)

 ($tag, $origtext)=@_;

# Проверка: входит ли найденный стартовый тег в список тех тегов, для которых не обязательно указывать парный концевой тег

 if (!($self->Tag_Is_Exception_Check($tag)))

 {

 # Обновление статистики по концевым HTML-тегам для текущей Главы

  $self->{Pages_Info}[$self->{Current_Page}]->{Tags}{«$tag»}->{End_Tag_Num}++;

 # Обновление статистики по концевым HTML-тегам для всего Документа

  $self->{Doc_Info}->{Tags}{«$tag»}->{End_Tag_Num}++;

  $self->{Doc_Info}->{All_End_Tags_Num}++;

 }

}

Процедура _Separate_Manual_Into_Pages() – используется для выборки глав из HTML-кода документа и сохранения их в массиве в виде отдельных текстовых фрагментов. При этом их HTML-структура не откорректирована.

Для выборки требуемых фрагментов HTML-кода используются регулярные выражения. Синтаксис выражения зависит от того, какая именно глава должна быть выбрана (первая, последняя или любая из промежуточных).

sub _Separate_Manual_Into_Pages

{

 my $self = shift;

 my $Manual_Text = shift;

 my @Manual_Parts;

 for ($i=-1; $i<=$#Array; $i++)

 {

  # Проверка: выбираем из Документа первую Главу

  if($i==-1)

  {

   $Manual_Text =~ /(.*)<\s*a\s+name=»*$Array[$i+1]»*\s*>/is;

   $Manual_Parts[$i+1]=$1;

  }

  # Проверка: выбираем из Документа промежуточную Главу

  elsif ($i<$#Array)

  {

   $Manual_Text =~ /(<\s*a\s+name=»*$Array[$i]»*\s*>.*<\s*\/a\s*>.*)<\s*a\s+name=»*$Array[$i+1]»*\s*>/is;

   $Manual_Parts[$i+1]=$1;

  }

  # Проверка: выбираем из Документа последнюю Главу

  else

  {

   $Manual_Text =~ /(<\s*a\s+name=»*$Array[$i]»*\s*>.*<\s*\/a\s*>.*)/is;

   $Manual_Parts[$i+1]=$1;

  }

 }

 # Возвращает массив, каждый элемент которого содержит текст одной из Глав

 return @Manual_Parts;

}

Процедура Generate_Manual_Parts() – в данной процедуре реализован анализ статистики , собранной ранее по главам и документу. На основании анализа корректируется HTML-структура глав.

sub Generate_Manual_Parts

{

 my $self = shift;

# Получение Глав в виде отдельных фрагментов HTML-кода и сохранение их в массив

 my @Manual_Parts=$self->_Separate_Manual_Into_Pages($Manual_Text);

# Инициализация счетчика страниц

 my $Current_Page_Num=0;

# Для каждой полученной Главы

 for (my $i=0; $i<=$#Manual_Parts; $i++)

 {

  $Current_Page_Num=$i;

 # Для каждого тега анализируемой Главы

  for (my $j=0; $j<$self->{Pages_Info}[$i]->{Num_Of_Tags}; $j++)

  {

  # Запомнить имя (тип) тега в $Current_Tag_Name

   my $Current_Tag_Name = $self->{Pages_Info}[$i]->{Tags_Seq_Page}{$j}->{Tag_Name};

  # Проверка: для тегов запомненного типа количество стартовый и концевых тегов в Главе не совпадает

   if ($self->{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{Start_Tag_Num} != \

    $self->{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{End_Tag_Num})

   {

   # Пометить текущую страницу как стартовую в рамках итерационного поиска парного завершающего тега

    $self->{Work_Container}->{Start_Page_Num}=$i;

  # Занести в «рабочий контейнер» тип парного концевого тега, который необходимо найти в одной из Глав

    $self->{Work_Container}->{Tag_Name}=$Current_Tag_Name;

  # Занести в «рабочий контейнер» порядковый номер текущего стартового тега в Документе

    $self->{Work_Container}->{Tag_Num_In_Doc} = $self->{Pages_Info}[$i]->{Tags_Seq_Page}{$j}->{Tag_Num_In_Doc};

  # Занести в «рабочий контейнер» порядковый номер текущего стартового тега в Главе

    $self->{Work_Container}->{Tag_Num_In_Page}=$j;

Инициализировать счетчик: его значение соответствует общему числу стартовых тегов – тегов запомненного типа, обнаруженных в главах, которые будут проанализированы на конкретной итерации поиска парного концевого тега:

$self->{Work_Container}->{Start_Tag_Num_Total} = $self->{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{Start_Tag_Num};

Инициализировать счетчик: его значение соответствует общему числу концевых тегов запомненного типа, обнаруженных в главах, которые будут проанализированы на конкретной итерации поиска парного концевого тега:

$self->{Work_Container}->{End_Tag_Num_Total} = $self->{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{End_Tag_Num};

До тех пор пока не будет найден парный концевой тег или не будут просмотрены все главы:

>{End_Tag_Num};

    }

   # Проверка: концевой тег был найден

    if ($Current_Page_Num <= $#Manual_Parts)

    {

    # Для всех Глав участвовавших в итеарционном поиске парного тега

     for ($k=$self->{Work_Container}->{Start_Page_Num}; $k <= $Current_Page_Num; $k++)

     {

      # Для стартовой Главы итерационного поиска

      if ($k == $self->{Work_Container}->{Start_Page_Num})

      {

      # Запомнить порядковый номер тега в Документе, для которого необходимо добавить парный концевой тег в Главе

       push (@{$self->{Pages_Info}[$k]->{End_Tags_To_Add}}, $self->{Work_Container}->{Tag_Num_In_Doc});

       $self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}->{End_Tag_Num}++;

       $self->{Doc_Info}->{Tags}{$Current_Tag_Name}->{End_Tag_Num}++;

      }

      # Для заключительной Главы итерационного поиска

      elsif ($k == $Current_Page_Num)

      {

      # Запомнить порядковый номер тега в Документе, для которого необходимо добавить парный стартовый тег в Главе

       push (@{$self->{Pages_Info}[$k]->{Start_Tags_To_Add}}, $self->{Work_Container}->{Tag_Num_In_Doc});

       $self->{Doc_Info}->{Tags}{$Current_Tag_Name}->{Start_Tag_Num}++;

       $self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}->{Start_Tag_Num}++;

      }

      # Для промежуточных Глав итерационного поиска

      else

      {

      # Запомнить порядковый номер тега в Документе, стартовый и концевой теги которго будут добавлены в промежуточную страницу

       push (@{$self->{Pages_Info}[$k]->{Start_Tags_To_Add}}, $self->{Work_Container}->{Tag_Num_In_Doc});

       $self->{Doc_Info}->{Tags}{$Current_Tag_Name}->{Start_Tag_Num}++;

       $self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}->{Start_Tag_Num}++;

       push (@{$self->{Pages_Info}[$k]->{End_Tags_To_Add}}, $self->{Work_Container}->{Tag_Num_In_Doc});

       $self->{Doc_Info}->{Tags}{$Current_Tag_Name}->{End_Tag_Num}++;

       $self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}->{End_Tag_Num}++;

      }

     }

    }

   }

  }

 }

 # Для всех Глав

 for (my $i=0; $i<=$#Manual_Parts; $i++)

 # Выполнить добавление недостающих стартовых и концевых тегов (корректировка HTML-структуры)

 {

  @{$self->{Pages_Info}[$i]->{Start_Tags_To_Add}}= sort {$b <=> $a} @{$self->{Pages_Info}[$i]->{Start_Tags_To_Add}};

  @{$self->{Pages_Info}[$i]->{End_Tags_To_Add}} = sort {$b <=> $a} @{$self->{Pages_Info}[$i]->{End_Tags_To_Add}};

   # Добавить стартовые теги

  foreach my $value (@{$self->{Pages_Info}[$i]->{Start_Tags_To_Add}})

  {

   $Manual_Parts[$i]=join (“”, $self->{Doc_Info}->{Tags_Seq_Doc}{$value}->{Tag_Orig_Text}, $Manual_Parts[$i]);

  }

  # Добавить концевые теги

  foreach my $value (@{$self->{Pages_Info}[$i]->{End_Tags_To_Add}})

  {

   $Manual_Parts[$i]=$Manual_Parts[$i].’</’.$self->{Doc_Info}->{Tags_Seq_Doc}{$value}->{Tag_Name}.’>’;

  }

 }

 # Вернуть Главы с откорректированной HTML-структурой

 return @Manual_Parts;

}

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

Корректировка Внутренних Гиперссылок на Главы

 foreach $key (keys %{$self->{Pages_Info}[$i]->{Local_Links_To_Pages}})

 {

  $Manual_Parts[$i] =~ s/(<\s*?a\s{1}.*?href\s*?=\s*?»?)#$key(«?\s?.*?>)/ \

    $1строка_http-запроса_указывающая_путь_с_точностью_до_документа&page=$i$2/is;

 }

 # Корректировка прочих Внутренних Гиперссылок

 foreach $key (keys %{$self->{Pages_Info}[$i]->{Local_Links}})

 {

  $Manual_Parts[$i] =~ s/(<\s*?a\s{1}.*?href\s*?=\s*?»?)(#$key»?\s?.*?>)/ \

    $1строка_http-запроса_указывающая_ путь_с_точностью_до_документа&page=$self->{Doc_Info}->{Local_Anchors}{$key}$2/is;

 }

Примечание: если ограничиться таким способом применения элементов Local_Links и Local_Links_To_Pages из структуры данных Pages_Info (см. ранее), то вместо хэшей могут быть использованы обычные списки.

Вместо заключения

Возможности Perl по работе с текстом действительно велики. Это прописная истина, и в ней можно убеждаться снова и снова.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru