tags:

views:

343

answers:

4

I have a simple xml document that looks like the following snippet. I need to write a XSLT transform that basically 'unpivots' this document based on some of the attributes.

<?xml version="1.0" encoding="utf-8" ?>
<root xmlns:z="foo">
    <z:row A="1" X="2" Y="n1" Z="500"/>
    <z:row A="2" X="5" Y="n2" Z="1500"/>
</root>

This is what I expect the output to be -

<?xml version="1.0" encoding="utf-8" ?>
<root xmlns:z="foo">
    <z:row A="1" X="2"  />
    <z:row A="1" Y="n1" />
    <z:row A="1" Z="500"/>
    <z:row A="2" X="5" />
    <z:row A="2" Y="n2"/>
    <z:row A="2" Z="1500"/>
</root>

Appreciate your help.

A: 

Here is a bit of a brute force way:

<xsl:template match="z:row">
 <xsl:element name="z:row">
  <xsl:attribute name="A">
   <xsl:value-of select="@A"/>
  </xsl:attribute>
  <xsl:attribute name="X">
   <xsl:value-of select="@X"/>
  </xsl:attribute>
 </xsl:element>
 <xsl:element name="z:row">
  <xsl:attribute name="A">
   <xsl:value-of select="@A"/>
  </xsl:attribute>
  <xsl:attribute name="Y">
   <xsl:value-of select="@Y"/>
  </xsl:attribute>
 </xsl:element>
 <xsl:element name="z:row">
  <xsl:attribute name="A">
   <xsl:value-of select="@A"/>
  </xsl:attribute>
  <xsl:attribute name="Z">
   <xsl:value-of select="@Z"/>
  </xsl:attribute>
 </xsl:element>
</xsl:template>


<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>

Darrel Miller
The {} syntax introduces an "Attribute Value Template". So look that up in the spec or elsewhere. You can use more than one in a single attribute value, for example "{@X}_{@Y}_{@Z}"
Steve Jessop
Except that I wanted $ for the value of an attribute, rather than @ for the attribute node itself...
Steve Jessop
A: 
<xsl:template match="row">
    <row A="{$A}" X="{$X}" />
    <row A="{$A}" Y="{$Y}" />
    <row A="{$A}" Z="{$Z}" />
</xsl:template>

Plus obvious boilerplate.

Steve Jessop
I like the {} syntax. Can you point me to some docs on their usage? I'm struggling to fine anything significant
Darrel Miller
Wow, this is my *only* answer currently in negative territory, and it's just been accepted. Presumably on a "simplest thing that works" principle. It's not strictly correct, since I didn't put the namespace in: see JeniT's answer for that.
Steve Jessop
+1  A: 

This is more complex but also more generic:

<xsl:template match="z:row">
 <xsl:variable name="attr" select="@A"/>
 <xsl:for-each select="@*[(local-name() != 'A')]">
  <xsl:element name="z:row">
   <xsl:copy-of select="$attr"/>
   <xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
  </xsl:element>
 </xsl:for-each>
</xsl:template>
Garth Gilmour
You could also store the name() and namespace-uri() of z:row into variables and then construct the element using them, that way you wouldn't have to hard-code the element name.
jelovirt
+3  A: 

Here's the full stylesheet you need (since the namespaces are important):

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

<xsl:template match="root">
  <root>
    <xsl:apply-templates />
  </root>
</xsl:template>

<xsl:template match="z:row">
  <xsl:variable name="A" select="@A" />
  <xsl:for-each select="@*[local-name() != 'A']">
    <z:row A="{$A}">
      <xsl:attribute name="{local-name()}">
        <xsl:value-of select="." />
      </xsl:attribute>
    </z:row>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

I much prefer using literal result elements (eg <z:row>) rather than <xsl:element> and attribute value templates (those {}s in attribute values) rather than <xsl:attribute> where possible as it makes the code shorter and makes it easier to see the structure of the result document that you're generating. Others prefer <xsl:element> and <xsl:attribute> because then everything is an XSLT instruction.

If you're using XSLT 2.0, there are a couple of syntactic niceties that help, namely the except operator in XPath and the ability to use a select attribute directly on <xsl:attribute>:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  xmlns:z="foo">

<xsl:template match="root">
  <root>
    <xsl:apply-templates />
  </root>
</xsl:template>

<xsl:template match="z:row">
  <xsl:variable name="A" as="xs:string" select="@A" />
  <xsl:for-each select="@* except @A">
    <z:row A="{$A}">
      <xsl:attribute name="{local-name()}" select="." />
    </z:row>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>
JeniT