如何使用XSLT创建单个元素集的子集?

时间:2009-07-01 08:42:19

标签: xml xslt

我有一个xml的形式:

<Set>
   <Element name="Superset1_Set1_Element1"/>
   <Element name="Superset1_Set1_Element2"/>
   <Element name="Superset1_Set2_Element1"/>
   <Element name="Superset2_Set1_Element1"/>
   <Element name="Superset2_Set2_Element1"/>
</Set>

我希望将其转换为以下形式的xml:

<Superset name="Superset1">
   <Set name="Set1">
       <Element name="Element1"/>
       <Element name="Element2"/>
   </Set>
   <Set name="Set2">
       <Element name="Element1"/>
   </Set>
</Superset>
<Superset name="Superset2">
   <Set name="Set1">
       <Element name="Element1"/>
   </Set>
   <Set name="Set2">
       <Element name="Element1"/>
   </Set>
</Superset>

如何使用XSLT完成此操作?

提前多多感谢!

2 个答案:

答案 0 :(得分:5)

这可以通过以下XSLT 1.0转换来解决:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes" />

  <!-- this key selects elements by their "Superset" name -->
  <xsl:key name="kElementBySuperset" match="Element" use="
    substring-before(@name, '_')" 
  />

  <!-- this key selects elements by their "Superset_Set" name -->
  <xsl:key name="kElementBySet" match="Element" use="
    concat(
      substring-before(@name, '_'), 
      '_',
      substring-before(substring-after(@name, '_'), '_')
    )
  " />

  <!--- initalize output (note the template modes) -->
  <xsl:template match="Set">
    <xsl:apply-templates select="Element" mode="Superset">
      <xsl:sort select="@name" />
    </xsl:apply-templates>
  </xsl:template>

  <!-- output <Superset> elements, grouped by name -->
  <xsl:template match="Element" mode="Superset">
    <xsl:variable name="vSupersetName" select="
      substring-before(@name, '_')
    " />

    <xsl:if test="
      generate-id() 
      = 
      generate-id(key('kElementBySuperset', $vSupersetName)[1])
    ">
      <Superset name="{$vSupersetName}">
        <xsl:apply-templates 
          select="key('kElementBySuperset', $vSupersetName)" 
          mode="Set"
        >
          <xsl:sort select="@name" />
        </xsl:apply-templates>
      </Superset>
    </xsl:if>
  </xsl:template>

  <!-- output <Set> elements, grouped by name -->
  <xsl:template match="Element" mode="Set">
    <xsl:variable name="vSetName" select="
      concat(
        substring-before(@name, '_'), 
        '_',
        substring-before(substring-after(@name, '_'), '_')
      )"
    />

    <xsl:if test="
      generate-id() 
      = 
      generate-id(key('kElementBySet', $vSetName)[1])
    ">
      <Set name="{substring-after($vSetName, '_')}">
        <xsl:apply-templates 
          select="key('kElementBySet', $vSetName)" 
          mode="Element"
        >
          <xsl:sort select="@name" />
        </xsl:apply-templates>
      </Set>
    </xsl:if>
  </xsl:template>

  <!-- output <Element> elements -->
  <xsl:template match="Element" mode="Element">
    <xsl:variable name="vElementName" select="
      substring-after(
        substring-after(@name, '_'), 
        '_'
      )
    " />

    <Element name="{$vElementName}" />
  </xsl:template>

</xsl:stylesheet>

应用于输入文档时系统上的输出:

<Superset name="Superset1">
  <Set name="Set1">
    <Element name="Element1" />
    <Element name="Element2" />
  </Set>
  <Set name="Set2">
    <Element name="Element1" />
  </Set>
</Superset>
<Superset name="Superset2">
  <Set name="Set1">
    <Element name="Element1" />
  </Set>
  <Set name="Set2">
    <Element name="Element1" />
  </Set>
</Superset>

值得注意的是,此解决方案区分大小写。我认为在你的情况下这是可取的(或至少没有害处)。如果需要不区分大小写,那么必须洒掉少数几个(当然“......”必须用缺失的字母代替):

translate($anyvalue, 'ABC…XYZ', 'abc…xyz')

我避免了这种情况,因为它非常重复并使解决方案(甚至更多)变得模糊不清。

进一步阅读:我的解决方案之一是使用两个<xsl:key>进行类似的两步分组:

XSLT 3-level grouping on attributes

内部有点冗长,它包含<xsl:key>的冗长解释,我想避免在这里重复。 ; - )

答案 1 :(得分:3)

保留供参考,但我强烈建议尽可能使用模板(即Tomalak的解决方案)以便于阅读......


当然可能,但实际上比我预期的更难,因为二阶集和双重下划线 - 如果“名称”值的格式更友好,下面肯定会得到改善。

<xsl:key name="supers" match="Set/Element" use="substring-before(@name,'_')"/>
<xsl:key name="sets" match="Set/Element" use="concat(substring-before(@name,'_'),'_',substring-before(substring-after(@name,'_'),'_'))"/>

<xsl:template match="/">
    <xsl:for-each select="Set/Element[generate-id() = generate-id(key('supers',substring-before(@name,'_'))[1])]">      
        <xsl:variable name="super" select="substring-before(@name,'_')"/>
            <Superset name="{$super}">          
            <xsl:for-each select="//Set/Element[generate-id() = generate-id(key('sets',concat($super,'_',substring-before(substring-after(@name,'_'),'_')))[1])]">
            <Set name="{substring-before(substring-after(@name,'_'),'_')}">
                <xsl:variable name="set" select="concat($super,'_',substring-before(substring-after(@name,'_'),'_'))"/>
                <xsl:for-each select="//Set/Element[starts-with(@name,$set)]">
                    <Element name="{substring-after(substring-after(@name,'_'),'_')}"/>
                </xsl:for-each>
            </Set>
            </xsl:for-each>
            </Superset>
    </xsl:for-each>
</xsl:template>

技巧只是muenchian grouping并捕获正确的键值。

它真的不漂亮,所以我确信有更好的解决方案可用,但我是时差的:P