Алексей Валиков - Технология XSLT
Представим себе два простых преобразования, first.xsl и second.xsl, первое из которых заменяет во входящем документе элементы а на элементы b, а второе — элементы b на элементы с.
Листинг 10.13. Преобразование first.xsl<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="a">
<b>
<xsl:apply-templates select="@*|node()"/>
</b>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Листинг 10.14. Преобразование second.xsl<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="b">
<c>
<xsl:apply-templates select="@*|node()"/>
</c>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Для того чтобы последовательно применить два этих преобразования к некоторому входящему документу a.xml, мы можем, например, дважды вызвать процессор:
java org.apache.xalan.xslt.Process -IN a.xml -XSL first.xsl -OUT b.xml
java org.apache.xalan.xslt.Process -IN b.xml -XSL second.xsl -OUT c.xml
В результате этих вызовов XSLT-процессор Xalan сначала применит преобразование first.xsl к документу a.xml и сохранит результат в файле b.xml, а затем обработает полученный документ b.xml при помощи преобразования second.xml и сохранит результат в файле c.xml.
В качестве альтернативы, например, для тех случаев, когда пакетная обработка невозможна, мы можем создать преобразование, последовательно применяющее шаблоны преобразований first.xsl и second.xsl к входящему документу. Для этого:
□ назначим шаблонам преобразования first.xsl режим first, а шаблонам преобразования second.xsl — режим second;
□ в основном шаблоне применим шаблоны режима first к узлам входящего документа, сохранив результат в переменной b;
□ приведем результирующее дерево, содержащееся в переменной b ко множеству узлов;
□ обработаем полученное множество узлов шаблонами режима second.
Следующий листинг демонстрирует предложенный подход.
Листинг 10.5. Преобразование first-then-second.xsl<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
exclude-result-prefixes="xalan">
<!-- Шаблоны преобразования first -->
<xsl:template match="a" mode="first">
<b>
<xsl:apply-templates select="@*|node()" mode="first"/>
</b>
</xsl:template>
<xsl:template match="@*|node()" mode="first">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="first"/>
</xsl:copy>
</xsl:template>
<!-- Шаблоны преобразования second -->
<xsl:template match="@*|node()" mode="second">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="second"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b" mode="second">
<c>
<xsl:apply-templates select="@*|node()" mode="second"/>
</c>
</xsl:template>
<!-- Основное преобразование -->
<xsl:template match="/">
<!-- Присваиваем переменной а корень входящего документа -->
<xsl:variable name="a" select="/"/>
<!-- Выводим переменную a -->
<xsl:comment> a: </xsl:comment>
<xsl:copy-of select="$a"/>
<!-- Присваиваем переменной b результат обработки переменной a -->
<xsl:variable name="b">
<xsl:apply-templates select="$a" mode="first"/>
</xsl:variable>
<!-- Выводим переменную b -->
<xsl:comment> b: </xsl:comment>
<xsl:copy-of select="$b"/>
<!-- Присваиваем переменной с результат обработки переменной b -->
<xsl:variable name="c">
<xsl:apply-templates select="xalan:nodeset($b)" mode="second"/>
</xsl:variable>
<!-- Выводим переменную c -->
<xsl:comment> c: </xsl:comment>
<xsl:copy-of select="$c"/>
</xsl:template>
</xsl:stylesheet>
Ход этого преобразования лучше всего прокомментирует полученный результат.
Листинг 10.16. Входящий документ<а>
<a>1</a>
<a>2</a>
</а>
Листинг 10.17. Выходящий документ<!-- а: -->
<а>
<a>1</a>
<a>2</a>
</а>
<!-- b:-->
<b>
<b>1</b>
<b>2</b>
</b>
<!-- с: -->
<с>
<c>1</c>
<c>2</c>
</с>
Элементы расширения
Другой, несколько реже используемой, но не менее мощной возможностью расширения XSLT являются элементы расширения. В отличие от обычных элементов, при выполнении преобразования элементы расширения не просто копируются в выходящее дерево. При их обработке процессор должен выполнить определенные действия. Например, многие XSLT-процессоры, написанные на Java, позволяют связывать элементы расширения с методами Java-классов.
ПримерПредположим, что при выполнении преобразования в выходящий документ нам необходимо включить информацию о том, когда документ был сгенерирован — добавить элемент вида:
This page was generated at 10:23.
Пожалуй, самым элегантным решением этой задачи будет использование элемента расширения, который копировал бы в выходящий документ текущее время. Иначе говоря, при выполнении шаблона вида:
<xsl:template match="/">
<!-- ... -->
This page was generated at <ext:time/>.
</xsl:template>
элемент расширения ext:time должен быть заменен текущим временем. Ниже мы приведем пример реализации этого элемента для процессора Xalan.
Интерфейс программирования расширений в Xalan требует, чтобы для каждого элемента расширения был определен метод вида:
тип элемент(org.apache.xalan.extensions.XSLProcessorContext context,
org.apache.xalan.templates.ElemExtensionCall elem)
где тип — тип возвращаемого значения, а элемент — локальная часть имени элемента расширения. Поскольку мы создаем элемент с локальной частью имени time и строковым типом возвращаемых данных, прототип нашего метода будет выглядеть как:
public String time(XSLProcessorContext context,
ElemExtensionCall elem)
Два аргумента, которые передаются методу элемента расширения, описывают контекст преобразования (XSLProcessorContext) и параметры вызова элемента расширения (ElemExtensionCall). Чуть позже мы покажем, как можно использовать эти объекты для создания более функциональных элементов расширения; пока же продолжим с элементом ext:time.
Следующим шагом мы создадим класс расширения ext.java, в котором реализуем описанный выше метод time.
Листинг 10.18 Класс ext.javapackage de.fzi.xslt;
import java.util.Date;
import java.text.SimpleDateFormat;
import org.apache.xalan.extensions.XSLProcessorContext;
import org.apache.xalan.templates.ElemExtensionCall;
public class ext {
public String time(XSLProcessorContext context,
ElemExtensionCall elem) {
SimpleDateFormat df = new SimpleDateFormat("HH:mm");
return df.format(new Date());
}
}
Равно как и в случае с функциями расширения, связующим звеном между элементами и Java-имплементацией их семантики служат пространства имен. В нашем случае класс de.fzi.xslt.ext может быть связан с префиксом пространства имен ext следующим объявлением:
xmlns:ext="xalan://de.fzi.xslt.ext"
Однако это еще не все. Для того чтобы элементы определенного пространства имен воспринимались процессором как элементы расширения, необходимо также явно указать префиксы этих пространств в атрибуте extension-element-prefixes элемента xsl:stylesheet:
<xsl:stylesheet
...
extension-element-prefixes="ext">
...
</xsl:stylesheet>
В итоге наше преобразование будет иметь следующий вид.
Листинг 10.19. Преобразование, использующее элемент расширения<xsl:stylesheet