XPath - Что это такое

XPath (XML Path Language) - это язык запросов, используемый для навигации и поиска информации в XML-документах. Он позволяет точно указать путь к элементам, атрибутам и текстовым данным, которые нужно найти в структуре XML файлов и получить данные при необходимости.

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

Для чего нужен XPath

XPath используется для навигации и выбора элементов XML-документов. Он позволяет находить пути к элементам и их атрибутам, а также выполнять операции с ними: поиск, фильтрация, выборка.

Вот некоторые основные случаи использования XPath:

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

2) Извлечение данных: XPath позволяет извлекать конкретные значения из XML-документа. Например, можно получить содержимое элемента или значение его атрибута.

3) Проверка условий: XPath позволяет задавать условия и проверять их в XML-документе. Например, можно проверить, что значение атрибута соответствует определенному критерию или что в XML-документе есть определенный элемент.

4) Навигация по структуре: XPath позволяет перемещаться по структуре XML-документа, находить конкретные узлы и выполнять с ними операции. Например, можно перейти к родительскому или дочернему элементу, выбрать первый или последний элемент, перейти на следующий или предыдущий элемент, а также совершать другие действия для исследования структуры XML-документа.

Использование XPath позволяет эффективно и удобно работать с XML-данными, особенно при обработке и анализе больших объемов информации.

Кому и зачем нужен Xpath

Если коротко — XPath нужен всем, кто работает с XML.

XPath широко используется в различных областях: программирование, веб-скрапинг, автоматизацию тестирования и анализ данных. Язык запросов позволяет легко и эффективно обращаться к нужным элементам в структурированных документах XML к нужным элементам, совершать какие то операции или получить необходимые данные.

Это лишь часть сценариев, в когда язык XPath может помочь — на самом деле, сценариев может быть намного больше.

Синтаксис XPath

Для выбора узлов в XML документе с помощью XPath используются выражения путей. Выборка узла производится следуя по заданному пути или по, так называемым, шагам.

Синтаксис XPath описывает правила для создания выражений, которые позволяют точно указать, какие элементы и атрибуты должны быть выбраны из XML-структуры. Точно указать, какой элемент выбрать нужно с помощью XPath можно при помощи:

1) Выбора узлов

2) Пути выбора элементов

3) Оси направления выбора

4) Функций

5) Предикатов

Выбор узлов

Чтобы выбрать нужный узел в XML документе, нужно обратиться к нему при помощи языка запросов XPath, указав путь к нужному элементу. Узел выбирается следуя по заданному пути.

Базовый синтаксис XPath для парсинга элементов:

1) * — Выбрать любой элемент.

2) [] — Найти конкретный элемент. Пример: //li[1]

имя_узла — Выбирает все узлы с указанным именем узла. Пример: div, p и т.д.

3) / — Ищет от корневого узла html

4) // — Ищет узлы в документе от текущего узла, который соответствует выбору, независимо от того, где они находятся

5) . — Ищет текущий узел

6) .. — Ищет родителя текущего узла

7) @ — Ищет нужный атрибут. Пример: //p[@value="2008"]

Абсолютные и относительные пути обращения к элементам


К конкретному элементу можно обратиться двумя способами:

1) Абсолютным. Абсолютный xpath начинается со слеша ( / ) и указывает на полный путь от корневого узла до целевого узла.

Пример выражения в XPath: /html/body/div[1]/h1

Конструкция означает: "выбрать первый элемент div, который находится по пути html > body > div и затем выбрать его дочерний элемент h1".

2) Относительным. Относительный путь начинается с двух слешей (//) и указывает на путь от любого узла, который соответствует определенным критериям, до целевого узла. Пример: //div[@id='main']

Конструкция означает: "выбрать любой элемент div, который имеет атрибут id со значением 'main'".

Общие функции XPath

Язык XPath имеет набор встроенных функций, которые позволяют осуществлять различные операции с XML-документами. Вот некоторые из основных функций XPath:

1) text() - возвращает текстовое содержимое элемента.

2) count() - возвращает количество элементов, соответствующих указанному выражению.

3) normalize-space() - удаляет лишние пробелы из строки и заменяет последовательности пробелов на одиночные пробелы.

4) starts-with(x,y) - проверяет, начинается ли строка с x-y.

5) contains(x,y) - проверяет, содержит ли строка x-y.

6) last() - возвращает последнюю позицию элемента в выборке.

7) position() - возвращает позицию текущего элемента в выборке.

8) name() - возвращает имя текущего элемента.

9) sum() - суммирует значения элементов выборки.

10) string() - преобразует узел в строку.

11) lower-case() - преобразует текст в нижний регистр.

12) @attribute - выбирает значение указанного атрибута.

13) concat() - объединяет две или более строки.

14) string-length() - возвращает длину строки.

15) substring() - возвращает подстроку из строки, начиная с указанной позиции.

Кроме этих основных функций, XPath также предоставляет более сложные функции, такие как функции математических операций (например, floor(), round(), sum()), функции работы с датами и временем и другие.

Оси XPath

Ось в XPath - это специальная концепция, которая используется для указания направления движения при поиске элементов в документе. Оси позволяют выбирать элементы, которые относятся к определенным отношениям с другими элементами в дереве XML или HTML.

В XPath есть несколько осей, которые можно использовать при создании выражений:

1) child::note — Выбирает все узлы note, которые являются прямыми потомками текущего узла

2) attribute::date — Выбирает атрибут date текущего узла

3) child::* — Выбирает всех прямых потомков текущего узла

4) attribute::* — Выбирает все атрибуты текущего узла

5) child::text() — Выбирает все текстовые узлы текущего узла

6) child::node() — Выбирает всех прямых потомков текущего узла

7) descendant::note — Выбирает всех потомков note текущего узла

8) ancestor::note — Выбирает всех предков note текущего узла

9) ancestor-or-self::note — Выбирает всех предков note текущего узла, а также сам текущий узел, если это узел note

10) child::*/child::heading — Выбирает всех прямых потомков прямых потомков (“внуков") heading текущего узла

11) last() — Выделяет последний элемент в дереве.

Предикаты (Коллекции)

Предикаты — конструкции, которые используются для фильтрации элементов и выбора конкретных элементов с помощью определенных условий.

Вот несколько примеров использования предикатов в XPath для :

1) //a — Выберите все элементы в документе

2) //a[@class='active'] — Выберите все элементы, у которых атрибут class равен "active"

3) //input[@type='checkbox'] — Выберите все элементы input, у которых атрибут type равен "checkbox"

4) //input[@type='text'] — Выберите все элементы input с атрибутом type равным "text"

5) //p[contains(text(), 'Lorem')] — Выберите все элементы, у которых текст содержит слово "Lorem"

6) //div[count(p) > 3] — Выберите все элементы, у которых количество дочерних элементов больше 3

7) //a[starts-with(@href, 'https://')] — Выберите все элементы, у которых атрибут href начинается с "https://"

8) //input[matches(@value, '^\d+$')] — Выберите все элементы input, у которых атрибут value содержит только цифры

9) //p[@value="01/2008"] — Выбирает все элементы p, у которых есть атрибут value со значением "01/2008"

10) //p[@value] — Выбирает все элементы p, у которых есть атрибут value

11) //p/text() — Выделит все текстовые узлы внутри всех элементов p

12) //div[not(p)] — Выберите все элементы, у которых не существует дочернего элемента

13) /div/p[position()<3] — Выбирает первые два элемента p, которые являются прямыми потомками элемента div

14) //tag[position()=1] — Выбирает первый элемент с тегом "tag"

15) /div/ul/li[1] — Выбирает первый элемент li, который является прямым потомком элемента ul в div

16) //li[a] — Выделяет элементы li, в которых есть элемент a

17) //li[last()] — Выделяет последний элемент li в документе

18) //a | //h2 - Выделить все элементы a и h2 с помощью оператора объединения |

19) //tag[@value > 9] — Получить узлы tag, value которых больше 9-ти.

20) //div[note[@value > 9]]/а — Получить только имена узлов, value которых больше 9-ти

21) //div[4]/h2[text() = "Текст"] — Выделит четвертый элемент div, h2 которого содержит слово: Текст

22) /div/note[last()] — Выбирает последний элемент note, который является прямым потомком элемента div

23) /div/note[last()-1] — Выбирает предпоследний элемент note, который является прямым потомком элемента div

24) //*[@id] — Выберите все элементы с атрибутом id

25) //div[contains(@class, 'content')]/p — Выберите все элементы, которые являются дочерними элементами с классом "content"

26) //img[contains(@src, 'logo')] — Выберите все элементы , у которых атрибут src содержит слово "logo"

27) //*[@href[contains(text(), '.pdf')]] — Выберите все элементы со значением атрибута href, оканчивающимся на .pdf

28) //*[@data-toggle='modal'] — Выберите все элементы с атрибутом data-toggle и значением "modal"

29) //tag[@attribute>5] — Выбирает все элементы с тегом "tag" и атрибутом "attribute", значение которого больше 5

30) //tag[@attribute="value"] — Выбирает все элементы с тегом "tag" и атрибутом "attribute" со значением "value"

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

Какие данные можно извлечь с сайта при помощи XPath и парсера

XPath и парсеры могут извлекать различные данные с веб-сайтов. Вот несколько типов данных, которые можно получить при помощи этих инструментов:

Количество товаров в категориях;
Описание, характеристики и цену товара;
Изображения;
Наличие и количество отзывов;
Количество просмотров статей;
Количество лайков у статей;
Почтовые ящики;
Хлебные крошки;
Страницы с видео;
Какие страницы, имеют сколько отзывов;
На каких страницах размещен определенный текстовый блок или нет;
Даты публикаций статей или товаров;
Рейтинги товаров или статей.
Это лишь некоторые примеры того, что можно извлечь с помощью XPath. Фактически, вы можете извлекать любые данные или атрибуты, доступные на веб-странице в зависимости от ее структуры и организации информации.

Формулы по извлечению HTML атрибутов которые будут полезны СЕОшникам
1) //@href — Извлечь все ссылки

2) //a[starts-with(@href,'mailto')]/@href — Получить ссылки, которые содержат “mailto” (email адрес)

3) //a[contains(@href,'tel:')]/@href — Получить все телефоны

4) //img/@src — Получить все URL адреса картинок

5) //img[contains(@class,'name-class')]/@src — Получить все URL адреса источников изображений с именем класса

6) //div[@class='class'] — Получить элементы в тегах с определенным классом, указанным в кавычках

7) count(//h2) — Посчитать количество элементов на странице

8) //*[@itemtype]/@itemtype — Извелчь какие типы микроразметок есть на странице

9) //*[@itemprop='price']/@content — Узнать значение поля свойства разметки

10) //iframe[contains(@src,'https://www.youtube.com/')]/@src — Найти ссылки на все видео с ютуба

11) //table[@class='product-table']//td/text() — Извлечь текст всех ячеек таблицы с классом "product-table"

12) //td[contains(text(),'Weight')]/following-sibling::td — Извлечение данных из ячеек таблиц

13) contains(//meta[@name='description']/@content, 'таргетированное-ключевое-слово') — Проверяет наличие ключевого слова в мета теге

14) //link[@rel=’amphtml’]/@href — Получить URL-адрес AMP

15) //form[*]/input[@type='text']/@name — Имена всех текстовых полей внутри форм

Это далеко не весь список, чего можно достать с сайта конкурента. Более того, в конкретном случае, синтаксис будет разный. Составить формулу можно при помощи расширения XPath Helper или нажав правой кнопкой на html элементе и выбрать скопировать XPath

Как извлечь данные при помощи XPath со страницы

Для извлечения данных со страниц, я использую расширение "XPath Helper".

1) Установите XPath Helper для браузера Google Chrome https://chrome.google.com/webstore/detail/xpath-helper/hgimnogjllphhhkhlmebbmlgjoejdpjl

2) После установки, обновите страницу или откройте новую вкладку и перейдите на любую веб-страницу.

3) Нажмите Ctrl-Shift-X (или Command-Shift-X в OS X) или нажмите кнопку XPath Helper на панели инструментов, чтобы открыть консоль XPath Helper.

 
3) Удерживая нажатой клавишу Shift, наведите указатель мыши на элементы на странице. Поле запроса будет постоянно обновляться, чтобы отображать запрос XPath для элемента под указателем мыши, а в окне результатов будут отображаться результаты для текущего запроса.

4) При желании отредактируйте XPath-запрос прямо в консоли. В окне результатов немедленно отразятся ваши изменения.

 
5) Повторите шаг (3), чтобы закрыть консоль.

Если консоль вам мешает, удерживайте нажатой клавишу Shift, а затем наведите на нее указатель мыши; он переместится на противоположную сторону страницы.

XPath. Основы построения запросов

Что же представляет из себя xpath-запрос?

XPath (XML Path Language) — язык запросов к элементам XML или XHTML документа. XML имеет древовидную структуру. В документе всегда имеется корневой элемент. У элемента дерева всегда существуют предки (исключение — корневой элемент, у которого предков нет) и могут существовать потомки. Каждый элемент дерева находится на определенном уровне вложенности. У элементов на одном уровне бывают предыдущие и следующие за ним элементы. Строка XPath — это фактически путь к элементу в дереве, где каждый уровень разделяется косой чертой «/». В результате обработки выражения XPath получается объект, который может быть:
набор узлов (node-set) — неупорядоченный набор узлов без дубликатов
булево значение (boolean) — true или false
число (number) — число с плавающей точкой
строка (string) — последовательность UCS символов
 

Небольшой примерчик для старта:
Результатом выполнения следующего XPath запроса будет узел <span>:
/html/body/*/span

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


Следующий запрос вернет несколько элементов (div#first и div#second):
/html/body/div

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div> <div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


* — обозначает любое имя или набор символов
/ — определяет уровень дерева
Если в запросе первым символом стоит «/» , то путь адресации считается абсолютным (то есть от корня документа). Корень документа всегда является контекстом по умолчанию. Контекст — это текущий полученный узел или набор узлов, относительно которых рассчитывается следующий шаг.
Оси
Оси это основа запросов XPath и их обязательная часть.
ancestor:: — возвращает множество предков.
ancestor-or-self:: — возвращает множество предков и текущий элемент.
attribute:: — возвращает множество атрибутов текущего элемента.
child:: — возвращает множество потомков на один уровень ниже.
descendant:: — возвращает полное множество потомков.
descendant-or-self:: — возвращает полное множество потомков и текущий элемент.
following:: — возвращает необработанное множество, ниже текущего элемента.
following-sibling:: — возвращает множество элементов на том же уровне, следующих за текущим.
namespace:: — возвращает множество имеющее пространство имён (то есть присутствует атрибут xmlns).
parent:: — возвращает предка на один уровень назад.
preceding:: — возвращает множество обработанных элементов исключая множество предков.
preceding-sibling:: — возвращает множество элементов на том же уровне, предшествующих текущему.
self:: — возвращает текущий элемент.
Для наиболее часто используемых осей существуют сокращения:
attribute:: — можно заменить на «@»
child:: — часто просто опускают
descendant:: — можно заменить на «.//»
parent:: — можно заменить на «..»
self:: — можно заменить на «.»
Для приведенного выше примера
/html/body/*/span
полный синтаксис будет иметь вид
/child::html/child::body/child::*/child::span
Чаще xpath-запрос начинают с «.//» или «//», это делает путь к элементу относительным. Символы ".//" в начале запроса возвращают полное множество потомков, которые являются дочерними для корня документа, т.е. все элементы на текущей странице. В данном случае точку в начале запроса можно опустить, потому что корневой элемент уже является контекстом.
Первый пример можно переписать так:
.//div/span

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


В то время как следующий xpath-запрос вернет оба элемента span:
.//span

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


Важную роль в построение запросов играют предикаты — это необязательная часть, заключаемая в квадратные скобки, в которой могут содержаться оси, условия проверки, функции и операторы. В ходе обработки предиката из полученного на шаге набора узлов исключаются узлы, не прошедшие условие проверки. Например, найдем элемент span, находящийся внутри любого элемента с id = «first»
.//*[@id="first"]/span

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


Примеры использования осей при построении запросов:
1. following-sibling::
.//*[@id="first"]/following-sibling::span
или
.//*[@id="second"]/following-sibling::*[1]

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>


2. parent::
.//span[1]/..

<html>
<body>
<div id="first">
<span>span внутри div</span>
</div>
<div id="second"></div>
<span>некоторый текст</span>
</body>
</html>

Математика и XSLT

<xsl:template match="numbers">
  A. 4 + 3.2        = <xsl:value-of select="x + y"/>
  B. 3.2 - 4        = <xsl:value-of select="y - x"/>
  C. 4 * 3.2        = <xsl:value-of select="x * y"/>
  D. 11/3.2         = <xsl:value-of select="z div y"/>
  E. 4 + 3.2 * 11   = <xsl:value-of select="x+y*z"/>
  F. (4 + 3.2) * 11 = <xsl:value-of select="(x+y)*z"/>
  G. 11 mod 4       = <xsl:value-of select="z mod x"/>
  H. 4 + 3.2 + 11   = <xsl:value-of select="sum(*)"/>
  I. floor(3.2)     = <xsl:value-of select="floor(y)"/>
  J. ceiling(3.2)   = <xsl:value-of select="ceiling(y)"/>
  K. round(3.2)     = <xsl:value-of select="round(y)"/>
  L. 11 + count(*)  = <xsl:value-of select="11+count(*)"/>
  M. 3.2 + string-length("3.2") = 
     <xsl:value-of select="y + string-length(y)"/>
  N. 11 + "hello"     = <xsl:value-of select="z + 'hello'"/>
</xsl:template>

СТРОКИ

В XPath строки формируются из символов Unicode, как можно было предположить. Ряд функций специально предназначен для работы со строками:
string(object object1)
Преобразует объект в строку;
starts-with(string string1, string string2)
Возвращает истину, если первая строка начинается (starts with) со второй строки;
contains(string string1, string string2)
Возвращает истину, если первая строка содержит (contains) вторую строку;
substring(string string1, number offset number length)
Возвращает
length
символов из строки, начиная со смещения
offset
substring-before(string string1, string string2)
Возвращает часть строки
string1
до первого вхождения строки
string2
substring-after(string string1, string string2)
Возвращает часть строки
string1
после первого вхождения
string2
string-length(string string1)
Возвращает количество символов в строке
string1
normalize-space(string string1)
Возвращает строку
string1
после отбрасывания лидирующих и завершающих символов-разделителей и замены нескольких последовательных разделителей на один пробел;
translate(string string1, string string2, string string3)
Возвращает строку
string1
в которой все вхождения символов в строке
string2
заменены на соответствующие символы в строке
string3
concat(string string1, string string2, ...)
Возвращает конкатенацию (объединение) всех строк.
Есть еще одна строковая функция, о которой вам следует знать, входящая не в XPath, а в XSLT:
format-number(number number1, string string2, string string3)
Возвращает строку, содержащую число
number1
в виде форматированной строки, используя
string2
в качестве форматирующей строки (форматирующие строки создаются так же, как для метода Java java.text.DecimalFormat) и
string3
как возможную строку локализации.
В листинге 4.9 я выбираю текстовые узлы, в которых текст начинается с 'Е', чтобы выбрать Earth (Земля), и добавляю текст '(the World)' (мир), получая 'Earth (the World)'. Для этого я применяю предикат "
text()[starts-with(., 'Е')]

Удаление заданных символов из строки

Задача

Требуется убрать из строки некоторые символы (например, пробельные).


Решение

XSLT 1.0


Воспользуйтесь функцией translate() с пустой строкой замены. Напри­мер, следующий код удаляет из строки все пробельные символы:

translate($input, " &#x9;&#xa;&#xd;", "")


Применение translate оправдано и в XSLT 2.0, так как обычно эта функция работает быстрее всего. Но некоторые задачи такого рода более естественно реша­ются с помощью регулярных выражений и новой функции replace():

(: \s соответствует любому пробельному символу
replace($input, "\s", "")

Обсуждение

translate() – довольно гибкая функция, которая часто применяется для компенсации отсутствующих в XSLT 1.0 средств работы со строками. Здесь мы воспользовались тем фактом, что translate() не копирует те символы входной строки, которые есть в строке from, но отсутствуют в строке to.


Функцию translate() можно использовать также для удаления из строки всех символов, кроме заданных. Например, следующий код удаляет из исходной стро­ки все символы, кроме цифр:

translate($string, translate($string, '0123456789', ''), '')


Внутренний вызов translate() удаляет все интересующие нас символы (в данном случае цифры), чтобы получить параметр from для внешнего вызова, который удалит из исходной строки все символы, кроме цифр.


Иногда нужно удалять не все пробелы, а только находящиеся в начале и в конце строки, а также оставлять из нескольких подряд идущих пробелов в середине только один. В XPath для этой цели есть встроенная функция normalize-space(). А если нужно решить ту же задачу для символа, отличного от пробела, то можно вос­пользоваться таким кодом (в данном случае мы нормализуем вхождения символа C):

translate(normalize-space(translate($input,"C "," C")),"C "," C")


Однако это преобразование будет работать неправильно, если входная стро­ка содержит другие символы пропуска, то есть знаки табуляции (#x9), конца строки (#xA) и перевода каретки (#xD). Причина в том, что это выражение пе­реставляет местами пробел и нормализуемый символ, затем нормализует пробе­лы и выполняет обратную перестановку. Если после первого преобразования остается символ пропуска, отличный от пробела, то он также нормализуется, хотя вы, возможно, этого и не хотели. Впрочем, нормализация чего-либо, кро­ме пробелов, встречается редко. Ниже показано, как можно удалить лишние символы -:

<xsl:template match="/">
<xsl:variable name="input" select=" ‘-this -is- the way we normalize
non-whitespace-‘ "/>
<xsl:value-of select="translate(normalize-space(
translate($input,’- "," -‘)),’- "," -‘)"/>


Более универсальный способ удаления ненужных символов дает встроенная в XSLT 2.0 функция replace(), основанная на аппарате регулярных выраже­ний. Ниже мы с помощью replace() нормализуем отличный от пробела символ без особых случаев, возникающих в решении для XSLT 1.0:

<xsl:template match="/">
<xsl:variable name="input" select=" ‘-this -is- the way we normalize non-whitespace-‘ "/> <xsl:value-of selec"="replace(replace($input,’ —+’,’-‘),’Л-+|-+$’,”)"/>
</xsl:template>


Здесь функция replace() вызывается дважды. Внутренний вызов заменяет все со­седние вхождения символа одним, а внешний удаляет начальные и конечные символы.


[x] Удаляем из строки все символы, кроме цифр:

<xsl:for-each select="modifications/shop_item">
<xsl:sort order="ascending" select="."/>
<li><xsl:value-of select="translate(name, translate(name, '0123456789', ''), '')" /></li>
</xsl:for-each>

Функции XPath для работы со строками

В XSLT доступны следующие функции XPath для работы со строками:
• concat(string string1, string string2,...). Возвращает конкатенацию (объединение) всех переданных ей строк;
• contains(string string1, string string2). Возвращает истину, если первая строка содержит (contains) вторую строку;
• normalize-space(string string1). Возвращает строку string1 (или контекстный узел, если строки string1 нет) после отбрасывания лидирующих и завершающих символов-разделителей и замены нескольких последовательных разделителей на один пробел;
• starts-with(string string1, string string2). Возвращает истину, если первая строка начинается (starts with) со второй подстроки;
• string(object). Преобразует объект в строку;
• string-length(string string1). Возвращает количество символов в строке string1;
• substring(string string1, number offset, number length). Возвращает length символов из строки, начиная со смещения offset;
• substring-after(string string1, string string2). Возвращает часть строки string1 после первого вхождения string2;
• substring-before(string string1, string string2). Возвращает часть строки string1 вплоть до первого вхождения строки string2;
• translate(string string1, string string2, string string3). Возвращает строку string1, в которой все вхождения символов в строке string2 заменены на соответствующие символы в строке string3;

Функция round()

Функция round округляет число до ближайшего целого значения.

Синтаксис

number round( number )
Описание и примеры
У этой функции есть несколько нюансов, которые мы сейчас разберем.
  • Если дробная часть числа равна 0.5, то функция вернет ближайшее большее целое.
  • Если аргумент является не-числом (NaN), то результат также будет NaN.
  • Если аргумент является положительной или отрицательной бесконечностью, то результатом будет тоже положительная или отрицательная бесконечность, то есть аргумент не изменится.
  • Если аргумент является положительным или отрицательным нулем, результатом будет также положительный или отрицательный нуль, то есть аргумент не изменится.
  • Если аргумент меньше нуля, но больше или равен — 0.5, результатом будет отрицательный нуль.
Примеры
round(2.5) ? 3
round(2.49) ? 2
round(-1.7) ? -2
1 div round(0.5) ? 1
1 div round(0.49) ? Infinity
1 div round(-0.5) ? -Infinity
round(1 div 0) ? Infinity
round('one') ? NaN

Элемент <xsl:number> - нумерация

Элемент xsl:number вычисляет номер узла в соответствии с заданными критериями, форматирует его и затем вставляет в результирующее дерево в виде текстового узла.

Синтаксис

XSLT 1.0

<xsl:number
level = "single | multiple | any"
count = "паттерн"
from = "паттерн"
value = "выражение"
format = "строка"
lang = "токен"
letter-value = "alphabetic | traditional"
grouping-separator = "символ"
grouping-size = "число" />

Атрибуты

  • level — необязательный атрибут, указывает, на каких уровнях дерева следует искать нумеруемые узлы.
  • count — необязательный атрибут, указывает, какие именно узлы следует считать при вычислении номера.
  • from — необязательный атрибут, указывает, в какой части документа будет производиться нумерация.
  • value — необязательный атрибут, задает выражения, которые следует использовать для вычисления значения номера.
  • format — необязательный атрибут, определяет, как номер будет форматироваться в строку.
  • lang — необязательный атрибут, задает языковой контекст нумерации.
  • letter-value — необязательный атрибут, определяет параметры буквенных методов нумерации.
  • grouping-separator — необязательный атрибут, задает символ, разделяющий группы цифр в номере.
  • grouping-size — необязательный атрибут, определяет количество цифр в одной группе.

XSLT 2.0

<xsl:number
value = "expression"
select = "expression"
level = "single | multiple | any"
count = "pattern"
from = "pattern"
format = "string"
lang = "nmtoken"
letter-value = "alphabetic | traditional"
ordinal = "string"
grouping-separator = "char"
grouping-size = "number" />

Атрибуты

  • count — необязательный атрибут, содержит шаблон XPath, определяющий, что именно должно подсчитываться. Если атрибут count не указан, подсчитываются узлы с таким же именем, как у текущего узла.
  • level — необязательный атрибут, определяет, какие уровни исходного дерева должны учитываться при нумерации элементов. Для него определены три допустимых значения: singlemultiple и anysingle — нумерация ведется только на одном уровне. Процессор XSLT переходит к первому узлу оси ancestor-or-self, соответствующему атрибуту count, а затем нумерует этот узел вместе со всеми предшествующими одноуровневыми узлами, соответствующими также атрибуту count. Значение используется по умолчанию. multiple — нумерация на нескольких уровнях. Процессор XSLT перебирает всех предков текущего узла вместе с самим текущим узлом и выбирает все узлы, соответствующие атрибуту countany — включает всех предков текущего узла (по аналогии с level="multiple"), а также все элементы оси preceding. Во всех этих случаях при использовании атрибута from анализируются только те предки, которые являются потомками ближайшего предка, соответствующего атрибуту from. Иначе говоря, с атрибутом from="h1" для нумерации анализируются только те узлы, которые находятся под ближайшим элементом <h1>.
  • from — необязательный атрибут, содержит шаблон XPath, определяющий начальную точку нумерации. Например, from="h1" означает, что нумерация должна начаться с предыдущего элемента <h1>.
  • value — необязательный атрибут, выражение, преобразуемое в число. С помощью этого атрибута можно быстро отформатировать число; элемент <xsl:number value="7" format="i:"/> возвращает строку "vii:". При заданном атрибуте value атрибуты countlevelfrom и select использоваться не могут.
  • format — необязательный атрибут, определяет формат генерируемой нумерации:
    • format="1" Нумерация вида 1 2 3 4 5 6 7 8 9 10 11 ….
    • format="01" Нумерация вида 01 02 03 04 … 09 10 11 … 99 100 101 ….
    • format="a" Нумерация вида a b c d e f … x y z aa ab ac ….
    • format="A" Нумерация вида A B C D E F … X Y Z AA AB AC ….
    • format="i" Нумерация вида i ii iii iv v vi vii viii ix x ….
    • format="I" Нумерация вида I II III IV V VI VII VIII IX X ….
    • format="w" Нумерация в виде словарной последовательности. С атрибутами ordinal="yes" и lang="en"нумерация форматируется в виде first second third fourth .... Если атрибут ordinal="yes" не задан, создается последовательность вида one two three four .... Если заданная комбинация formatordinal и lang не поддерживается, процессор XSLT обязан прибегнуть к последнему средству – отформатировать числа в виде 1 2 3 4 5 ....
    • format="Ww" Нумерация в виде словарной последовательности. С атрибутами ordinal="yes" и lang="en"нумерация форматируется в виде First Second Third Fourth .... Если атрибут ordinal="yes" не задан, создается последовательность вида One Two Three Four .... Если заданная комбинация formatordinal и lang не поддерживается, процессор XSLT обязан прибегнуть к последнему средству – отформатировать числа в виде 1 2 3 4 5 ....
    • format="остальные обозначения" Зависит от используемого процессора XSLT. В спецификации XSLT перечислено несколько других схем нумерации (например, format="&#0E51;" для тайского языка); за информацией о поддерживаемых форматах обращайтесь к документации процессора XSLT. Если процессор XSLT не поддерживает запрашиваемую схему нумерации, спецификация XSLT требует, чтобы по умолчанию использовался атрибут format="1".
  • lang — необязательный атрибут, определяет язык, алфавит которого должен использоваться при нумерации. Разные процессоры XSLT поддерживают разные языки; за дополнительной информацией обращайтесь к документации своего процессора XSLT.
  • letter-value — необязательный атрибут, имеет допустимые значения alphabetic и traditional. Во многих языках поддерживаются две словарные схемы нумерации; в одной числовые значения представляются в алфавитной последовательности, а в другой используется традиция, специфическая для данного языка. (Одним из примеров нумерации, не использующей алфавитный порядок, служит римская нумерация.) По умолчанию используется значение alphabetic.
  • grouping-separator — необязательный атрибут, определяет символ, используемый для разделения групп разрядов в сгенерированном числе. По умолчанию используется запятая (,).
  • grouping-size — необязательный атрибут, определяет количество цифр в группах; по умолчанию используется значение 3.
  • ordinal — необязательный атрибут, управляет использованием порядковых числительных; допустимые значения: yes и no. Атрибуты ordinalformat и lang работают совместно. Например, комбинация ordinal="yes", format="1" и lang="en" создает последовательность 1st 2nd 3rd 4th. С атрибутом format="w"нумерация принимает вид first second third fourth, а с атрибутом format="Ww" – First Second Third Fourth. Если комбинация ordinal, format и lang не поддерживается, процессор XSLT обязан сгенерировать обычную числовую нумерацию (1 2 3 4). За информацией о комбинациях языков и форматов, поддерживаемых с атрибутом ordinal, обращайтесь к документации своего процессора XSLT.
  • select — необязательный атрибут, выбирает нумеруемый узел. Атрибут позволяет выбрать узел, отличный от контекстного узла.

XSLT 3.0

<xsl:number
value? = expression
select? = expression
level? = "single" | "multiple" | "any"
count? = pattern
from? = pattern
format? = { string }
lang? = { language }
letter-value? = { "alphabetic" | "traditional" }
ordinal? = { string }
start-at? = { integer }
grouping-separator? = { char }
grouping-size? = { integer } />

Описание и примеры

Нумерация, несомненно, является одной из самых естественных проблем, решаемых при помощи XSLT. Задача нумерации состоит в том, чтобы, исходя из позиции обрабатываемого узла в дереве документа, вычислить по заданным критериям его порядковый номер. В качестве примера такого рода задачи можно привести вывод номеров частей, разделов и глав книги, указание номеров элементов списка или строк таблицы.
Для вычисления порядковых номеров узлов в дереве в XSLT существует несколько способов. В простых случаях для достижения цели бывает достаточно воспользоваться одним из следующих XPath-выражений.
  • Для того чтобы получить порядковый номер текущего узла в обрабатываемом множестве, можно использовать функцию position. Обратим внимание, что это будет позиция узла в обрабатываемом в данный момент множестве, а не в дереве исходящего документа.
  • Функция count(preceding-sibling::*)+1 возвращает порядковый номер текущего элемента среди других элементов его родителя, иначе говоря, среди его братьев. Путь выборки preceding-sibling::* выбирает множество братских элементов, предшествующих текущему узлу, а функция count вычисляет их количество. Таким образом, значение count(preceding-sibling::*)+1 будет равно 1 для первого элемента (поскольку ему другие элементы не предшествуют), 2 — для второго (ему предшествует один элемент) и так далее.
  • Для того чтобы учитывать при подсчете только определенные элементы, можно переписать предыдущее выражение в чуть более строгом виде. Например, выражение, считающее только элементы chapter, будет задаваться следующим образом: (preceding-sibling::chapter) +1.
  • Глубина текущего узла от корня дерева может быть вычислена выражением count(ancestor-or-self::node()). Это выражение будет возвращать 1 для корневого узла, 2 для элемента документа и так далее.
Вычислять выражения и выводить вычисленные значения в результирующее дерево следует, как и обычно — при помощи элемента xsl:value-of.
Пример 1
<xsl:value-of select="count(preceding-sibling::chapter)+1" />
В более сложных ситуациях бывает необходимо подсчитывать узлы, находящиеся на разных уровнях вложенности или удовлетворяющие определенным условиям, начинать отсчет с заданной позиции в документе и использовать при вычислении номера сложные выражения. Использование XPath в таких случаях может быть очень неудобным — выражения будут слишком громоздкими и вычислять их придется в несколько этапов.
Другим, несравненно более легким и удобным способом нумерации и индексирования узлов является использование элемента xsl:number.
Выполнение элемента xsl:number можно условно разделить на два этапа — вычисление номера и его строковое форматирование. На этапе вычисления активными являются атрибуты levelcountfrom и value. Форматирование производится с учетом значений атрибутов formatlangletter-valuegrouping-separator и grouping-size. Результатом первого этапа является список номеров, который форматируется в текстовый узел на втором этапе.

Вычисление номеров

Пожалуй, самым простым для понимания (но не самым простым в использовании) способом вычисления номера является использование XPath-выражений. Этот способ практически идентичен использованию xsl:value-of, как было показано выше. Единственным отличием xsl:number является то, что после вычисления номера он сначала форматируется, а потом уже вставляется в результирующее дерево в виде текстового узла.
Результатом первого этапа форматирования при определенном атрибуте value является список, состоящий из числа, полученного в результате вычисления выражения, указанного в значении этого атрибута.
Пример 2
В этом и нескольких следующих примерах мы будем вычислять номера в одном и том же документе, который представлен в листинге 8.31.
Листинг 8.31. Входящий документ для примеров преобразований с использованием xsl:number
<doc>
<chapter title="First chapter">
<section title="First section">
<para>paragraph 1</para>
<para>paragraph 2</para>
<para>paragraph 3</para>
</section>
<section title="Second section">
<para>paragraph 4</para>
<para>paragraph 5</para>
</section>
</chapter>
<chapter title="Second chapter">
<section title="Third section">
<para>paragraph 6</para>
<para>paragraph 7</para>
<para>paragraph 8</para>
<para>paragraph 9</para>
</section>
<section title="Forth section">
<para>paragraph 10</para>
<para>paragraph 11</para>
<para>paragraph 12</para>
</section>
<section title="Fifth section">
<para>paragraph 13</para>
<para>paragraph 14</para>
<para>paragraph 15</para>
<para>paragraph 16</para>
</section>
</chapter>
<chapter title="Third chapter">
<section title="Sixth section">
<para>paragraph 17</para>
<para>paragraph 18</para>
</section>
</chapter></doc>
В качестве первого примера приведем два шаблона, обрабатывающих элементы chapter: один с использованием xsl:value-of, а второй с использованием xsl:number.
Листинг 8.32. Вариант нумерующего шаблона с использованием xsl:value-of
<xsl:template match="chapter">
<xsl:value-of select="position()"/>
<xsl:text>. </xsl:text>
<xsl:value-of select="@title"/>
<xsl:text> </xsl:text></xsl:template>
Листинг 8.33. Вариант нумерующего шаблона с использованием xsl:number
<xsl:template match="chapter">
<xsl:number value="position()" format="1. "/>
<xsl:value-of select="@title"/>
<xsl:text> </xsl:text></xsl:template>
Результат обоих шаблонов имеет следующий вид:
1. First chapter
2. Second chapter
3. Third chapter
Использование xsl:number даже в этом простом случае сэкономило одну строчку в коде. Однако, если бы вместо нумерации арабскими цифрами (1, 2, 3 и т.д.) нужно было применить нумерацию римскими цифрами (I, II, III и т.д.), в преобразовании с xsl:number мы бы изменили всего один символ (вместо format="1. " указали бы format="I. "), в то время как в преобразовании с xsl:value-of пришлось бы писать сложную процедуру преобразования числа в римскую запись.
В том случае, если атрибут value опущен, номера элементов вычисляются исходя из значений атрибутов levelcount и from.
Атрибут level имеет три варианта значений: singlemultiple и any, значением по умолчанию является single. Процедура вычисления номеров существенным образом зависит от того, какой из этих вариантов используется — при методе single считаются элементы на одном уровне, при методе multiple — на нескольких уровнях и при методе any — на любых уровнях дерева. Алгоритм вычисления списка номеров в каждом из случаев не слишком сложен, но понять его только по формальному описанию довольно непросто. Поэтому каждый из методов будет дополнительно проиллюстрирован примерами вычисления.
Атрибут count содержит паттерн, которому должны удовлетворять нумеруемые узлы. Узлы, не соответствующие этому образцу, просто не будут приниматься в расчет. Значением этого атрибута по умолчанию является паттерн, выбирающий узлы с тем же типом и именем, что и у текущего узла (если, конечно, у него есть имя).
Атрибут from содержит паттерн, который определяет так называемую область нумерации, или область подсчета. При вычислении номера будут приниматься во внимание только те нумеруемые узлы, которые принадлежат этой области. По умолчанию областью подсчета является весь документ.

Метод single

Метод single используется для того, чтобы вычислить номер узла, основываясь на его позиции среди узлов того же уровня. Нумерацию, в которой используется метод single, также называют одноуровневой нумерацией.
Областью нумерации этого метода будет множество всех потомков ближайшего предка текущего узла, удовлетворяющего паттерну, указанному в атрибуте from.
Вычисление номера производится в два шага.
  • На первом шаге находится узел уровня дерева. Узлом уровня будет узел, удовлетворяющий следующим условиям:
    • он является первым (то есть ближайшим к текущему) узлом, принадлежащим оси ancestor-or-self текущего узла;
    • он удовлетворяет паттерну count;
    • он принадлежит области подсчета;
    • если такого узла нет, список номеров будет пустым.
  • На втором шаге вычисляется номер узла уровня. Этот номер будет равен 1 плюс количество узлов, принадлежащих оси навигации preceding-sibling и удовлетворяющих паттерну count.
Надо сказать, от атрибута from в методе single мало пользы. Единственный эффект, который можно от него получить, — это пустой список номеров в случае, если первый узел, принадлежащий оси ancestor-or-self и удовлетворяющий паттерну count, не будет иметь предка, соответствующего паттерну атрибута from.
Пример 3
Разберем функционирование одноуровневой нумерации в следующем шаблоне:
<xsl:template match="para">
<xsl:number format=" 1." count="section"/>
<xsl:number format="1." count="para"/>
<xsl:value-of select="."/>
<xsl:text> </xsl:text></xsl:template>
Мы продемонстрируем вычисление номера одного из элементов para на схематическом изображении дерева обрабатываемого документа (рис. 8.1). Узел обрабатываемого элемента мы выделим полужирной линией, узел элемента doc пометим буквой d, узлы элементов chapter — буквой c, элементов section и para — буквами sи p соответственно.
Рис. 8.1. Дерево обрабатываемого документа
В качестве первого примера приведем вычисление номера элементом
<xsl:number format=" 1." count="section"/>
На первом шаге нам нужно найти узел уровня дерева. Этим узлом будет первый элемент section, являющийся предком текущего узла. На рис. 8.2 он обведен пунктиром.
Рис. 8.2. Первый шаг вычисления номера
Номер этого элемента будет равен 1 плюс количество предшествующих ему братских элементов section. Это множество выделено пунктиром на рис. 8.3.
Рис. 8.3. Второй шаг вычисления номера
Выделенное множество содержит два узла. Таким образом, искомый номер будет равен 3.
Проведем такой же разбор для определения
<xsl:number format="1." count="para"/>
В этом случае паттерну, указанному в элементе count удовлетворяет сам текущий узел, значит, он и будет являться узлом уровня, как это показано на рис. 8.4.
Рис. 8.4. Первый шаг вычисления номера
Выделим множество элементов para, являющихся братьями узла уровня и предшествующих ему (рис. 8.5).
Рис. 8.5. Второй шаг вычисления номера
Выделенное множество содержит всего один узел, значит, искомый номер будет равен 2.
Таким образом, результатом обработки выделенного элемента para будет следующая строка:
3.2.paragraph 14

Метод multiple

Метод multiple похож на метод single, но при этом он немного сложнее, поскольку вычисляет номера узлов сразу на нескольких уровнях дерева. Нумерацию с применением метода multiple называют также многоуровневой нумерацией.
Область нумерации метода multiple определяется так же, как и в случае с методом single: учитываются только потомки ближайшего предка текущего узла, удовлетворяющего паттерну, указанному в атрибуте from.
Вычисление списка номеров узлов выполняется в два этапа:
  • На первом этапе выбирается множество нумеруемых узлов, удовлетворяющее следующим условиям:
    • его узлы принадлежат оси навигации ancestor-or-self текущего узла;
    • его узлы соответствуют паттерну count;
    • его узлы принадлежат области подсчета.
  • На втором этапе для каждого узла нумеруемого множества вычисляется позиция среди собратьев. Позиция нумеруемого узла будет равна 1 плюс количество узлов, принадлежащих его оси навигации preceding-sibling и соответствующих паттерну count.
Пример 4
Для демонстрации вычисления номеров на нескольких уровнях дерева документа проследим за выполнением инструкции
<xsl:number format=" 1.1." level="multiple" count="doc|chapter|para" from="doc"/>
при обработке того же элемента para.
Прежде всего, надо определить область подсчета. Значением атрибута from является паттерн doc, значит, подсчет будет вестись среди всех потомков ближайшего к текущему элементу para предка, который является элементом doc. Это множество выделено на рис. 8.6 штрих-пунктирной линией.
Рис. 8.6. Определение области подсчета
Следующим шагом выберем узлы, принадлежащие оси навигации ancestor-or-self текущего узла para и удовлетворяющие паттерну doc|chapter|para. Это множество будет включать сам текущий элемент, а также его предки chapter и doc. На рис. 8.7 они обведены пунктиром.
Рис. 8.7. Первый шаг вычисления номера
Следующим шагом оставим только те из выбранных узлов, которые входят в область подсчета. Эти узлы обведены на рис. 8.8 пунктиром.
Рис. 8.8. Второй шаг вычисления номера
Мы получили множество узлов, состоящее всего из двух элементов — chapter и para вследствие того, что элемент doc был исключен как не входящий в область подсчета. Выделим множества пересчитываемых узлов, предшествующих нумеруемым элементам (рис. 8.9).
Рис. 8.9. Третий шаг вычисления номера
В этом примере элемент chapter, так же как и элемент para, будет иметь номер 2. Соответственно, результатом выполнения инструкции xsl:number в этом случае будет строка
2.2.paragraph 14

Метод any

Метод any используется для того, чтобы вычислить номер узла, основываясь на его позиции среди всех учитываемых узлов элемента.
Областью нумерации этого метода будет множество всех узлов, следующих в порядке просмотра документа за первым предком текущего узла, который удовлетворяет паттерну, указанному в атрибуте from.
Номер вычисляется как 1 плюс количество узлов области подсчета, удовлетворяющих паттерну count и предшествующих в порядке просмотра документа текущему узлу.
Пример 5
В качестве примера применения метода any вычислим порядковый номер элемента para среди всех элементов документа, начиная со второй главы. Инструкцию такого рода мы запишем в виде
<xsl:number format=" 1." level="any" count="*" from="chapter[2]"/>
При ее выполнении мы сначала определим область, в которой будут подсчитываться узлы (обведены штрих-пунктирной линией на рис. 8.10).
Рис. 8.10. Определение области подсчета узлов
Следующим шагом выделим подмножество области подсчета, предшествующее в порядке просмотра текущему узлу para (рис. 8.11).
Рис. 8.11. Первый шаг вычисления номера
Выделенное множество содержит 11 узлов, значит, искомый номер будет равен 12.
Перед тем, как перейти к рассмотрению способов форматирования номеров, приведем итоговый пример (листинг 8.34), в котором в шаблонах будут использоваться все три метода нумерации.
Листинг 8.34. Шаблон, использующий разные методы нумерации
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="doc">
<xsl:text>Resulting document </xsl:text>
<xsl:text>================== </xsl:text>
<xsl:apply-templates select="chapter"/>
</xsl:template>
<xsl:template match="chapter">
<xsl:number format="1. "/>
<xsl:value-of select="@title"/>
<xsl:text> </xsl:text>
<xsl:apply-templates select="section"/>
</xsl:template>
<xsl:template match="section">
<xsl:number format=" 1.1 " level="multiple" count="chapter|section"/>
<xsl:value-of select="@title"/>
<xsl:text> </xsl:text>
<xsl:apply-templates select="para"/>
</xsl:template>
<xsl:template match="para">
<xsl:number format=" a) " level="any" count="para"/>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:template></xsl:stylesheet>
Опишем словесно нумерацию, которая будет применяться в этом преобразовании.
  • Элементы chapter будут нумероваться в соответствии со своей позицией среди других элементов chapter того же уровня.
  • Элементы section будут нумероваться при помощи многоуровневой нумерации — номер будет состоять из номера элемента chapter и номера самого элемента section.
  • Элементы para будут нумероваться исходя из своей позиции среди всех остальных элементов para вне зависимости от того, на каких уровнях в документе они находятся.
Результатом применения этого преобразования к документу, приведенному в листинге 8.31, будет следующий текст.
Листинг 8.35. Выходящий документ
Resulting document
==================1. First chapter
1.1 First section
a) paragraph 1 b) paragraph 2 c) paragraph 3
1.2. Second section
d) paragraph 4 e) paragraph 52. Second chapter
2.1 Third section
f) paragraph 6 g) paragraph 7 h) paragraph 8 i) paragraph 9
2.2 Forth section
j) paragraph 10 k) paragraph 11 l) paragraph 12
2.3 Fifth section
m) paragraph 13 n) paragraph 14 o) paragraph 15 p) paragraph 163. Third chapter
3.1 Sixth section
q) paragraph 17 r) paragraph 18

Форматирование номеров

Возвращаясь немного назад, напомним, что результатом первого этапа выполнения xsl:number является список номеров, который может быть пустым или содержать одно или несколько чисел. Несложно увидеть, что количество номеров в этом списке будет зависеть от следующих условий.
  • Список номеров будет пустым, если в области нумерации не оказалось нумеруемых узлов.
  • Список номеров будет состоять не более чем из одного числа при использовании методов single и any.
  • Список номеров будет состоять из нуля или более чисел (по одному на каждый уровень нумерации) при использовании метода multiple.
На этапе форматирования список номеров преобразуется в строку и вставляется результирующее дерево в виде текстового узла.
Преобразование номеров из списка в строку имеет совершенно иной характер, нежели чем приведение числа к строковому типу. При форматировании номера нужно получить не просто строковое представление числа, здесь требуется сгенерировать значащий текстовый индекс, который совершенно необязательно должен иметь цифровую запись.
Форматирование списка номеров производится в соответствии со значениями атрибутов formatlangletter-valuegrouping-separator и grouping-size, назначение и использование которых мы и будем разбирать в этом разделе.
Основным атрибутом форматирования является атрибут format, который содержит последовательность форматирующих токенов. Каждый форматирующий токен состоит из букв и цифр; он определяет процедуру форматирования для каждого числа из списка форматируемых номеров. В значении атрибута formatформатирующие токены отделяются друг от друга сочетаниями символов, которые не являются буквами и цифрами. Такие сочетания называются разделяющими последовательностями. При форматировании они остаются в строковом выражении номера без изменений.
Пример 6
В примере к методу multiple мы использовали следующий элемент xsl:number:
<xsl:number format=" 1.1." level="multiple" count="doc|chapter|para" from="doc"/>
Разберем строение атрибута format этого элемента (на рис. 8.12 пробелы обозначены символами “_”):
Рис. 8.12. Строение атрибута format элемента xsl:number
Список номеров в том примере состоял из номера элемента chapter (числа 2) и номера элемента para (тоже 2). Номер, генерируемый элементом xsl:number, будет состоять из:
  • разделяющей последовательности "_____", которая будет скопирована, как есть;
  • числа 2, которое получается в результате форматирования номера 2 форматирующим токеном “1”;
  • разделяющего символа “.”;
  • числа 2, которое получается в результате форматирования номера 2 вторым форматирующим токеном “1”;
  • разделяющего символа “.”.
Объединив все эти части, мы получим отформатированный номер "_____2.2".
Несложно заметить, что главную роль при преобразовании списка номеров в их строковое представление играют форматирующие токены. Каждый такой токен преобразовывает соответствующий ему номер в строку. В табл. 8.3 мы приведем описания этих преобразований.

Таблица 8.3. Форматирующие токены

ТОКЕН
ОПИСАНИЕ
ПРИМЕРЫ
ТОКЕН
ПРЕОБРАЗОВАНИЕ
1
Форматирует номер в виде строкового представления десятичного числа
1
1 ? ‘1’
1
2 ? ‘2’
1
10 ? ‘10’
1
999 ? ‘999’
1
1000 ? ‘1000’
0…01
Форматирует номер в виде строкового представления десятичного числа; если получившая строка короче токена, она дополняется предшествующими нулями
0001
1 ? ‘0001’
001
2 ? ‘002’
001
10 ? ‘010’
01
999 ? ‘999’
00001
1000 ? ‘01000’
A
Форматирует номер в виде последовательности заглавных букв латинского алфавита
A
1 ? ‘A’
A
2 ? ‘B’
A
10 ? ‘J’
A
27 ? ‘AA’
A
999 ? ‘ALK’
A
1000 ? ‘ALL’
a
Форматирует номер в виде последовательности строчных букв латинского алфавита
a
1 ? ‘a’
a
2 ? ‘b’
a
10 ? ‘j’
a
27 ? ‘aa’
a
999 ? ‘alk’
a
1000 ? ‘all’
I
Форматирует номер заглавными римскими цифрами
I
1 ? ‘I’
I
2 ? ‘II’
I
10 ? ‘X’
I
27 ? ‘XXVII’
I
999 ? ‘IM’
I
1000 ? ‘M’
i
Форматирует номер строчными римскими цифрами
i
1 ? ‘i’
i
2 ? ‘ii’
i
10 ? ‘x’
i
27 ? ‘xxvii’
i
999 ? ‘im’
i
1000 ? ‘m’
Другой
Форматирует номер k как k-й член последовательности, начинающейся этим токеном. Если нумерация таким токеном не поддерживается, вместо него используется токен 1.
Не поддерживающийся токен
1 ? ‘1’
b
10 ? ‘k’
Б
2 ? ‘В’
Б
27 ? ‘Ы’
?
999 ? ‘???’
?
1000 ? ‘???’
При использовании алфавитной нумерации процессор может учитывать значение атрибута lang элемента xsl:number для того, чтобы использовать буквы алфавита соответствующего языка. Однако на практике возможность эта поддерживается очень слабо: большинство процессоров поддерживают алфавитную нумерацию только с использованием латиницы. Поэтому для того, чтобы использовать при алфавитной нумерации кириллицу, вместо атрибута lang следует использовать форматирующие токены “А” (русская заглавная буква “А”) и “а” (русская строчная буква “а”).
Пример 7
Для форматирования номеров в последовательности 1.1.а, 1.1.б, 1.1.в, …, 1.2.а и так далее можно использовать объявление вида:
<xsl:number format="1.&#х430;" level="multiple" count="chapter|section" from="doc"/>
Представим теперь себе следующую ситуацию: нам нужно начать нумерацию с латинской буквы i для того, чтобы получить последовательность номеров вида i, j, k, l, m и так далее. Первое, что приходит в голову — это запись вида:
<xsl:number format="i" ... />
Однако вместо требуемой последовательности мы получим последовательность строчных римских цифр: i, ii, iii и так далее. Иными словами, некоторые форматирующие токены определяют нумерующую последовательность двусмысленно: одним вариантом является алфавитная последовательность, начинающаяся этим токеном, другим — некая традиционная для данного языка (например, последовательность римских цифр для английского). Для того чтобы различать эти последовательности в двусмысленных ситуациях, в xsl:number существует атрибут letter-value. Если его значением является “alphabetic”, нумерующая последовательность является алфавитной, значение “traditional” указывает на то, что следует использовать традиционный для данного языка способ. Если атрибут letter-value опущен, процессор может сам выбирать между алфавитным и традиционным способами нумерации.
При использовании цифровых форматов нумерации (иными словами, токенов вида 101001 и так далее) цифры в номере можно разделить на группы, получив, например, такие номера как “2.00.00” из 20000 или “0-0-0-2” из 2. Для этой цели в xsl:number используется пара атрибутов grouping-separator и grouping-size.
Атрибут grouping-separator задает символ, который следует использовать для разбивки номера на группы цифр, в то время как grouping-size указывает размер группы. Эти атрибуты всегда должны быть вместе — если хотя бы один из них опущен, второй просто игнорируется.
Пример 8
Элемент xsl:number вида
<xsl:number format="[00000001]" grouping-separator="." grouping-size="2"/>
будет генерировать номера в следующей последовательности:
1 ? '[00.00.00.01]'
2 ? '[00.00.00.02]'
...
999 ? '[00.00.09.99]'
1000 ? '[00.00.10.00]'
Пожалуй, следует упомянуть, что в значениях атрибутов formatlangletter-valuegrouping-size и grouping-separator могут быть указаны шаблоны значений, иными словами могут использоваться выражения в фигурных скобках. Это может быть полезно, например, для того, чтобы сгенерировать форматирующие токены во время выполнения преобразования.
Пример 9
В следующем шаблоне формат номера секции зависит от значения атрибута format ее родительского узла:
<xsl:template match="section">
<xsl:number format="{../@format}-1 " level="multiple" count="chapter|section"/>
<xsl:value-of select="@title"/></xsl:template>
При обработке входящего документа
<doc>
<chapter format="I" title="First Chapter">
<section title="First Section"/>
<section title="Second Section"/>
<section title="Third Section"/>
</chapter></doc>
нумерация секций будет выглядеть как
I-1 First Section
I-2 Second Section
I-3 Third Section
Если же атрибут format элемента chapter будет иметь значение 1, секции будут пронумерованы в виде
1-1 First Section
1-2 Second Section
1-3 Third Section

Пример для XSLT 2.0

Для полноценной демонстрации применения xsl:number потребуется документ XML c достаточно большим количеством элементов для нумерации. Мы используем следующий документ:
<?xml version="1.0"?><!-- items-to-number.xml --><book>
<chapter>
<title>Alfa Romeo</title>
<sect1>
<title>Bentley</title>
</sect1>
<sect1>
<title>Chevrolet</title>
<sect2>
<title>Dodge</title>
<sect3>
<title>Eagle</title>
</sect3>
</sect2>
</sect1>
</chapter>
<chapter>
<title>Ford</title>
<sect1>
<title>GMC</title>
<sect2>
<title>Honda</title>
<sect3><title>Isuzu</title></sect3>
<sect3><title>Javelin</title></sect3>
<sect3><title>K-Car</title></sect3>
<sect3><title>Lincoln</title></sect3>
</sect2>
<sect2><title>Mercedes</title></sect2>
<sect2>
<title>Nash</title>
<sect3><title>Opel</title></sect3>
<sect3><title>Pontiac</title></sect3>
</sect2>
<sect2>
<title>Quantum</title>
<sect3><title>Rambler</title></sect3>
<sect3><title>Studebaker</title></sect3>
</sect2>
</sect1>
<sect1><title>Toyota</title></sect1>
</chapter></book>
Для сокращения объема выходных данных мы будем подсчитывать только элементы sect2. Начнем с использования атрибута ordinal:
<?xml version="1.0"?><!-- number8.xsl --><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="book">
<xsl:for-each select=".//sect2">
<xsl:number level="any" count="chapter|sect1|sect2|sect3" format="Ww - " ordinal="yes"/>
<xsl:value-of select="title"/>
<xsl:text></xsl:text>
</xsl:for-each>
</xsl:template></xsl:stylesheet>
Словарная нумерация выглядит так:
Fourth - Dodge
Eighth - Honda
Thirteenth - Mercedes
Fourteenth - Nash
Seventeenth - Quantum
Теперь мы воспользуемся комбинацией format и lang:
<?xml version="1.0"?><!-- number9.xsl --><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="book">
<xsl:for-each select=".//sect2">
<xsl:number level="any" count="chapter|sect1|sect2|sect3" format="w - " lang="de"/>
<xsl:value-of select="title"/>
<xsl:text></xsl:text>
</xsl:for-each>
</xsl:template></xsl:stylesheet>
При обработке этой таблицы стилей в Saxon нумерация выводится на немецком языке:
vier - Dodge
acht - Honda
dreizehn - Mercedes
vierzehn - Nash
siebzehn - Quantum
Если запросить комбинацию атрибутов formatordinal и lang, не поддерживаемую процессором XSLT, процессор возвращается к стандартному поведению. Таблица стилей показывает, что делает Saxon при запросе нумерации на польском языке:
<?xml version="1.0"?><!-- number10.xsl --><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="book">
<xsl:for-each select=".//sect2">
<xsl:number level="any" count="chapter|sect1|sect2|sect3" format="w - " lang="pl"/>
<xsl:value-of select="title"/>
<xsl:text></xsl:text>
</xsl:for-each>
</xsl:template></xsl:stylesheet>
Saxon не поддерживает польский язык, поэтому нумерация выводится на английском:
four - Dodge
eight - Honda
thirteen - Mercedes
fourteen - Nash
seventeen - Quantum
В последнем примере используются атрибуты format="๑" (тайская нумерация) и ordinal="yes":
<?xml version="1.0"?><!-- number11.xsl --><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="book">
<html>
<head>
<title>Thai numbering</title>
</head>
<body style="font-family: sans-serif;">
<h1>Thai numbering</h1>
<p style="font-size: 150%">
<xsl:for-each select=".//sect2">
<xsl:number level="any" count="sect2" format="๑ "/>
<xsl:value-of select="title"/>
<br/>
</xsl:for-each>
</p>
</body>
</html>
</xsl:template></xsl:stylesheet>
Таблица стилей генерирует документ HTML, в котором элементы <sect2> нумеруются на тайском языке:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Thai numbering</title>
</head>
<body style="font-family: sans-serif;">
<h1>Thai numbering</h1>
<p style="font-size: 150%">๑ Dodge<br>
๒ Honda<br>๓ Mercedes<br>
๔ Nash<br>๕ Quantum<br></p>
</body></html>

Элемент <xsl:sort> - сортировка

При преобразовании документа элементами xsl:for-each и xsl:apply-templates, выбранные узлы по умолчанию обрабатываются в порядке просмотра документа, который зависит от выражения, использованного в атрибуте select этих элементов. XSLT позволяет изменять этот порядок посредством использования механизма сортировки.
Элементы xsl:for-each и xsl:apply-templates могут содержать один или несколько элементов xsl:sort, которые позволяют предварительно сортировать обрабатываемое множество узлов.

Синтаксис

XSLT 1.0

<xsl:sort
select = "string-expression"
lang = "nmtoken"
data-type = "text | number | qname-but-not-ncname"
order = "ascending | descending"
case-order = "upper-first | lower-first" />

<xsl:apply-templates select="informationsystem_item">
<xsl:sort
select = "sorting"
data-type = "number"
order = "ascending"
/>
</xsl:apply-templates>
Атрибуты
  • select — обязательный атрибут, значением которого является выражение, называемое также ключевым выражением. Это выражение вычисляется для каждого узла обрабатываемого множества, преобразуется в строку и затем используется как значение ключа при сортировке. По умолчанию значением этого атрибута является ".", что означает, что в качестве значения ключа для каждого узла используется его строковое значение.
  • order — необязательный атрибут, определяет порядок, в котором узлы должны сортироваться по своим ключам. Этот атрибут может принимать только два значения — "ascending", указывающее на восходящий порядок сортировки, и "descending", указывающее на нисходящий порядок. Значением по умолчанию является "ascending", то есть восходящий порядок.
  • lang — необязательный атрибут, определяет язык ключей сортировки. Дело в том, что в разных языках символы алфавита могут иметь различный порядок, что, соответственно, должно учитываться при сортировке. Атрибут lang в XSLT может иметь те же самые значения, что и атрибут xml:lang (например: "en", "en-us", "ru" и т. д.). Если значение этого атрибута не определено, процессор может либо определять язык исходя из параметров системы, либо сортировать строки исходя из порядка кодов символов Unicode.
  • data-type — необязательный атрибут, определяет тип данных, который несут строковые значения ключей.
Техническая рекомендация XSLT разрешает этому атрибуту иметь следующие значения:
  • "text" — ключи должны быть отсортированы в лексикографическом порядке исходя из языка, определенного атрибутом lang или параметрами системы. Это значение используется по умолчанию;
  • "number" — ключи должны сравниваться в численном виде. Если строковое значение ключа не является числом, оно будет преобразовано к не-числу (NaN), и, поскольку нечисловые значения неупорядочены, соответствующий узел может появиться в отсортированном множестве где угодно;
  • "имя" — в целях расширяемости XSLT также позволяет указывать в качестве типа данных произвольное имя. В этом случае реализация сортировки полностью зависит от процессора.
  • case-order — необязательный атрибут, указывает на порядок сортировки символов разных регистров. Значениями этого атрибута могут быть "upper-first", что означает, что заглавные символы должны идти первыми, или "lower-first", что означает, что первыми должны быть строчные символы. К примеру, строки "ночь", "Улица", "фонарь", "Аптека", "НОЧЬ", "Фонарь" при использовании case-order="upper-first" будут иметь порядок "Аптека", "НОЧЬ", "ночь", "Фонарь", "фонарь", "улица". При использовании case-order="lower-first" те же строки будут идти в порядке "Аптека", "ночь", "НОЧЬ", "фонарь", "Фонарь", "улица". Значение case-order по умолчанию зависит от процессора и языка сортировки. В большинстве случаев заглавные буквы идут первыми.

XSLT 2.0 и XSLT 3.0

<xsl:sort
    select? = expression
    lang? = { nmtoken }
    order? = { "ascending" | "descending" }
    collation? = { uri }
    stable? = { "yes" | "no" }
    case-order? = { "upper-first" | "lower-first" }
    data-type? = { "text" | "number" | qname-but-not-ncname }>
    <!-- Content: sequence-constructor -->
</xsl:sort>

Описание и примеры

В случае если xsl:for-each и xsl:apply-templates содержат элементы xsl:sort, обработка множества узлов должна производиться не в порядке просмотра документа, а в порядке, который определяется ключами, вычисленными при помощи xsl:sort. Первый элемент xsl:sort, присутствующий в родительском элементе, определяет первичный ключ сортировки, второй элемент — вторичный ключ, и так далее.
Как можно видеть, элемент xsl:sort определяет сортировку достаточно гибко, но вместе с тем не следует забывать, что эти возможности могут быть реализованы в процессорах далеко не полностью. Поэтому одна и та же сортировка может быть выполнена в разных процессорах по-разному.
Приведем простой пример сортировки имен и фамилий.

Пример

Листинг 8.10. Входящий документ
<list>
    <person>
        <name>William</name>
        <surname>Gibson</surname>
    </person>
    <person>
        <name>William</name>
        <surname>Blake</surname>
    </person>
    <person>
        <name>John</name>
        <surname>Fowles</surname>
    </person>
</list>
Отсортируем этот список сначала по именам в убывающем, а затем по фамилиям в возрастающем порядке.
<xsl:template match="list">
    <xsl:copy>
        <xsl:for-each select="person">
            <xsl:sort select="name" order="descending"/>
            <xsl:sort select="surname"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>
Листинг 8.12. Выходящий документ
<list>
    <person>
        <name>William</name>
        <surname>Blake</surname>
    </person>
    <person>
        <name>William</name>
        <surname>Gibson</surname>
    </person>
    <person>
        <name>John</name>
        <surname>Fowles</surname>
    </person>
</list>
К сожалению, сортировкой нельзя управлять динамически. Все атрибуты элемента xsl:sort должны обладать фиксированными значениями.
XSL