Previous Page Next Page

Recipe 8.3. Renaming Elements or Attributes

Problem

You need to rename or re-namespace elements or attributes in an XML document.

Solution

If you need to rename a small number of attributes or elements, use a straightforward version of the overriding copy idiom, as shown in Example 8-4.

Example 8-5. Rename person to individual
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<xsl:import href="copy.xslt"/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
   
<xsl:template match="person">
  <individual>
    <xsl:apply-templates select="@* | node( )"/>
  </individual>
</xsl:template>
   
</xsl:stylesheet>

Or, alternatively, use xsl:element:

...
<xsl:template match="person">
  <xsl:element name="individual">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>
...

Renaming attributes is just as straightforward:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<xsl:import href="copy.xslt"/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
   
<xsl:template match="@lastname">
  <xsl:attribute name="surname">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>
   
</xsl:stylesheet>

Sometimes you need to re-namespace rather than rename, as shown in Example 8-5.

Example 8-6. A document using the namespace foo
<foo:someElement xmlns:foo="http://www.ora.com/XMLCookbook/namespaces/foo">
  <foo:aChild>
    <foo:aGrandChild/>
    <foo:aGrandChild>
    </foo:aGrandChild>
  </foo:aChild>
</foo:someElement>

For each element in the foo namespace, create a new element in the bar namespace, as shown in Example 8-6 and Example 8-7.

Example 8-7. A stylesheet that maps foo to bar
<xsl:stylesheet version="1.0"   
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:foo="http://www.ora.com/XMLCookbook/namespaces/foo"
 xmlns:bar="http://www.ora.com/XMLCookbook/namespaces/bar">
   
<xsl:import href="copy.xslt"/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   
<xsl:strip-space elements="*"/>
   
<xsl:template match="foo:*">
  <xsl:element name="bar:{local-name( )}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>     
   
</xsl:stylesheet>

Example 8-8. Output
<bar:someElement xmlns:bar="http://www.ora.com/XMLCookbook/namespaces/bar">
   <bar:aChild>
      <bar:aGrandChild/>
      <bar:aGrandChild/>
   </bar:aChild>
</bar:someElement>

Discussion

Naming is an important skill that few software practitioners (including yours truly) have mastered.[2] Hence, you should know how to rename things when you don't get the names quite right on the first get go.

[2] As evidence of my naming ineptitude, my son actually spent two whole days in this world without a name. My wife and I simply could not think of a good one that we both liked. To our credit, we both understood the importance of picking a good name and we think Leonardo agrees.

If many elements or attributes need renaming, then you may want to use a generic table-driven approach, as shown in Example 8-8 to Example 8-10.

Example 8-9. A generic table-driven rename stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ren="http://www.ora.com/namespaces/rename">
   
<xsl:import href="copy.xslt"/>
   
<!--Override in importing stylesheet -->
<xsl:variable name="lookup"  select="/.."/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   
<xsl:template match="*">
  <xsl:choose>
    <xsl:when test="$lookup/ren:element[@from=name(current( ))]">
      <xsl:element 
           name="{$lookup/ren:element[@from=local-name(current( ))]/@to}">
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates/>
      </xsl:element>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-imports/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="@*">
  <xsl:choose>
    <xsl:when test="$lookup/ren:attribute[@from=name(current( ))]">
      <xsl:attribute name="{$lookup/ren:attribute[@from=name(current( ))]/@to}">
        <xsl:value-of select="."/>
      </xsl:attribute>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-imports/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
   
</xsl:stylesheet>

Example 8-10. Using the table driven stylesheet
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ren="http://www.ora.com/namespaces/rename">
   
<xsl:import href="TableDrivenRename.xslt"/>
   
<!-- Load the lookup table. We define it locally but it can also
 come from an external file -->     
<xsl:variable name="lookup"  select="document('')/*[ren:*]"/>
   
<!-- Define the renaming rules -->
<ren:element from="person" to="individual"/>
<ren:attribute from="firstname" to="givenname"/>
<ren:attribute from="lastname" to="surname"/>
<ren:attribute from="age" to="yearsOld"/>
   
</xsl:stylesheet>

Example 8-11. Output
<?xml version="1.0" encoding="UTF-8"?>
<people which="MeAndMyFriends">
   
   <individual givenname="Sal" surname="Mangano" yearsOld="38" height="5.75"/>
   
   <individual givenname="Mike" surname="Palmieri" yearsOld="28" height="5.10"/>
   
   <individual givenname="Vito" surname="Palmieri" yearsOld="38" height="6.0"/>
   
   <individual givenname="Vinny" surname="Mari" yearsOld="37" height="5.8"/>
   
</people>

You can still use this approach if some elements or attributes need context-sensitive handling. For example, consider the following document fragment:

<clubs>
  <club name="The 500 Club">
    <members>
       <member name="Joe Smith">
         <position name="president"/>
      </member>
       <member name="Jill McFonald">
         <position name="treasurer"/>
      </member>
       <!-- ... -->
    <members>
  </club>
  <!-- ... -->
<clubs>

Suppose you want to change attribute @name to attribute @title, but only for position elements. If you use the table-driven approach, all elements containing a name attribute will be changed. The solution is to create a template that overrides the default behavior for all elements except position:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ren="http://www.ora.com/namespaces/rename">
   
<xsl:import href="TableDrivenRename.xslt"/>
   
<!-- Load the lookup table. We define it locally but it can also
 come from an external file -->     
<xsl:variable name="lookup"  select="document('')/*[ren:*]"/>
   
<!-- Define the renaming rules -->
<ren:attribute from="name" to="title"/>
   
<!--OVEVRIDE: Simply copy all names that are not attributes of position element -->
<xsl:template match="@name[not(parent::position)]">
     <xsl:copy/>
</xsl:template>
   
</xsl:stylesheet>

When re-namespacing using copy, the old namespace may stubbornly refuse to go away even when it is not needed. Consider the foo document again with an additional element from a doc namespace:

<foo:someElement xmlns:foo="http://www.ora.com/XMLCookbook/namespaces/foo" xmlns:
doc="http://www.ora.com/XMLCookbook/namespaces/doc">
  <foo:aChild>
    <foo:aGrandChild/>
    <foo:aGrandChild>
      <doc:doc>This documentation should not be removed or altered in any way.
      </doc:doc>
    </foo:aGrandChild>
  </foo:aChild>
</foo:someElement>

If you apply the re-namespacing stylesheet to this document, the foo namespace is carried along with the doc element:

<bar:someElement xmlns:bar="http://www.ora.com/XMLCookbook/namespaces/bar">
   <bar:aChild>
      <bar:aGrandChild/>
      <bar:aGrandChild>
         <doc:doc xmlns:doc="http://www.ora.com/XMLCookbook/namespaces/doc" 
            xmlns:foo="http://www.ora.com/XMLCookbook/namespaces/foo">
          This documentation should not be removed or altered in any way.
         </doc:doc>
      </bar:aGrandChild>
   </bar:aChild>
</bar:someElement>

This is because the doc element is processed by xsl:copy. Both xsl:copy and xsl:copy-of always copy all namespaces associated with an element. In XSLT 2.0 both xsl:copy and xsl:copy-of have an optional attribute called copy-namespaces, which you can set to yes or no. Since the doc element is enclosed in elements from the foo namespace, it has a foo namespace node, even though it is not directly visible in the input. To avoid copying this unwanted namespace, use xsl:element to make sure that elements are recreated, not copied:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:foo="http://www.ora.com/XMLCookbook/namespaces/foo"
 xmlns:bar="http://www.ora.com/XMLCookbook/namespaces/bar">
   
<xsl:import href="copy.xslt"/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   
<xsl:strip-space elements="*"/>
   
<!-- For all elements create a new element with the same 
name and namespace 
-->
<xsl:template match="*">
  <xsl:element name="{name( )}" namespace="{namespace-uri( )}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>
   
<xsl:template match="foo:*">
  <xsl:element name="bar:{local-name( )}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>     
   
</xsl:stylesheet>

You can even use this technique to strip all namespaces from a document:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="copy.xslt"/>
   
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   
<xsl:strip-space elements="*"/>
   
<xsl:template match="*">
  <xsl:element name="{local-name( )}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>
   
</xsl:stylesheet>


Previous Page Next Page