Парсинг XML/HTML Free Pascal библиотека Internet Tools. Часть 1

В данном документе я рассмотрю несколько примеров использования библиотеки Internet Tools для обработки XML/HTML документов.

В качестве инструмента будет использованы htmlparserExampleGUI или Xidel онлайн версия для любого браузера, также вы можете скачать Xidel для командной строки для различных операционных систем (на данный момент новая версия 0.8.4). htmlparserExampleGUI находится в папке internettools/data/examples/ в некомпилированном виде. Для компиляции в проекте IDE Lazarus требуется указать пути к файлам библиотеки. В качестве образца используется вот этот шаблон. В online Xidel'е документ вставляется в поле "HTML/XML-Input file", в htmlparserExampleGUI в поле "HTML-File" как это на скриншотах ниже.

Окно Xidel для парсинга XML HTML документов
В онлайн инструменте Xidel
Окно программы для парсинга XML HTML документов
В окне скомпилированной программы htmlparserExampleGUI

HTML документы. Примеры.

Данная библиотека предназначена для парсинга XML/HTML документов, имеет необходимый набор для работы с сайтами (инструменты для GET и POST запросов). Библиотека и созданные на её основе Xidel способны обрабатывать любые документы с ошибками: незавершенные тэги; неправильные атрибуты, и т.д.

В программе htmlparserExampleGUI по умолчанию включен режим "Verbose output", это означает что в результаты анализа программа добавит описание. Описание определяет к какому типу данных относится элемент. Есть несколько типов: node() - тип данных "узел"; string, integer, double - строковый, числовой, двойной числовой тип; sequence - последовательность из предыдущих типов данных.

1. Поиск ссылок в документе. Следующий запрос вставляется в поле Template.

<body template:optional="true">
<a>*{@href}</a>
</body>

Запрос вернет адреса всех ссылок в пределах тэга BODY. Указатель template:optional="true" (сокращенная форма t:optional="true") в тэге означает необязательность, иначе если в документе нет тэга BODY, то парсер вернет ошибку. Используется короткий вид запроса в виде фигурных скобок (Short notation). Подробнее про виды запросов вы можете прочесть в документации к библиотеке и в начальной статье. Знак звездочка * перед фигурной скобкой значит, что тэг может присутствовать 0 или больше раз.

Квантификаторы:

  • * 0 или более раз
  • + это 1 или более раз
  • ? это 0 или 1 раз
  • {min,max} от и до, только в шаблоне template:loop
  • {число} заданное кол-во раз, но только в шаблоне template:loop

Самое значение @href означает атрибут как в CSS или XPath. В данном запросе это атрибут ссылки href. Template-запрос это почти XML-документ и должен соответствовать правилам этого формата, например у каждого открывающего тэга должен быть закрывающий тэг, исключения это самозакрывающиеся тэги (<br />).

В Xidel запрос вернет:

http://example.com
mailto:[email protected]
index.html
http://www.unicode.org/versions/Unicode4.0.0/ch06.pdf
http://example.com
links.html

В htmlparserExampleGUI результат будет с дополнительной информацией о узлах и последовательностях, так как результат в виде массива данных:

_result=node(): http://example.com
_result=node(): mailto:[email protected]
_result=node(): index.html
_result=node(): http://www.unicode.org/versions/Unicode4.0.0/ch06.pdf
_result=node(): http://example.com
_result=node(): links.html

Дальнейшие запросы буду приводить результаты только программы htmlparserExampleGUI. Этот же запрос в виде XPath //body//a/@href

 

2. Запрос для поиска email адреса:

<body template:optional="true">
<a template:condition="starts-with(@href,'mailto:')">*{@href}</a>
</body>

Ответ:

_result=node(): mailto:[email protected]

Указатель template:condition (сокращенная форма t:condition) в запросе имеет XPath выражение starts-with(@href,'mailto:'), т.е. парсер вернет только те ссылки которые начинаются на "mailto:". Этот же запрос в XPath //body​//a/@href[starts-with(.,'mailto:')]​

 

3. Поиск ссылок при наличии определенного атрибута в тэге. Запрос с дополнительными условием template:condition="@lang='en'" в тэге HTML. Условие означает, что нужны ссылки где в тэге HTML атрибут lang равен en. Если значение lang поменять на допустим ru, то запрос возвратит пустой результат. В виде XPath //html[@lang='en']//body//a/@href

<html template:optional="true" template:condition="@lang='en'">
<body template:optional="true">
  <a>*{@href}</a>
</body>
</html>

Результат:

_result=node(): http://example.com
_result=node(): mailto:[email protected]
_result=node(): index.html
_result=node(): http://www.unicode.org/versions/Unicode4.0.0/ch06.pdf
_result=node(): http://example.com
_result=node(): links.html

 

4. Поиск ссылок при нескольких условиях. Следующий запрос усложняет условия поиска. Теперь необходимо найти ссылки при нескольких условиях.

<body template:optional="true">
<h2>Links</h2>
<p><a>*{@href}</a><cite /></p>
</body>

Результат:

_result=node(): http://example.com

Данный запрос весьма сложный и результат требует некоторого разбора. Парсер вернул все ссылки, которые могли быть в тэге BODY. По условию ссылки должны быть ниже чем тэг H2 с содержимым Links. Следующее условие ссылки должны быть внутри тэга абзаца <p> и должны быть перед тэгом цитирования <cite>. В XPath-запросе нужны соответствия условию, получилось вот это //body//h2[.='Links']/following::p/.[not(self::cite)]/a/@href​. Если ссылку обернуть внутрь какого-либо другого тэга, допустим <span>, то XPath выражение перестает работать. Template-запрос будет выполнен независимо от любого уровня вложенности.

 

5. Поиск ссылок при нескольких условиях. Переделав предыдущий запрос, с условием что требуется ссылки внутри тэга P и CITE, при этом сестринский тэг ссылки должен присутствовать в HTML-документе.

<body template:optional="true">
<h2>Links</h2>
<p><a /><cite><a>*{@href}</a></cite></p>
</body>

Этот же запрос в виде XPath //body//h2[.='Links']/following::p/.[preceding::a]/cite/a/@href​. Результат:

_result=node(): links.html

 

6. Пример запроса с возвращением массива данных:

<body template:optional="true">
<a>*{@href,.}</a>
</body>

Ответ вернул последовательности sequence с содержимым атрибута href и самим текстом ссылки. XPath вид //body//a/@href|//body//a/., результат:

_result=sequence: (node(): http://example.com, node(): example.com)
_result=sequence: (node(): mailto:[email protected], node(): [email protected])
_result=sequence: (node(): index.html, node(): main page)
_result=sequence: (node(): http://www.unicode.org/versions/Unicode4.0.0/ch06.pdf, node(): Unicode Standard,
chapter 6)
_result=sequence: (node(): http://example.com, node(): usability)
_result=sequence: (node(): links.html, node(): Links Want To Be Links)

 

7. Следующий пример с функциями:

<body template:optional="true">
<a>*{@href,.,random()}</a>
</body>

Результат вернет что и предыдущий запрос но будет добавлена функция генерации случайных чисел random(). Сам XPath не генерирует случайные числа.

_result=sequence: (node(): http://example.com, node(): example.com, double: 0.105907606426626)
_result=sequence: (node(): mailto:[email protected], node(): [email protected], double: 0.414661941118538)
_result=sequence: (node(): index.html, node(): main page, double: 0.473600422730669)
_result=sequence: (node(): http://www.unicode.org/versions/Unicode4.0.0/ch06.pdf, node(): Unicode Standard,
chapter 6, double: 0.264555611414835)
_result=sequence: (node(): http://example.com, node(): usability, double: 0.186332344543189)
_result=sequence: (node(): links.html, node(): Links Want To Be Links, double: 0.774233686504886)

 

8. Запрос с функцией со случайными числами и с помощью шаблона <template:if test=""> (<t:if test=""> сокращенная форма этого шаблона).

<body template:optional="true">
<a>*<t:if test="random()>=0.5">{@href,.}</t:if></a>
</body>

или сделать еще короче:

<body template:optional="true">
<a t:condition="random()>=0.5">*{@href,.}</a>
</body>

Запрос выдает ссылки, где будет выполнено условие в шаблоне <t:if test="random()>=0.5">. Если случайное число больше или равно 0.5, то ссылка попадает в результат. В результате всегда будут набор случайных ссылок, возможно что запрос вернет пустой ответ.

 

9. Еще запрос с функцией string-length().

<body template:optional="true">
<a t:condition="(random()*string-length(@href))>20">*{@href,.,string-length(@href)}</a>
</body>

Ответ будет случайным в зависимости от генератора случайных чисел.

 

10. Поиск тэга по тексту ссылки. Библиотека использует регистронезависимый поиск.

<a t:condition="text()='links want to be links'">{@href}</a>

или

<a template:optional="true"><t:s>@href</t:s>links want to be links</a>

Ответ вернет адрес ссылок:

_result=node(): links.html

 

11. Найти тэг в котором присутствует атрибут http-equiv

<meta t:condition="@http-equiv">?{@content}</meta>

Ответ:

_result=node(): text/html; charset=us-ascii

 

12. Получить все атрибуты for для тэга LABEL

<label>*{@for}</label>

Ответ

_result=node(): but
_result=node(): f0
_result=node(): f1
_result=node(): f2
_result=node(): f3
_result=node(): f4
_result=node(): f5
_result=node(): f6
_result=node(): f10
_result=node(): f11
_result=node(): f99

В данной статье я не описал все шаблоны и функции, их можно найти в документации в библиотеке. Продолжение.