Chapter 6. Copying Nodes

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.

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.

Example 6-1. An XML document listing EU member and candidate states
<?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.

Adding Attributes with copy

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.

Example 6-2. An XSLT stylesheet that adds attributes in the course of a copy
<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.

The Identity Transform

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

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.

Copying Nodes from Two Documents

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.

Example 6-3. An XML document listing non-EU states
<?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.

Example 6-4. An XML document listing European countries and their relation to the EU
<?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>

Summary

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.

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

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