darkknightuga
darkknightuga

Reputation: 13

Wrapping multiple identically named elements into a new element using XSLT

I'm trying to reformat some XML using XSLT. A snippet of my input looks like this:

<toggles>
  <toggle toggleDisplayName="Charges">
    <anotherElement attribute="value" />
    <gridColumn sourceField.name="FIELD1" />
    <gridColumn sourceField.name="FIELD2" />
    <gridColumn sourceField.name="FIELD3" />
  </toggle>
</toggles>

I want to wrap all 'gridColumn' elements into a single 'grid' element like so:

<toggles>
  <toggle toggleDisplayName="Charges">
    <anotherElement attribute="value" />
    <grid>
      <gridColumn sourceField.name="FIELD1" />
      <gridColumn sourceField.name="FIELD2" />
      <gridColumn sourceField.name="FIELD3" />
    </grid>
  </toggle>
</toggles>

My current XSLT is:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:msxsl="urn:schemas-microsoft-com:xslt"
     exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>

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

  <!--Identity transform for remaining-->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Right now this sort of works, but I get each 'gridColumn' element in its own 'grid' element. Is there any easy way to modify this so that I can obtain the aforementioned results?

Note: edited to clarify input XML.

Thanks in advance for any help!

Upvotes: 1

Views: 649

Answers (2)

Ian Roberts
Ian Roberts

Reputation: 122424

The simplest variant I can think of, which works if all the gridColumn elements are adjacent (forming one contiguous block with no other intervening elements) in the original input XML:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:strip-space elements="*" />
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="toggle">
    <xsl:copy>
      <!-- apply templates to everything except the second and subsequent
           gridColumn child elements -->
      <xsl:apply-templates select="@* | node()[not(self::gridColumn)]
                                      | gridColumn[1]" />
    </xsl:copy>
  </xsl:template>

  <!-- this template will be called for just the first gridColumn within
       a toggle... -->
  <xsl:template match="gridColumn">
    <grid>
      <!-- ... and will gather all its sibling gridColumn elements under the
           new grid element -->
      <xsl:copy-of select="../gridColumn" />
    </grid>
  </xsl:template>

  <!--Identity transform for remaining-->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

If the gridColumn elements are not all adjacent you'll find that they've all been gathered together into a single <grid> at the place where the first gridColumn appeared in the input XML.

Upvotes: 1

Thomas Weller
Thomas Weller

Reputation: 59640

I came up with this solution. Please check if it fulfills all your requirements.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes" />

  <!-- Process toggle separately, because its childredn cannot simply be copied -->
  <xsl:template match="toggle">
    <xsl:copy>
      <xsl:apply-templates select="./@*" /> <!-- For some unknown reason I needed this -->
      <xsl:apply-templates />
      <grid>
        <xsl:for-each select="gridColumn">
          <xsl:copy-of select="." />
        </xsl:for-each>
      </grid>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="gridColumn">
    <!-- Do nothing, because it is processed by for-each -->
  </xsl:template>

  <!--Identity transform for remaining-->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Related Questions