1

I have an XML file with this structure

<levels>
  <level id="0" qd="NE">
    <gate>99</gate>
    <zone>2</zone>
    <laydown>4</laydown>
  </level>
  <level id="0" qd="SE">
    <gate>1</gate>
    <zone>6</zone>
    <laydown>1</laydown>
    <laydown>2</laydown>
    <zone>5</zone>
    <laydown>3</laydown>
  </level>
</levels>

And I need to convert it to something like this to display in a grid

<level id="0" qd="NE" gate="99" zone="2"  laydown="4">
<level id="0" qd="SE" gate="1" zone="6,5" laydown="1,2,3">

The order of the comma separated list is not important for me. I am an xsl/xslt newbie so any annotation will be helpful. I work with VS2013 which I gather only supports XSLT 1. Previously asked in the title for XSLT 2 so thought it best for this to be a separate question.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
Nick Hoare
  • 21
  • 2

2 Answers2

2

Here is a very efficient approach:

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

  <xsl:key name="kDistinctChildren" match="level/*"
           use="concat(generate-id(..), '+', name())" />

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

  <xsl:template match="level">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <xsl:apply-templates mode="group"
             select="*[generate-id() = 
                       generate-id(key('kDistinctChildren',
                                   concat(generate-id(..), '+', name()))[1])]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*" mode="group">
    <xsl:attribute name="{name()}">
      <xsl:apply-templates select="key('kDistinctChildren', 
                                     concat(generate-id(..), '+', name()))" 
                           mode="joinWithCommas"/>
    </xsl:attribute>
  </xsl:template>

  <xsl:template match="*" mode="joinWithCommas">
    <xsl:value-of select="concat(., substring(',', 1, position() != last()))"/>
  </xsl:template>

</xsl:stylesheet>

When run on your sample input, the output is:

<levels>
  <level id="0" qd="NE" gate="99" zone="2" laydown="4" />
  <level id="0" qd="SE" gate="1" zone="6,5" laydown="1,2,3" />
</levels>
JLRishe
  • 99,490
  • 19
  • 131
  • 169
1

Not a great idea answering a question that shows zero effort at attempting to answer the question... but, here it is anyway.

XSL 1.0:

<xsl:template match="//level">
    <xsl:element name="level">
        <xsl:copy-of select="@*" />
        <xsl:for-each select="*">
            <xsl:call-template name="groupByName" />
        </xsl:for-each>
    </xsl:element>
</xsl:template>

<xsl:template name="groupByName">
    <xsl:variable name="name" select="local-name()" />
    <xsl:variable name="nodes" select="../*[$name=local-name()]" />
    <xsl:attribute name="{local-name($nodes)}">
        <xsl:for-each select="$nodes">
            <xsl:value-of select="." />
            <xsl:if test="not(position() = last())">,</xsl:if>
        </xsl:for-each>
    </xsl:attribute>
</xsl:template>

Gives:

<level id="0" qd="NE" gate="99" zone="2" laydown="4"></level>
<level id="0" qd="SE" gate="1" zone="6,5" laydown="1,2,3"></level>

Example:

http://www.xsltcake.com/slices/e3si2j/3

References:

  1. XSLT concat string, remove last comma
Community
  • 1
  • 1
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • Well, this *will* work - but is it a good solution? Basically you are overwriting the same attribute as many times as there are nodes with the same name. The mind rebels at such waste of CPU power... – michael.hor257k Dec 19 '14 at 03:07
  • This will not work correctly for the following input: ` 99 2 4 99 2 4 `. – JLRishe Dec 19 '14 at 04:54
  • Thanks @JLRishe, good catch! I've reverted back to my previous version (which worked better anyway). – Nick Grealy Dec 19 '14 at 05:08
  • I did spend some time making an effort at this but as a newbie I new it was rubbish so did not see the point in including it. I foolishly thought XML was simple and so thought I would use it but have ended up spending 3 or 4 times as much time as a DB solution would have taken. As to the the xslt code well ..... I have tried the above and apart from VS2013 crashing and restarting I am getting an error 'This document already has a 'Document Element node'. – Nick Hoare Dec 19 '14 at 17:25
  • @NickHoare not a problem Nick, XSLT is not an easy beast to tame! Never dismiss your own attempt at the code, I'd much prefer helping someone with their own code, than just giving them the answer. Good luck! – Nick Grealy Dec 20 '14 at 13:09