Учебник по XPath

Этот учебник — введение в XPath, язык запросов, который можно использовать для выбора произвольных частей HTML в calibre. XPath — широко используемый стандарт, и поиск Google выдаст тонну информации. Однако этот учебник сосредотачивается на решении с помощью XPath задач, связанных с электронными книгами, например поиске заголовков разделов в неструктурированном HTML-документе.

Выбор по имени тега

Простейшая форма выбора — выбор тегов по имени. Предположим, что вы хотите выбрать все теги <h2> в документе. Запрос XPath для этого прост:

//h:h2        (Selects all <h2> tags)

Префикс // означает поиск на любом уровне документа. Теперь предположим, что вы хотите искать теги <span>, которые находятся внутри тегов <a>. Этого можно достичь с помощью:

//h:a/h:span    (Selects <span> tags inside <a> tags)

Если вы хотите искать теги на определённом уровне в документе, измените префикс:

/h:body/h:div/h:p (Selects <p> tags that are children of <div> tags that are
             children of the <body> tag)

Это будет соответствовать только <p>A very short e-book to demonstrate the use of XPath.</p> в разделе «Образец электронной книги», а не каким-либо другим тегам <p>. Префикс h: в вышеприведённых примерах необходим для соответствия тегам XHTML. Потому что внутри calibre всё содержимое представлено как XHTML. В тегах XHTML есть пространство имён, а h: — префикс пространства имён для HTML-тегов.

Теперь предположим, что вы хотите выбрать теги <h1> и <h2>. Для этого нам нужна конструкция XPath, называемая предикат. A предикат — это просто тест, который используется для выбора тегов. Тесты могут быть сколь угодно мощными, и по мере продвижения по этому учебнику вы увидите более мощные примеры. Предикат создаётся путём заключения тестового выражения в квадратные скобки:

//*[name()='h1' or name()='h2']

В этом выражении XPath имеется несколько новых функций. Во-первых, это использование подстановочного символа *. Это означает соответствие любому тегу. Теперь посмотрим на тестовое выражение name()='h1' or name()='h2'. name() — пример встроенной функции. Она просто вычисляет имя тега. Таким образом, с её помощью мы можем выбрать теги, имена которых либо h1, либо h2. Обратите внимание, что функция name () игнорирует пространства имён, поэтому нет необходимости в префиксе h:. XPath имеет несколько полезных встроенных функций. Некоторые из них будут показаны в этом учебнике.

Выбор по атрибутам

Чтобы выбрать теги на основе их атрибутов, необходимо использовать предикаты:

//*[@style]              (Select all tags that have a style attribute)
//*[@class="chapter"]    (Select all tags that have class="chapter")
//h:h1[@class="bookTitle"] (Select all h1 tags that have class="bookTitle")

Здесь оператор @ ссылается на атрибуты тега. Вы можете использовать некоторые из XPath built-in functions (встроенных функций XPath) для выполнения более сложных сопоставлений значений атрибутов.

Выбор по содержимому тега

Используя XPath, вы можете выбирать теги даже на основе содержащегося в них текста. Лучший способ сделать это — призвать силу регулярных выражений с помощью встроенной функции re:test ():

//h:h2[re:test(., 'chapter|section', 'i')] (Selects <h2> tags that contain the words chapter or
                                          section)

Здесь оператор . ссылается на содержимое тега, так же как оператор @ ссылается на его атрибуты.

Образец электронной книги

<html>
    <head>
        <title>A very short e-book</title>
        <meta name="charset" value="utf-8" />
    </head>
    <body>
        <h1 class="bookTitle">A very short e-book</h1>
        <p style="text-align:right">Written by Kovid Goyal</p>
        <div class="introduction">
            <p>A very short e-book to demonstrate the use of XPath.</p>
        </div>

        <h2 class="chapter">Chapter One</h2>
        <p>This is a truly fascinating chapter.</p>

        <h2 class="chapter">Chapter Two</h2>
        <p>A worthy continuation of a fine tradition.</p>
    </body>
</html>

Встроенные функции XPath

name()

Имя текущего тега.

contains()

contains(s1, s2) возвращает true, если s1 содержит s2.

re:test()

re:test(src, pattern, flags) возвращает true, если строка src соответствует регулярному выражению pattern. Особенно полезен флаг i: он отвечает за нечувствительность к регистру. Хороший учебник синтаксиса регулярных выражений для начинающих можно найти по ссылке regexp syntax.