Doing a pivot using XSLT

2020-06-20 02:14发布

I have an xml file like this:

<root>
    <item>
        <name>one</name>
        <status>good</status>
    </item>
    <item>
        <name>two</name>
        <status>good</status>
    </item>
    <item>
        <name>three</name>
        <status>bad</status>
    </item>
    <item>
        <name>four</name>
        <status>ugly</status>
    </item>
    <item>
        <name>five</name>
        <status>bad</status>
    </item>
</root>

I want to transform this using XSLT to get something like:

<root>
    <items><status>good</status>
        <name>one</name>
        <name>two</name>
    </items>
    <items><status>bad</status>
        <name>three</name>
        <name>five</name>
    </items>
    <items><status>ugly</status>
        <name>four</name>
    </items>
</root>

In other words, I get a list of items, each with a status, and I want to turn it into a list of statuses, each with a list of items.

My initial thought was to do apply-templates matching each status type in turn, but that means I have to know the complete list of statuses. Is there a better way to do it?

Thanks for any help.

标签: xslt pivot
4条回答
孤傲高冷的网名
2楼-- · 2020-06-20 02:32

Yes, this can be done in XSLT 1.0

<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="kStatByVal" 
         match="status" use="."/>
    <!--                                   -->
    <xsl:key name="kItemByStat" 
         match="item" use="status"/>
    <!--                                   -->
    <xsl:variable name="vDoc" select="/"/>

    <xsl:template match="/">
      <top>
        <xsl:for-each select=
        "/*/*/status[generate-id()
                    =
                     generate-id(key('kStatByVal',.)[1])
                    ]">
          <items>
            <status><xsl:value-of select="."/></status>
          <xsl:for-each select="key('kItemByStat', .)">
            <xsl:copy-of select="name"/>
          </xsl:for-each>
          </items>
        </xsl:for-each>
      </top>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the original XML document:

<root>
    <item>
        <name>one</name>
        <status>good</status>
    </item>
    <item>
        <name>two</name>
        <status>good</status>
    </item>
    <item>
        <name>three</name>
        <status>bad</status>
    </item>
    <item>
        <name>four</name>
        <status>ugly</status>
    </item>
    <item>
        <name>five</name>
        <status>bad</status>
    </item>
</root>

The wanted result is produced:

<top>
    <items>
        <status>good</status>
        <name>one</name>
        <name>two</name>
    </items>
    <items>
        <status>bad</status>
        <name>three</name>
        <name>five</name>
    </items>
    <items>
        <status>ugly</status>
        <name>four</name>
    </items>
</top>

Do note the use of:

  1. The Muenchian method for grouping

  2. The use of <xsl:key> and the key() function

查看更多
你好瞎i
3楼-- · 2020-06-20 02:39

It depends about your xslt engine. If you're using xslt 1.0 without any extension, then your approach is certainly the best.

On the other side, if you're allowed to use exslt (especially the node-set extension) or xslt 2.0, then you could do it in a more generic way:

  1. Collect all the available statuses
  2. Create a node-set from the obtained result
  3. Iterating on this node set, create your pivot by filtering you status base on the current element in your iteration.

But before doing that, consider that it may be overkill if you only have a few set of statuses and that adding another status is quite rare.

查看更多
ら.Afraid
4楼-- · 2020-06-20 02:48

Muench to the rescue!

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>

    <xsl:key name="muench" match="/root/item/status" use="."/>

    <xsl:template match="/">
        <root>
        <xsl:for-each select="/root/item/status[generate-id() = generate-id(key('muench',.)[1])]">
            <xsl:call-template name="pivot">
                <xsl:with-param name="status" select="."/>
            </xsl:call-template>
        </xsl:for-each>
        </root>
    </xsl:template>

    <xsl:template name="pivot">
        <xsl:param name="status"/>
        <items>
            <status><xsl:value-of select="$status"/></status>
            <xsl:for-each select="/root/item[status=$status]">
                <name><xsl:value-of select="name"/></name>
            </xsl:for-each>
        </items>
    </xsl:template>

</xsl:stylesheet>
查看更多
来,给爷笑一个
5楼-- · 2020-06-20 02:52

In XSLT 2.0 you can replace the muenchian grouping by its standard grouping mechanism. Applied to the given answer the xslt would look like:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>

    <xsl:key name="muench" match="/root/item/status" use="."/>

    <xsl:template match="/">
        <root>
        <xsl:for-each-group select="/root/item/status" group-by="key('muench', .)">
            <xsl:call-template name="pivot">
                <xsl:with-param name="status" select="."/>
            </xsl:call-template>
        </xsl:for-each-group>
        </root>
    </xsl:template>

    <xsl:template name="pivot">
        <xsl:param name="status"/>
        <items>
            <status><xsl:value-of select="$status"/></status>
            <xsl:for-each select="/root/item[status=$status]">
                <name><xsl:value-of select="name"/></name>
            </xsl:for-each>
        </items>
    </xsl:template>

</xsl:stylesheet>
查看更多
登录 后发表回答