开发者

how to sort values with XSLT?

开发者 https://www.devze.com 2023-04-08 15:56 出处:网络
I have an XML similar to this: <Test> <grapes> <a>TypeA</a> <b>value1</b>

I have an XML similar to this:

<Test>
  <grapes>
     <a>TypeA</a>
     <b>value1</b>
  </grapes>
  <oranges>
     开发者_开发百科<a>TypeB</a>
     <b>value2</b>
  </oranges>
  <apples>
    <a>TypeA</a>
     <b>value3</b>
  </apples>
</Test>

where the values are unique but the Type, might be the same.

I am trying to sort it so that the output is similar to this:

<group type="TypeA">
  <value v="value1" />
  <value v="value3" />
</group>
<group type="TypeB">
  <value v="value2" />
</group>

I am having a hard time making sure the groups are unique in the output and the values are in the right group.

How should my XSL be structured?


Here is a much simpler solution (completely "push style", no <xsl:for-each>, no nesting, no <xsl:variable>, no current(), , no //, no axes):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kGoodsByType" match="/*/*" use="a"/>

 <xsl:template match=
  "/*/*[generate-id()
       =
        generate-id(key('kGoodsByType', a)[1])
       ]
  ">
     <group type="{a}">
       <xsl:apply-templates select="key('kGoodsByType', a)/b"/>
     </group>
 </xsl:template>

 <xsl:template match="b">
  <value v="{.}"/>
 </xsl:template>

 <xsl:template match="*/* | text()" priority="-1"/>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<Test>
    <grapes>
        <a>TypeA</a>
        <b>value1</b>
    </grapes>
    <oranges>
        <a>TypeB</a>
        <b>value2</b>
    </oranges>
    <apples>
        <a>TypeA</a>
        <b>value3</b>
    </apples>
</Test>

the wanted, correct result is produced:

<group type="TypeA">
   <value v="value1"/>
   <value v="value3"/>
</group>
<group type="TypeB">
   <value v="value2"/>
</group>

Explanation: Muenchian grouping of /*/* using as key the string values of their a children.

II. XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <xsl:for-each-group select="*/a" group-by=".">
   <group type="{current-grouping-key()}">
     <xsl:sequence select="current-group()/../b"/>
   </group>
  </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>

When this transformation is performed on the same XML document (above), the same correct result is produced:

<group type="TypeA">
   <b>value1</b>
   <b>value3</b>
</group>
<group type="TypeB">
   <b>value2</b>
</group>

Explanation:

  1. <xsl:for-each-group>

  2. current-group()

  3. current-grouping-key()


XSLT 1.0 :

You start by creating unique groups for your types using the muenchian method. Google it to find out what it is. Then it's just a matter of iterating through them and printint out what you want, how you want it :

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="types" match="a" use="text()"/>
<xsl:template match="/">
<result>
  <xsl:for-each select="//a[generate-id(.) = generate-id(key('types', text())[1])]">
    <group type="{current()/text()}">
      <xsl:for-each select="//a[text() = current()/text()]">
        <xsl:variable name="values" select="following-sibling::b | preceding-sibling::b"/>
        <xsl:for-each select="$values">
          <value v="{current()}"/>
        </xsl:for-each>
      </xsl:for-each>
    </group>
  </xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>

You will find that the output is identical to what you expect.


My proposal is slightly different from FailedDev's:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="/Test">
    <root>
      <xsl:for-each select="*[a != following::a]">
        <xsl:sort select="a" data-type="text" />
        <group type="{a}">
          <xsl:for-each select="/Test/*[a = current()/a]">
            <xsl:sort select="b" data-type="text" />
            <value v="{b}" />
          </xsl:for-each>
        </group>
      </xsl:for-each>
    </root>
  </xsl:template>
</xsl:stylesheet>

The outer

<xsl:for-each select="*[a != following::a]" />

select all unique types, ie. TypeA and TypeB. The

<xsl:sort select="a" data-type="text" />

sorts these according to name, making sure TypeA will appear above TypeB. The inner

<xsl:for-each select="/Test/*[a = current()/a]" />

selects a list of unique values for each type, ie. for TypeA the values value1 and value3 are extracted. Again, the resulting list is sorted to list value1 before value3.

0

精彩评论

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