Recipe8.3.Renaming Elements or Attributes


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>




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net