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 6-5.

Example 6-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/>
  </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 6-6.

Example 6-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 6-7 and Example 6-8.

Example 6-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 6-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.[10] Hence, you should know how to rename things when you don’t get the names quite right on the first get go.

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

Example 6-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 6-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 6-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(../../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. 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 recopied:

<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>


[10] 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 hope Leonardo will agree when he is old enough to know the difference.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.121.242