Алексей Валиков - Технология XSLT
result-prefix="#default"/>
означает, что элементы, принадлежащие в преобразовании пространству имен а, в выходящем документе должны принадлежать пространству имен по умолчанию. Определение вида
<xsl:namespace-alias
stylesheet-prefix="#default"
result-prefix="a"/>
означает, что элементы, принадлежащие в преобразовании пространству имен по умолчанию, в выходящем документе должны принадлежать пространству имен а.
Пример Листинг 8.17. Преобразование<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="urn:a"
xmlns="urn:b">
<xsl:namespace-alias
stylesheet-prefix="#default"
result-prefix="a"/>
<xsl:namespace-alias
stylesheet-prefix="a"
result-prefix="#default"/>
<xsl:template match="root">
<result>
<a:element/>
</result>
</xsl:template>
</xsl:stylesheet>
Листинг 8.18. Выходящий документ<result xmlns="urn:a" xmlns:a="urn:b">
<a:element/>
</result>
Результатом этого преобразования является то, что пространство имен с URI "urn:а" стало пространством имен по умолчанию, а пространство имен с URI "urn:b" изменило префикс на а.
В преобразованиях можно объявлять несколько псевдонимов пространств имен при условии, что одно и то же пространство имен преобразования не должно быть объявлено элементами xsl:namespace-alias с одинаковым порядком импорта псевдонимом для различных пространств имен выходящего документа.
ПримерЕсли преобразование a.xsl содержит определение
<xsl:namespace-alias
stylesheet-prefix="x"
result-prefix="a"/>
а преобразование b.xsl — определение
<xsl:namespace-alias
stylesheet-prefix="x"
result-prefix="b"/>
где в обоих преобразованиях префикс x представляет одно пространство имен, а пространства имен a и b — разные, то преобразование a.xsl не сможет включать преобразование b.xsl и наоборот, потому что они будут иметь одинаковый порядок импорта и содержать элементы xsl:namespace-alias, назначающие разным пространствам имен одинаковые псевдонимы. В одном преобразовании такие псевдонимы также не имеют права встречаться. Если же подобное все же случилось, процессор может сигнализировать ошибку или использовать определение, которое было дано в преобразовании последним.
Совсем иначе обстоит дело с импортированием. При импортировании определения старших в порядке импорта преобразований могут переопределять определения младших преобразований. Таким образом, если преобразование a.xsl будет импортировать преобразование b.xsl, пространство имен x будет назначено псевдонимом пространству имен а и наоборот.
Ключи
Прежде чем мы приступим к разбору ключей, которые являются одной из самых мощных концепций языка XSLT, попробуем решить одну несложную задачку.
Листинг 8.19. Входящий документ<items>
<item source="a" name="A"/>
<item source="b" name="B"/>
<item source="a" name="C"/>
<item source="c" name="D"/>
<item source="b" name="E"/>
<item source="b" name="F"/>
<item source="c" name="G"/>
<item source="a" name="H"/>
</items>
Пусть входящий документ представляет собой список объектов (элементов item), каждый из которых имеет имя (атрибут name) и источник (атрибут source). Требуется сгруппировать объекты по своим источникам и получить документ приблизительно следующего вида.
Листинг 8.20. Требуемый результат<sources>
<source name="a">
<item source="a" name="A"/>
<item source="a" name="C"/>
<item source="a" name="H"/>
</source>
<source name="b">
<item source="b" name="B"/>
<item source="b" name="E"/>
<item source="b" name="F"/>
</source>
<source name="c">
<item source="c" name="D"/>
<item source="c" name="G"/>
</source>
</sources>
Первым шагом на пути решения этой задачи является формулировка в терминах XSLT предложения "сгруппировать объекты по своим источникам". Источник каждого объекта определяется его атрибутом source, значит множество объектов, принадлежащих одному источнику "а", будет определяться путем выборки
/items/item[@source='a']
Тогда для каждого элемента item в его группу войдут элементы, которые будут выбраны выражением
/items/item[@source=current()/@source]
Попробуем использовать этот факт в следующем шаблоне:
<xsl:template match="item">
<source name="{@source}">
<xsl:copy-of select="/items/item[@source=current()/@source]"/>
</source>
</xsl:template>
Как и ожидалось, при применении этого правила к элементам item для каждого из них будет создана группа, принадлежащая тому же источнику, — уже хороший результат, но в условии требуется создать по группе не для каждого объекта, а для каждого источника. Чтобы достичь этого, можно создавать группу только для первого объекта, принадлежащего ей. Провести такую проверку опять же несложно: объект будет первым в группе тогда и только тогда, когда ему не предшествуют другие, элементы item, принадлежащие тому же источнику. Иначе говоря, создаем группы только для тех элементов, для которых выражение
preceding-sibling::item[@source-current()/@source]
будет возвращать пустое множество.
С небольшими добавлениями искомое преобразование целиком будет иметь вид.
Листинг 8.21. Преобразование<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="items">
<sources>
<xsl:apply-templates/>
</sources>
</xsl:template>
<xsl:template match="item">
<xsl:choose>
<xsl:when
test="preceding-sibling::item[@source=current()/@source]"/>
<xsl:otherwise>
<source name="{@source}">
<xsl:copy-of select="self::node()
|following-sibling::item[@source=current()/@source]"/>
</source>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Бесспорно, решение было несложным, но довольно громоздким. Самым же узким местом в этом преобразовании является обращение к элементам item источника текущего элемента посредством сравнения атрибутов source.
Проблема совершенно стандартна для многих преобразований: нужно выбирать узлы по определенным признакам, причем делать это нужно как можно более эффективно. Хорошо, что в нашем документе было всего восемь элементов item, но представьте себе ситуацию, когда элементов действительно много.
Проблема, которую мы подняли, достаточно серьезна. Она состоит в оптимизации поиска узлов с определенными свойствами в древовидно организованной структуре.
Попробуем разобраться в смысле фразы "узел обладает определенными свойствами". Очевидно, это означает, что для этого узла выполняется некое логическое условие, иначе говоря, некий предикат обращается в "истину".
Однако какого именно типа условия мы чаще всего проверяем? Анализируя различные классы задач, можно придти к выводу, что в большинстве случаев предикаты являются равенствами — выражениями, которые обращаются в "истину" тогда и только тогда, когда некоторый параметр узла, не зависящий от текущего контекста, равен определенному значению. В нашем примере смысл предиката на самом деле состоит не в том, чтобы проверить на истинность выражение @source=current()/@source, а в том, чтобы проверить на равенство @source и current()/@source.
Если переформулировать это для общего случая, то нам нужно выбрать не те узлы, для которых истинно выражение A=B, скорее нужно выбрать те, для которых значение A равно значению B. Иначе говоря, узел будет идентифицироваться значением в своего свойства A. И если мы заранее вычислим значения свойств A, проблема поиска узлов в дереве сведется к классической проблеме поиска элементов множества (в нашем случае — узлов дерева) по определенным значениям ключей (в нашем случае — значениями свойств A).
Чтобы пояснить это, вернемся к нашему примеру: мы ищем элементы item со значением атрибута source, равным заданному. Свойством, идентифицирующим эти элементы, в данном случае будут значения их атрибутов source, которые мы можем заранее вычислить и включить в табл. 8.2.