Search code examples
xsltxslt-2.0

Pushing XML elements except one into a second level in XSL without hard-coding element names


I have below XML input.

<value>
  <UniqueId>160052-ASDF</UniqueId>
  <ShipmentId>160052</ShipmentId>
  <PickListNo>160052_1</PickListNo>
</value>

The output I need is below.

<value> and <UniqueID> tags are always present. Other tags vary for different messages. Those needs to be enclosed within a <Payload> tag.

<value>
  <UniqueId>160052-ASDF</UniqueId>
  <Payload>
    <ShipmentId>160052</ShipmentId>
    <PickListNo>160052_1</PickListNo>
  </Payload>
</value>

Created following XSL, but I need a generic solution without hard-coding XML tags, other than <value> and <UniqueID> tags.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"       xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <value>
    <UniqueId>
      <xsl:value-of   select="/value/UniqueId"/>
    </UniqueId>
    <Payload>
      <ShipmentId>
        <xsl:value-of   select="/value/ShipmentId"/>
      </ShipmentId>
      <PickListNo>
        <xsl:value-of   select="/value/PickListNo"/>
      </PickListNo>
    </Payload>
  </value>
</xsl:template>
</xsl:stylesheet>

Tried using something like below, but no luck.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <value>
    <UniqueId>
      <xsl:value-of   select="/value/UniqueId"/>
    </UniqueId>
    <Payload>
      <xsl:for-each select="*[not(self::UniqueId)]">
        <xsl:copy-of select="."/>
      </xsl:for-each>
    </Payload>
  </value>
</xsl:template>
</xsl:stylesheet>

Can anyone please help?


Solution

  • Use e.g.

      <xsl:template match="value">
        <xsl:copy>
          <xsl:apply-templates select="UniqueId"/>
          <xsl:where-populated>
            <Payload>
              <xsl:apply-templates select="* except UniqueId"/>
            </Payload>
          </xsl:where-populated>
        </xsl:copy>
      </xsl:template>
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    

    The xsl:mode and xsl:where-populated is XSLT 3, the current version and generally supported version of XSLT; if you are stuck with XSLT 2 replace the xsl:mode with the identity transformation template

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

    and the xsl:where-populated with <xsl:if test="* except UniqueId">.