Specifying Patterns for the match AttributeYou can use an involved syntax with the <xsl:template> element's match attribute, and you can use an even more involved syntax with the select attribute of the <xsl:apply-templates> , <xsl:value-of> , <xsl:for-each> , <xsl:copy-of> , and <xsl: sort > elements. We'll see them both in this chapter, starting with the syntax you can use with the match attribute. Matching the Root NodeAs we've already seen, you can match the root node with / , like this: <xsl:template match="/"> <HTML> <xsl:apply-templates/> </HTML> </xsl:template> Matching ElementsYou can match elements simply by giving their name , as we've also seen: <xsl:template match="PLANETS"> <HTML> <xsl:apply-templates/> </HTML> </xsl:template> Matching ChildrenYou can use the / operator to separate element names when you want to refer to a child of a particular node. For example, say that you want to create a rule that applies only to <NAME> elements that are children of <PLANET> elements. In that case, you can match to the expression "PLANET/NAME" . Here's a rule that will surround the text of such elements in an <H3> element: <xsl:template match="PLANET/NAME"> <H3><xsl:value-of select="."/></H3> </xsl:template> Note the expression "." here. You use "." with the select attribute to specify the current node, as we'll see when discussing the select attribute. You can also use the * character as a wildcard, standing for any element ( * can match only elements). For example, this rule applies to all <NAME> elements that are grandchildren of <PLANET> elements: <xsl:template match="PLANET/*/NAME"> <H3><xsl:value-of select="."/></H3> </xsl:template> Matching Element DescendantsIn the previous section, I used the expression "PLANET/NAME" to match all <NAME> elements that are direct children of <PLANET> elements, and I used the expression "PLANET/*/NAME" to match all <NAME> elements that are grandchildren of <PLANET> elements. However, there's an easier way to perform both matches: Just use the expression "PLANET//NAME" , which matches all <NAME> elements that are inside <PLANET> elements, no matter how many levels deep (the matched elements are called descendants of the <PLANET> element). In other words, "PLANET//NAME" matches "PLANET/NAME" , "PLANET/*/NAME" , "PLANET/*/*/NAME" , and so on: <xsl:template match="PLANETS//NAME"> <H3><xsl:value-of select="."/></H3> </xsl:template> Matching AttributesYou can match an attribute if you preface its name with @ . Here's an example; in this case, I'll display the data in ch13_01.xml in an HTML table. You might note, however, that the units for the various measurements are stored in attributes, like this: <PLANET> <NAME>Earth</NAME> <MASS UNITS="(Earth = 1)">1</MASS> <DAY UNITS="days">1</DAY> <RADIUS UNITS="miles">2107</RADIUS> <DENSITY UNITS="(Earth = 1)">1</DENSITY> <DISTANCE UNITS="million miles">128.4</DISTANCE><!--At perihelion--> </PLANET> To recover the units and display them as well as the values for the mass and so on, I'll match the UNITS attribute with @UNITS . Here's how that looks. Note that I'm using the element <xsl:text> to insert a space into the output document (more on <xsl:text> later): Listing ch13_09.xsl<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/PLANETS"> <HTML> <HEAD> <TITLE> The Planets Table </TITLE> </HEAD> <BODY> <H1> The Planets Table </H1> <TABLE BORDER="1"> <TD>Name</TD> <TD>Mass</TD> <TD>Radius</TD> <TD>Day</TD> <xsl:apply-templates/> </TABLE> </BODY> </HTML> </xsl:template> <xsl:template match="PLANET"> <TR> <TD><xsl:value-of select="NAME"/></TD> <TD><xsl:apply-templates select="MASS"/></TD> <TD><xsl:apply-templates select="RADIUS"/></TD> <TD><xsl:apply-templates select="DAY"/></TD> </TR> </xsl:template> <xsl:template match="MASS"> <xsl:value-of select="."/> <xsl:text> </xsl:text> <xsl:value-of select="@UNITS"/> </xsl:template> <xsl:template match="RADIUS"> <xsl:value-of select="."/> <xsl:text> </xsl:text> <xsl:value-of select="@UNITS"/> </xsl:template> <xsl:template match="DAY"> <xsl:value-of select="."/> <xsl:text> </xsl:text> <xsl:value-of select="@UNITS"/> </xsl:template> </xsl:stylesheet> Now the resulting HTML table includes not only values, but also their units of measurement. (The spacing leaves a little to be desired, but HTML browsers will have no problem with it; we'll take a look at ways of handling whitespace later in this chapter.) <HTML> <HEAD> <TITLE> The Planets Table </TITLE> </HEAD> <BODY> <H1> The Planets Table </H1> <TABLE BORDER="1"> <TD>Name</TD><TD>Mass</TD><TD>Radius</TD><TD>Day</TD> <TR> <TD>Mercury</TD><TD>.0553 (Earth = 1)</TD><TD>1516 miles</TD><TD>58.65 days</TD> </TR> <TR> <TD>Venus</TD><TD>.815 (Earth = 1)</TD><TD>3716 miles</TD><TD>116.75 days</TD> </TR> <TR> <TD>Earth</TD><TD>1 (Earth = 1)</TD><TD>2107 miles</TD><TD>1 days</TD> </TR> </TABLE> </BODY> </HTML> You can see this result in Figure 13-3 in Internet Explorer. Figure 13-3. An XSL transformation that creates an HTML table.
You can also use the @* wildcard to select all attributes of an element. For example, "PLANET/@*" selects all attributes of <PLANET> elements. Matching by IDYou can also match elements that have a specific ID value using the pattern id() . To use this selector, you must give elements an ID attribute, and you must declare that attribute of type ID, as you can do in a DTD. Here's an example rule that adds the text of all elements that have the ID Christine : <xsl:template match = "id('Christine')"> <H3><xsl:value-of select="."/></H3> </xsl:template> Matching CommentsYou can match the text of comments with the pattern comment() . You shouldn't store data that should go into the output document in comments in the input document, of course. However, you might want to convert comments from the <!-- comment --> form into something that another markup language might use, such as a <COMMENT> element. Here's an example; ch13_01.xml was designed to include comments so that we could see how to extract them: <PLANET> <NAME>Venus</NAME> <MASS UNITS="(Earth = 1)">.815</MASS> <DAY UNITS="days">116.75</DAY> <RADIUS UNITS="miles">3716</RADIUS> <DENSITY UNITS="(Earth = 1)">.943</DENSITY> <DISTANCE UNITS="million miles">66.8</DISTANCE><!--At perihelion--> </PLANET> To extract comments and put them into <COMMENT> elements, I'll include a rule just for comments: Listing ch13_10.xsl<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="PLANETS"> <HTML> <xsl:apply-templates/> </HTML> </xsl:template> <xsl:template match="comment()"> <COMMENT> <xsl:value-of select="."/> </COMMENT> </xsl:template> </xsl:stylesheet> Here's what the result is for Venus; I've transformed the comment into a <COMMENT> element: Venus .815 116.75 3716 .943 66.8<COMMENT>At perihelion</COMMENT> Note that the text for the other elements in the <PLANET> element is also inserted into the output document. This is because the default rule for each element is to include its text in the output document. Because I haven't provided a rule for elements, their text is simply included in the output document. I'll take a closer look at default rules later in the chapter. Matching Text Nodes with text()You can match the text in a node with the pattern text() . There's really not much reason to ever use text() because XSLT includes a default rule: If there are no other rules for a text node, the text in that node is inserted into the output document. If you were to make that default rule explicit, it might look like this: <xsl:template match="text()"> <xsl:value-of select="."/> </xsl:template> You can override this rule by not sending the text in text nodes to the output document, like this: <xsl:template match="text()"> </xsl:template> In the previous example, you can see that a great deal of text made it from the input document to the output document because there was no explicit rule besides the default one for text nodes: The only output rule I used was for comments. If you turn off the default rule for text nodes by adding the previous two lines to ch13_10.xsl, the text of those text nodes does not go into the output document, and this is the result: <HTML> <COMMENT>At perihelion</COMMENT> <COMMENT>At perihelion</COMMENT> <COMMENT>At perihelion</COMMENT> </HTML> Matching Processing InstructionsYou can use the pattern processing-instruction() to match processing instructions. <xsl:template match="/processing-instruction()"> <I> Found a processing instruction. </I> </xsl:template> You can also specify what processing instruction you want to match by giving the name of the processing instruction (excluding the <? and ?> ), as in this case, where I'm matching the processing instruction <?xml-include?> : <xsl:template match="/processing-instruction(xml-include)"> <I> Found an xml-include processing instruction. </I> </xsl:template>
Using the Or OperatorYou can match to a number of possible patterns, which is very useful when your documents get a little more involved than the ones we've been using so far in this chapter. Here's an example. In this case, I want to display <NAME> and <MASS> elements in bold, which I'll do with the HTML <B> tag. To match either <NAME> or <MASS> elements, I'll use the Or operator, which is a vertical bar ( ) in a new rule, like this: Listing ch13_11.xsl<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="PLANETS"> <HTML> <xsl:apply-templates/> </HTML> </xsl:template> <xsl:template match="PLANET"> <P> <xsl:apply-templates/> </P> </xsl:template> <xsl:template match="NAME MASS"> <B> <xsl:apply-templates/> </B> </xsl:template> </xsl:stylesheet> Here are the results. Note that the name and mass values are both enclosed in <B> elements. (Also note that, because of the XSL default rules, the text from the other child elements of the <PLANET> element is also displayed.) <HTML> <P> <B>Mercury</B> <B>.0553</B> 58.65 1516 .983 43.4 </P> <P> <B>Venus</B> <B>.815</B> 116.75 3716 .943 66.8 </P> <P> <B>Earth</B> <B>1</B> 1 2107 1 128.4 </P> </HTML> You can use any valid pattern with the operator, such as the expressions PLANET PLANET//NAME . You also can use multiple operators, as in NAME MASS DAY , and so on. Testing with []You can use the [] operator to test whether a certain condition is true. For example, you can test the following:
Here are some examples. This expression matches <PLANET> elements that have child <NAME> elements: <xsl:template match = "PLANET[NAME]"> This expression matches any element that has a <NAME> child element: <xsl:template match = "*[NAME]"> This expression matches any <PLANET> element that has either a <NAME> or a <MASS> child element: <xsl:template match="PLANET[NAME MASS]"> Say that we gave the <PLANET> elements a new attribute COLOR , which holds the planet's color: Listing ch13_12.xml<?xml version="1.0"?> <?xml-stylesheet type="text/xml" href="ch13_02.xsl"?> <PLANETS> <PLANET COLOR="RED"> <NAME>Mercury</NAME> <MASS UNITS="(Earth = 1)">.0553</MASS> <DAY UNITS="days">58.65</DAY> <RADIUS UNITS="miles">1516</RADIUS> <DENSITY UNITS="(Earth = 1)">.983</DENSITY> <DISTANCE UNITS="million miles">43.4</DISTANCE><!--At perihelion--> </PLANET> <PLANET COLOR="WHITE"> <NAME>Venus</NAME> <MASS UNITS="(Earth = 1)">.815</MASS> <DAY UNITS="days">116.75</DAY> <RADIUS UNITS="miles">3716</RADIUS> <DENSITY UNITS="(Earth = 1)">.943</DENSITY> <DISTANCE UNITS="million miles">66.8</DISTANCE><!--At perihelion--> </PLANET> <PLANET COLOR="BLUE"> <NAME>Earth</NAME> <MASS UNITS="(Earth = 1)">1</MASS> <DAY UNITS="days">1</DAY> <RADIUS UNITS="miles">2107</RADIUS> <DENSITY UNITS="(Earth = 1)">1</DENSITY> <DISTANCE UNITS="million miles">128.4</DISTANCE><!--At perihelion--> </PLANET> </PLANETS> This expression matches <PLANET> elements that have COLOR attributes: <xsl:template match="PLANET[@COLOR]"> What if you wanted to match planets whose COLOR attribute was "BLUE" ? You can do that with the = operator, like this: Listing ch13_13.xsl<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="PLANETS"> <HTML> <xsl:apply-templates/> </HTML> </xsl:template> <xsl:template match="PLANET[@COLOR = 'BLUE']"> The <xsl:value-of select="NAME"/> is blue. </xsl:template> <xsl:template match="text()"> </xsl:template> </xsl:stylesheet> This style sheet filters out all planets whose color is blue and omits the others by turning off the default rule for text nodes. Here's the result: <HTML> The Earth is blue. </HTML> In fact, the expressions you can use in the [] operators are W3C XPath expressions. XPath expressions give you ways of specifying nodes in an XML document using a fairly involved syntax. And because the select attribute, which we're about to cover, uses XPath, I'll take a look at XPath as well. |