Transform multiple elements with same attribute name to a new element with those attribute values as child-elements

Viewed 70

I am almost there, but stuck at the last part. I am trying to transform this:

<record>
    <title language="en">title EN</title>
    <title language="nl">title NL</title>

    <technique language="en">tech EN</technique>
    <technique language="nl">tech NL</technique>
</record>

to

<record>
    <title>
      <en>title EN</en>
      <nl>title NL</nl>
    </title>
    <technique>
      <en>tech EN</en>
      <nl>tech NL</nl>
    </technique>
</record>

I have a stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 <!-- identity transform for otherwise-unmatched nodes and attributes -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

<!-- transform for title elements at any depth -->
  <xsl:template match="title">
            <xsl:element name="{@language}">
            <!-- transform this node's non-attribute children -->
           <xsl:apply-templates select="node()" />
           </xsl:element>
  </xsl:template>

 <xsl:template match="technique">
            <xsl:element name="{@language}">
            <!-- transform this node's non-attribute children -->
           <xsl:apply-templates select="node()" />
           </xsl:element>
  </xsl:template>

</xsl:stylesheet>

but that produces this:

<record>
      <en>title EN</en>
      <nl>title NL</nl>

      <en>tech EN</en>
      <nl>tech NL</nl>
</record>

so almost there, But I can't figure out how to get the new elements nested in the <title> and <technique> elements

I hope somebody can help me out, thanks in advance!

Wouter


Edit:

here is the complete XML (well, only 2 elements, but this should give a better idea of what I want to achieve. Only the elements that are there twice (with the language attribute) should be transformed...the rest of the doc should remain intact

<?xml version="1.0" encoding="UTF-8"?>
<resultset>
   <records>
      <record>
         <metadata>
            <dcls />
            <recordnumber>1</recordnumber>
            <publisher>XXX</publisher>
         </metadata>
         <data>
            <records>
               <objectid>12</objectid>
               <objectnumber>1212</objectnumber>
               <artist>
                  Bob
                  <alphasort>Bob</alphasort>
                  <displaydate>1937</displaydate>
               </artist>
               <title language="NL">Title in NL</title>
               <title language="EN">Title in EN</title>
               <category language="NL">Schilderkunst</category>
               <category Language="EN">Paintings</category>
               <dated>1972</dated>
               <datebegin>1972</datebegin>
               <dateend>1972</dateend>
               <technique language="NL">olieverf op doek</technique>
               <technique language="EN">oil on canvas</technique>
               <dimensions>150 x 150 cm</dimensions>
               <webtext language="EN">Some EN text</webtext>
               <webtext language="NL">Some NL text</webtext>
               <standplaats>Amsterdam</standplaats>
            </records>
         </data>
      </record>
      <record>
         <metadata>
            <dcls />
            <recordnumber>2</recordnumber>
            <publisher>XXX</publisher>
         </metadata>
         <data>
            <records>
               <objectid>33</objectid>
               <objectnumber>3333</objectnumber>
               <artist>
                  Saskia
                  <alphasort>Saskia</alphasort>
                  <displaydate>1982</displaydate>
               </artist>
               <title language="EN">title in EN</title>
               <title language="NL">title in NL</title>
               <Series>1 / 2</Series>
               <category language="NL">Fotografie</category>
               <category Language="EN">Photography</category>
               <dated>2016</dated>
               <datebegin>2016</datebegin>
               <dateend>2016</dateend>
               <technique language="NL">C-print op aluminium</technique>
               <technique language="EN">C-print on aluminum</technique>
               <dimensions>A2 (2x)</dimensions>
               <webtext language="NL">Some NL text</webtext>
               <webtext language="EN">Some EN text</webtext>
               <standplaats>Amsterdam</standplaats>
            </records>
         </data>
      </record>
   </records>
</resultset> 
3 Answers

It is not mandatory to use identity transform template.

Two simple loops (pull style) do the job.

XSLT

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

    <xsl:template match="/record">
        <record>
            <title>
                <xsl:for-each select="title">
                    <xsl:element name="{@language}">
                        <xsl:value-of select="."/>
                    </xsl:element>
                </xsl:for-each>
            </title>
            <technique>
                <xsl:for-each select="technique">
                    <xsl:element name="{@language}">
                        <xsl:value-of select="."/>
                    </xsl:element>
                </xsl:for-each>
            </technique>
        </record>
    </xsl:template>
</xsl:stylesheet>

If (as it seems) you know in advance the names of the elements with the language attribute, you could do simply:

XSLT 1.0

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

<xsl:template match="/record">
    <xsl:copy>
        <title>
            <xsl:apply-templates select="title"/>
        </title>
        <technique>
            <xsl:apply-templates select="technique"/>
        </technique>
    </xsl:copy>
</xsl:template>

<xsl:template match="title | technique">
    <xsl:element name="{@language}">
        <xsl:value-of select="."/>
    </xsl:element>
</xsl:template>

</xsl:stylesheet>

Otherwise you would need to group them by name first.

Or this could also be done in a completely generic way :

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

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="*">
    <xsl:copy>
        <xsl:for-each-group select="*" group-by="name()">
          <xsl:element name="{current-grouping-key()}">
            <xsl:for-each select="current-group()/@*">
              <xsl:element name="{.}">
                <xsl:value-of select="../."/>
              </xsl:element>
            </xsl:for-each>
          </xsl:element>
        </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>

See it working here : https://xsltfiddle.liberty-development.net/93nwMoz

Related