开发者

Identifying unique subtrees in XSL

I have some XML that looks like this:

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

I have some XSL that I'm using to generate Java code from this XML. Previously I've been making a key, then generating a Java class for each group.

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

All was well. Now, I've discovered that some messages have groups using the same name as groups present elsewhere, but missing one of the fields. To continue the example XML from above:

  <message name="nana">
    <field type="string" name="na" />
    <group name="foo">
      <field type="integer" name="id" />
      <field type="integer" name="value" />
    </group>
  </message>

A group named "foo" is present, but it's missing the field with name "action".

What I'd like to do is to generate a Java class for each unique subtree. Is this possible? I can't work out what the xsl:key for that would look like. The closest idea I've had is

<xsl:key name="groupsKey" match="//group" use="concat(@name,count(*))"/>

which works for the case in the example above, but is hardly elegant. If there were instead two groups named "foo" with the same number (but different types) of fields, it would fail, so it's not actually a solution.

To be clear, the ideal key (or whatever alternative) would end up calling the template only once for the "peter" and "wendy" cases above, once for the "nana" case and again once for this case:

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

...because the fields within the group are different to those in the other cases. My key above doesn't cover this case. Is there a way to do so?


This transformation fulfills the requirements:

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

When applied on the provided XML document (with all additions):

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

the wanted result is produced:

  class foo|(integer+integer+integer)
  class foo|(integer+integer)
  class foo|(string+integer+integer)

It is left as an exercise to the reader to further adjust this to produce valid names in one's PL, and also to make this work with structures of unlimited nestedness (which I may do in another answer -- however, we need a more precise definition for this more general provlem).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜