Sometimes when you are building a result tree, you just want to copy
nodes out of an XML document without altering them. You can do this
with the copy
and copy-of
instruction elements. You will learn about the differences in these
two elements and how to use them in this chapter.
I’ll start with the copy
element.
You’ll be working with several XML documents relating to the European Union (EU) in the examples that follow. You can read more about the EU—in at least 12 different languages—at http://europa.eu.int/. The document eu.xml , Example 6-1, found in the directory examples/ch06, represents member states, founding member states, and candidate member states from the EU.
<?xml version="1.0" encoding="UTF-8"?> <!-- European Union member states and candidate states --> <eu> <member> <state>Austria</state> <state founding="yes">Belgium</state> <state>Denmark</state> <state>Finland</state> <state founding="yes">France</state> <state founding="yes">Germany</state> <state>Greece</state> <state>Ireland</state> <state founding="yes">Italy</state> <state founding="yes">Luxembourg</state> <state founding="yes">The Netherlands</state> <state>Portugal</state> <state>Spain</state> <state>Sweden</state> <state>United Kingdom</state> </member> <candidate> <state>Bulgaria</state> <state>Cyprus</state> <state>Czech Republic</state> <state>Estonia</state> <state>Hungary</state> <state>Latvia</state> <state>Lithuania</state> <state>Malta</state> <state>Poland</state> <state>Romania</state> <state>Slovenia</state> <state>Slovakia</state> <state>Turkey</state> </candidate> </eu>
There are currently, as of early 2003, 15 member states in the EU,
and 13 candidate member states. If you wanted to duplicate element
nodes from this document, you could use the XSLT
copy
instruction element, though it has certain
limitations.
First, consider this inadequate stylesheet, copy.xsl :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="eu">
<xsl:apply-templates select="member"/>
</xsl:template>
<xsl:template match="member">
<xsl:apply-templates select="state[2]"/>
</xsl:template>
<xsl:template match="state">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
The output method is explicitly XML because that’s
really the only output method that makes any sense with
copy
. This stylesheet targets the second
state
element in eu.xml for
copying. This state
element has the word
Belgium
as content, and also has a
founding
attribute, indicating that Belgium is one
of the founding members of the EU. If you apply the stylesheet to
eu.xml, however, with:
xalan eu.xml copy.xsl
you get this result:
<?xml version="1.0" encoding="UTF-8"?> <state/>
Where’s the attribute value and the element content?
Surprise! The copy
element
doesn’t get it for you. It copies the current node,
but only if it is an element and has any namespace nodes associated
with that element. That’s it. This is sometimes
called a shallow copy
. A shallow copy does not copy any
attribute nodes or child nodes, including text nodes. This is a good
thing, though, because sometimes that’s exactly what
you want. I’ll show you why.
Suppose, for example, that you wanted to pull all the elements that represent the six founding states of the EU out of eu.xml. You could do this with the stylesheet founding.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:template match="eu"> <xsl:apply-templates select="member"/> </xsl:template> <xsl:template match="member"> <eu-members> <xsl:apply-templates select="state[@founding]"/> </eu-members> </xsl:template> <xsl:template match="state"> <xsl:copy> <xsl:apply-templates/> </xsl:copy> </xsl:template> </xsl:stylesheet>
This stylesheet finds all the state
elements that
are children of member
, and that also have
founding
attributes, and wraps them all in an
eu-members
document element. The addition of the
apply-templates
element as a child of
copy
means that the template will process all
children of the matched nodes, including text, invoking the built-in
templates whenever there is a match. When you process
eu.xml against
founding.xsl:
xalan -i 1 eu.xml founding.xsl
you will get the following:
<?xml version="1.0" encoding="UTF-8"?> <eu-members> <state>Belgium</state> <state>France</state> <state>Germany</state> <state>Italy</state> <state>Luxembourg</state> <state>The Netherlands</state> </eu-members>
The copy
element outputs the
state
element, and
apply-templates
picks up the text content of the
state
elements through the built-in template for
text. Because attributes are not considered children of elements,
apply-templates
does not return their values.
Attributes have parents, however, which are always elements.
The copy
element has one optional attribute,
use-attribute-sets
. In Chapter 2, you saw this attribute in action. It allows
you to invoke a named set of attributes stored in an
attribute-set
element, adding them to an element
in the result tree. The following stylesheet,
notfounding.xsl, shown in Example 6-2, shows you how it’s done.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:attribute-set name="new"> <xsl:attribute name="founding">no</xsl:attribute> </xsl:attribute-set> <xsl:template match="eu"> <xsl:apply-templates select="member"/> </xsl:template> <xsl:template match="member"> <eu> <members> <xsl:apply-templates select="state[not(@founding)]"/> </members> </eu> </xsl:template> <xsl:template match="state"> <xsl:copy use-attribute-sets="new"> <xsl:apply-templates/> </xsl:copy> </xsl:template> </xsl:stylesheet>
In contrast to founding.xsl, this stylesheet
finds state
elements that do not have
founding
attributes by using the XPath Boolean
function not( )
. When it finds these elements, it
uses the attribute set named new
to add a new
founding
attribute to the resulting element. Apply
it with:
xalan -i 1 eu.xml notfounding.xsl
and you’ll get this result:
<?xml version="1.0" encoding="UTF-8"?> <eu> <members> <state founding="no">Austria</state> <state founding="no">Denmark</state> <state founding="no">Finland</state> <state founding="no">Greece</state> <state founding="no">Ireland</state> <state founding="no">Portugal</state> <state founding="no">Spain</state> <state founding="no">Sweden</state> <state founding="no">United Kingdom</state> </members> </eu>
As you can see, the XSLT processor grabbed all
state
elements that did not have a
founding
attribute and then added a new
founding
attribute with a value of
no
to each of them. The stylesheet also duplicated
the eu
and members
elements
using literal result elements.
One other thing: the copy
element can contain a template. That’s why it can
have an apply-templates
child, as shown in the
following example, identity.xsl. A
copy
element can even contain other
copy
elements.
You can create an identity
transform that copies nodes by using the copy
element. The stylesheet identity.xsl matches all
nodes and then copies each of them:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="US-ASCII"/> <xsl:template match="@*|node( )"> <xsl:copy> <xsl:apply-templates select="@*|node( )"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
The XPath syntax @*
matches
attributes, or (|
) node( )
matches all other nodes. apply-templates
then
triggers the
built-in templates for any node it
finds.
The document identity.xml contains an example of each of the seven node types in the XPath data model (root, element, attribute, text, comment, processing instruction, and namespace):
<?xml-stylesheet href="identity.xsl" type="text/xsl"?> <!-- EU state: Belgium --> <state member="true" xmlns="urn:wyeast-net:eu">Belgium</state>
Because identity.xml contains an XML stylesheet PI, you can transform it using:
xalan -a identity.xml
The copy looks very much like the original, except Xalan adds an XML declaration:
<?xml version="1.0" encoding="US-ASCII"?> <?xml-stylesheet href="identity.xsl" type="text/xsl"?> <!-- EU state: Belgium --> <state xmlns="urn:wyeast-net:eu" member="true">Belgium</state>
The copy
element works fine in some situations,
but in other instances, you may prefer to copy an
element’s attributes and children automatically, as
a matter of course. You can do that with
copy-of
.
The copy-of
element goes further than its
counterpart copy
. Where copy
by
itself only copies element nodes and their associated namespace
nodes, copy-of
copies element and namespace nodes,
plus attribute nodes and children. This is called a deep
copy
.
The stylesheet copy-of.xsl demonstrates the difference between a shallow copy and a deep copy (note bold):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="eu">
<xsl:apply-templates select="member"/>
</xsl:template>
<xsl:template match="member">
<xsl:apply-templates select="state[2]"/>
</xsl:template>
<xsl:template match="state">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
The only difference between copy.xsl and
copy-of.xsl is that
copy.xsl uses copy
with no
attributes, and copy-of.xsl uses
copy-of
with a select
attribute. You will see the real evidence when you process
eu.xml against copy-of.xsl:
xalan eu.xml copy-of.xsl
This produces:
<?xml version="1.0" encoding="UTF-8"?> <state founding="yes">Belgium</state>
Instead of just copying the element node as with
copy
, copy-of
copies the
element node state
, its attribute
founding
with its value, and its text node child
Belgium
.
The copy-of
element can also copy other kinds of
child nodes. The stylesheet candidate.xsl copies
all the state
children of
candidate
in eu.xml with the
deep copy method using copy-of
, and it grabs the
eu
and candidate
elements using
a shallow copy method with copy
:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="eu">
<xsl:copy>
<xsl:apply-templates select="candidate"/>
</xsl:copy>
</xsl:template>
<xsl:template match="candidate">
<xsl:copy>
<xsl:copy-of select="state"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Apply the stylesheet using this line:
xalan -i 1 eu.xml candidate.xsl
and you will get the following output:
<?xml version="1.0" encoding="UTF-8"?> <eu> <candidate> <state>Bulgaria</state> <state>Cyprus</state> <state>Czech Republic</state> <state>Estonia</state> <state>Hungary</state> <state>Latvia</state> <state>Lithuania</state> <state>Malta</state> <state>Poland</state> <state>Romania</state> <state>Slovakia</state> <state>Slovenia</state> <state>Turkey</state> </candidate> </eu>
This creates a new document that contains only the names of the 13 European state candidates.
Using
copy-of
, you can piece together new documents by
copying the nodes you want wholesale. So far, you have only seen how
to copy nodes out of one document as, customarily, an XSLT processor
handles only one input document or source tree. This example will
show you how to process more than one input document with the XSLT
function document( )
. You caught a glimpse of
document( )
in Chapter 4; Chapter 13 will cover document( )
in
more detail.
In addition to eu.xml, you will also find other.xml in examples/ch06, which contains a list of non-EU states, shown in Example 6-3.
<?xml version="1.0" encoding="UTF-8"?> <!-- Other non-EU, European states --> <eu> <other> <state>Albania</state> <state>Andorra</state> <state>Belarus</state> <state>Estonia</state> <state>Bosnia-Herzegovina</state> <state>Croatia</state> <state>Iceland</state> <state>Liechtenstein</state> <state>Macedonia, Former Yugoslav Republic of</state> <state>Moldova</state> <state>Monaco</state> <state>Norway</state> <state>Russia</state> <state>San Marino</state> <state>Serbia and Montenegro</state> <state>Switzerland</state> <state>Ukraine</state> <state>Vatican City</state> </other> </eu>
Using the document( )
function, the stylesheet
two.xsl takes nodes out of
other.xml in addition to
eu.xml:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="eu">
<xsl:copy>
<xsl:comment>
<xsl:text>Member states: </xsl:text>
<xsl:value-of select="count(member/state)"/>
</xsl:comment>
<xsl:copy-of select="member"/>
<xsl:comment>
<xsl:text>Candidate states: </xsl:text>
<xsl:value-of select="count(candidate/state)"/>
</xsl:comment>
<xsl:copy-of select="candidate"/>
<xsl:comment>
<xsl:text>Other states: </xsl:text>
<xsl:value-of select="count(document('other.xml')/eu/other/state)"/>
</xsl:comment>
<xsl:copy-of select="document('other.xml')/eu/other"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The nodes in the first source tree contained in the file
eu.xml are fed to the XSLT processor via the
command line. The document( )
function picks up
the source in other.xml at the stylesheet level.
This stylesheet also uses the count(
)
function to count
state
elements, arriving at the number of
countries in each category: member, candidate, and other. With this
command:
xalan -i 1 eu.xml two.xsl
the processor will produce the following result, shown in Example 6-4.
<?xml version="1.0" encoding="UTF-8"?> <eu> <!--Member states: 15--> <member> <state>Austria</state> <state founding="yes">Belgium</state> <state>Denmark</state> <state>Finland</state> <state founding="yes">France</state> <state founding="yes">Germany</state> <state>Greece</state> <state>Ireland</state> <state founding="yes">Italy</state> <state founding="yes">Luxembourg</state> <state founding="yes">The Netherlands</state> <state>Portugal</state> <state>Spain</state> <state>Sweden</state> <state>United Kingdom</state> </member> <!--Candidate states: 13--> <candidate> <state>Bulgaria</state> <state>Cyprus</state> <state>Czech Republic</state> <state>Estonia</state> <state>Hungary</state> <state>Latvia</state> <state>Lithuania</state> <state>Malta</state> <state>Poland</state> <state>Romania</state> <state>Slovakia</state> <state>Slovenia</state> <state>Turkey</state> </candidate> <!--Other states: 18--> <other> <state>Albania</state> <state>Andorra</state> <state>Belarus</state> <state>Estonia</state> <state>Bosnia-Herzegovina</state> <state>Croatia</state> <state>Iceland</state> <state>Liechtenstein</state> <state>Macedonia, Former Yugoslav Republic of</state> <state>Moldova</state> <state>Monaco</state> <state>Norway</state> <state>Russia</state> <state>San Marino</state> <state>Serbia and Montenegro</state> <state>Switzerland</state> <state>Ukraine</state> <state>Vatican City</state> </other> </eu>
In this chapter, you have learned how to copy nodes using the
copy
element for shallow copies and the
copy-of
element for deep copies. You also learned
how to merge nodes from more than one source tree with the
document( )
function (more on this in Chapter 13). In the next chapter, you’ll
learn how to use variables and parameters.
3.17.79.20