Search code examples
xmlxslttransformstylesheetelement

xml - xslt - transform a chosen element into attribute


I am trying to transform an XML document into a new one where the only one of the elements transforms itself into an attribute and keeps the rest of the document tree in the same way... Here is the XML document

    <?xml version="1.0" encoding="UTF-8"?>
    <cities>
    <city>
        <cityID>c1</cityID>
        <cityName>Atlanta</cityName>
        <cityCountry>USA</cityCountry>
        <cityPop>4000000</cityPop>
        <cityHostYr>1996</cityHostYr>   

    </city>

    <city>
        <cityID>c2</cityID>
        <cityName>Sydney</cityName>
        <cityCountry>Australia</cityCountry>
        <cityPop>4000000</cityPop>
        <cityHostYr>2000</cityHostYr>   
        <cityPreviousHost>c1</cityPreviousHost >   
    </city>

    <city>
        <cityID>c3</cityID>
        <cityName>Athens</cityName>
        <cityCountry>Greece</cityCountry>
        <cityPop>3500000</cityPop>
        <cityHostYr>2004</cityHostYr>   
        <cityPreviousHost>c2</cityPreviousHost >   
    </city>

    </cities>

I am trying to get the "cityID" element into an attribute to "city" and keep the rest. Here is my .xsl so far. Unfortunately it seems to lose the rest of the elements:

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

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


    <xsl:template match="city">
        <xsl:element name="city" use-attribute-sets="NameAttributes"/>
    </xsl:template>


    <xsl:attribute-set name="NameAttributes">
        <xsl:attribute name="cityID">
            <xsl:value-of select="cityID"/>
        </xsl:attribute>
    </xsl:attribute-set>

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


</xsl:stylesheet>

Solution

  • I would use

    <xsl:template match="city">
        <city cityID="{cityID}">
           <xsl:apply-templates select="node() except cityID"/> <!-- except is XSLT 2.0, use select="node()[not(self::cityID)]" for XSLT 1.0 -->
        </city>
    </xsl:template>
    

    or write a template for

    <xsl:template match="cityID">
      <xsl:attribute name="{name()}" select="."/>
      <!-- XSLT 1.0 <xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute> -->
    </xsl:template>
    

    All attributes need to be created before child nodes, so using <xsl:strip-space elements="*"/> might be necessary for that approach to work.

    Both suggestions assume the presence of the identity transformation template, which you have.