#xslt #xpath #xslt-1.0
#xslt #xpath #xslt-1.0
Вопрос:
У меня есть некоторый XML, который выглядит следующим образом:
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
У меня есть некоторый XSL, который я использую для генерации Java-кода из этого XML. Ранее я создавал ключ, а затем генерировал класс Java для каждой группы.
<xsl:key name="groupsByName" match="//group" use="@name"/>
....
<xsl:for-each select="//group[generate-id(.) = generate-id(key('groupsByName',@name)[1])]">
<xsl:call-template name="class-for-group"/>
</xsl:for-each>
Все было хорошо. Теперь я обнаружил, что в некоторых сообщениях есть группы, использующие то же имя, что и группы, присутствующие в другом месте, но в них отсутствует одно из полей. Чтобы продолжить приведенный выше пример XML:
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
Группа с именем «foo» присутствует, но в ней отсутствует поле с именем «действие».
Что я хотел бы сделать, так это сгенерировать класс Java для каждого уникального поддерева. Возможно ли это? Я не могу понять, как xsl:key
для этого будет выглядеть. Ближайшая идея, которая у меня была, это
<xsl:key name="groupsKey" match="//group" use="concat(@name,count(*))"/>
который работает для случая в примере выше, но вряд ли является элегантным. Если бы вместо этого были две группы с именем «foo» с одинаковым количеством (но разными типами) полей, это привело бы к сбою, так что на самом деле это не решение.
Чтобы было понятно, идеальный ключ (или любая другая альтернатива) в конечном итоге вызывал бы шаблон только один раз для случаев «Питер» и «венди» выше, один раз для случая «нана» и еще раз для этого случая:
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
…потому что поля внутри группы отличаются от полей в других случаях. Мой приведенный выше ключ не охватывает этот случай. Есть ли способ сделать это?
Комментарии:
1. Итак, в чем вопрос? В случае, если вы просите кого-либо предоставить «более элегантное» решение, тогда вы должны определить «ellegant».
2. @Dimitre, ключ, который я предоставил выше, на самом деле не решает проблему. Это отвратительный взлом. Я поясню.
3. Кроме того, вы должны показать точный результат, который должен быть создан — в противном случае он не определен, и это явно не проблема…
4. @Jon: Кроме того, неясно, что именно такое «каждое уникальное поддерево».
5. @Dimitre, тебе не нужно беспокоиться о результатах. Я хочу, чтобы он вызывал шаблон один раз для каждого уникального поддерева. То, что в шаблоне, не имеет значения?
Ответ №1:
Это преобразование удовлетворяет требованиям:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kGroupByType" match="group"
use="@type"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:call-template name="makeType"/>
</xsl:copy>
</xsl:template>
<xsl:template mode="pass2"
match="group[generate-id()
=
generate-id(key('kGroupByType',@type)[1])
]
">
class <xsl:value-of select="concat(@name, '|', @type)"/>
</xsl:template>
<xsl:template name="makeType">
<xsl:attribute name="type">
<xsl:text>(</xsl:text>
<xsl:for-each select="*">
<xsl:value-of select="@type"/>
<xsl:if test="not(position()=last())"> </xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
При применении к предоставленному XML-документу (со всеми дополнениями):
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
получен желаемый результат:
class foo|(integer integer integer)
class foo|(integer integer)
class foo|(string integer integer)
В качестве упражнения читателю предлагается дополнительно настроить это для создания допустимых имен в своем PL, а также заставить это работать со структурами неограниченной вложенности (что я могу сделать в другом ответе — однако нам нужно более точное определение для этого более общего правила).
Комментарии:
1. Дмитрий, большое спасибо. Я все еще работаю над интеграцией этого ответа в свой собственный XSLT. Как только я сделаю это и удостоверюсь для себя, что это работает, я отмечу это правильно.
2. 1 Правильный ответ: сложный ключ (выходящий за рамки выражений XPath / XSLT) будет нуждаться в такой композиции преобразования.
3. Теперь все работает, еще раз спасибо. Димитр: при использовании exslt в ответе, возможно, стоит подчеркнуть это. В моем случае сначала мне пришлось переключиться на обработку с использованием Xalan (а не любого другого JRE по умолчанию), прежде чем все начало работать.
4. @Jon-Bright: Хорошее замечание: Я обычно указываю EXSLT
ext:node-set()
потому что он реализован большинством процессоров XSLT. Кроме того, не думал, что его использование останется незамеченным, потому что в верхнем элементе определено новое пространство имен, что ясно показывает, что используется EXSLT.