Идентификация уникальных поддеревьев в XSL

#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.