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