You want to package test code with the stylesheet but make reasonably certain that this test code does not interfere with the normal usage of the stylesheet.. Of course, you should test
Trang 1node: /employee[1]/employee[3]/employee[2]/employee[1]
stack: /1/2/2/2 (match="employee[employee]")
node:
/employee[1]/employee[3]/employee[2]/employee[1]/employee[1] stack: /1/2/2/2/3 (match="employee")
node:
/employee[1]/employee[3]/employee[2]/employee[2]/employee[2] stack: /1/2/2/2/3 (match="employee")
node: [XPath to this node]
stack: [call stack of the templates invoked]
param: name="[parameter name]" value="[parameter value]" more parameters
variable: name="[variable name]" value="[variable value]" more variables
Trang 2The call stack takes the form of a path (with / as separator) and includes all passed templates If a template has a name attribute, then this name is used Otherwise, the number (position) of the template appears within the stack If the current template does not have a name, the match attribute is displayed If a mode attribute is specified, its value is displayed
One known problem is that the output for parameters or variables is their string value (produced
with xsl:value-of) That's not reasonable for node sets and result-tree fragments However, using xsl:copy-of results in an error if the variable contains attribute or namespace nodes without parents
13.3.4 See Also
The trace.xslt source and further examples can be found at
http://www.informatik.hu-berlin.de/~obecker/XSLT/#trace
Trang 313.4 Including Embedded Unit Test Data in Utility
<xsl:variable name="count" select="count($nodes)"/>
<xsl:variable name="aNode" select="$nodes[ceiling($count div 2)]"/>
Trang 4
<! TEST CODE: DO NOT REMOVE! >
<xsl:template match="/xsl:stylesheet[@id='math:math.max'] | xsl:include[@href='math
Trang 5math:max -Infinity Test FAILED [<xsl:value-of select="$ans2"/>]
Trang 6</xsl:stylesheet>
13.4.3 Discussion
The xsl:stylesheet element has an optional attribute called id This attribute idenfities stylesheets that are embedded in larger documents However, here the ID is used for testing purposes You want to package test code with the stylesheet but make reasonably certain that this test code does not interfere with the normal usage of the stylesheet Do this by creating a template that will match only when the stylesheet processes itself:
<xsl:template match="/xsl:stylesheet[@id='math:math.max'] | xsl:include[@href='math.max.xslt']">
This explains the /xsl:stylesheet[@id='math:math.max'], but what about the xsl:include[@href='math.max.xslt'] part? To see the value of this, here is a stylesheet that packages all your math utilities into a single file for easy inclusion You would like
an easy way to test the entire package too:
Trang 7WARNING: <xsl:value-of select="@href"/> has no test code
to the aforementioned xsl:include[@href='filename'] part of the match
Notice the template <xsl:template match="xsl:include" 10"> This template causes emission of a warning if an included file does not contain test code This concept is important for quality control, since forgetting to create tests is easy
priority="-If you object to packaging the tests with the actual code, you can achieve the same effect by creating separate test files for each utility In this case, there is no need to use the id attribute of the stylesheet; simply match against the root:
Trang 913.5 Structuring Unit Tests
no output when the test succeeds
13.5.3 Discussion
Some of the best advice on automating testing is in Brian W Kernighan's and Rob Pike's The Practice of Programming (Addison Wesley, 1999) The authors state that test programs should produce output only when tests fail Why? Who wants to wade through pages of test output to
look for cases where the test fail? If you expect test code to produce no output, you will quickly
notice failures when there is output Of course, you should test your test code to make sure it
actually executes before relying on this testing technique
The method that stores the answer as an attribute in the test element works for simple tests that produce a primitive result However, some templates produce node sets In this case, you might need to store the correct answer as child elements in the tests You can then use the value set operations of Recipe 7.2 to compare results However, sometimes you can test node-set producing templates more simply Consider the test driver for the math:lowest template Recall that math:lowest returns a node set consisting of all instances of the lowest number in an input node set:
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns: math="http://www.exslt.org/math" exclude-result-
<xsl:when test="number($min) = $min">
<xsl:copy-of select="$nodes[ = $min]"/> </xsl:when>
Trang 10<xsl:if test="not($ans-ns/* != test:data[ =
current( )/@ans]) and
Trang 11The comparison relies on the behavior of != when both sides are node sets: the result is true if
a pair of nodes, one from each node set, have different string values You can make sure that the nodes returned by selecting the answer nodes from the test set are the same as the nodes returned
by math:lowest You can also make sure that the counts are the same
Some forms of computation (especially mathematical approximations) produce results that are correct even when the value produced is not exactly equal to the theoretically correct answer In this case, you can include an error tolerance in the test data and make sure the computed answer is identical to the correct answer within the stated tolerance
13.5.4 See Also
Brian W Kernighan's and Rob Pike's The Practice of Programming (Addison Wesley, 1999),
although not specifically written for XSLT, contains relevant advice for testing and debugging all kinds of programs
Trang 1213.6 Testing Boundary and Error Conditions
of typical cases you should consider
If a template acts on node sets, then be sure to test the following cases:
• An empty node set
• A node set with one element
• A node set with two elements
• A node set with an odd number of elements other than 1
• A node set with an even number of elements other than 2
If a template acts on a string, be sure to test the following cases:
• The empty string
• A string of length 1
• Other strings of varying sizes
If your template uses substring-before or substring-after for searches, be sure
to test the following cases:
• Strings that do not contain the test string
• Strings that start with the search string
• Strings that end with the search string
• Strings that contain only the search string
If a template acts on numbers, be sure to test:
• Other special boundary numbers that are unique to your problem
If a template compares tw o numbers X and Y, be sure to test cases in which:
• X < Y, especially X = Y - 1 and X = Y - d, where d is a small fraction
• X = Y
Trang 13• X > Y, especially X = Y + 1 and X = Y + d, where d is a small fraction
When you know or have access to the schema of a document that a stylesheet will process, be sure
to test inputs where:
• Optional elements are absent
• Optional elements are present
• Unbounded elements have only one instance
• Unbounded elements have several instances
• Zero when undefined (e.g., logarithms)
• Negative numbers when undefined (e.g., for factorial)
• Non-numeric input (e.g., "foo")
When templates or stylesheets use parameters, be sure to test what happens when:
• Parameters without default values are not set
• Parameters receive out-of-bound values
• Parameters receive values of the wrong type
You can check for parameters that aren't set by using the following trick:
<xsl:param name="param1" select="1 div 0" />
Trang 14When you know or have access to the document's schema a stylesheet is expected to process to see how the stylesheet responds to that input:[1]
[1]
This test assumes that the XSLT processor uses a nonvalidating parser or that you remove the schema reference from the input document
• Completely violates the schema (e.g., an unrelated XML document as input)
• Contains some elements that violate the schema
• Violates minOccurs and maxOccurs constraints
• Violates data type constraints
Trang 15Chapter 14 Generic and Functional Programming
The brilliant moves we occasionally make would not have been possible without the prior dumb ones
—Stanley Goldstein
Trang 1614.1 Introduction
This chapter renders all previous chapters moot Okay, maybe this is a slight exaggeration The fact is that the examples in previous chapters solve the particular problems they address They are also useful for didactic purposes If an example does not solve a problem you face, it might point the way to a solution The desired solution could be a small modification of the code or a
reapplication of the same techniques
This chapter sets its goals a little higher It presents examples that solve a very broad range of problems without requiring customization of the example's core code Those of you who are
familiar with C++, and specifically the Standard Template Library (STL), already know the power
you can obtain by creating generic code (generic algorithms) and reusing them in various contexts Others who use functional programming languages (e.g., Lisp, ML, or Haskell) also know of the great power obtained through the creation of higher-order functions: general-purpose functions that are specialized by accepting special purpose functions as arguments This chapter shows that XSLT, although not specifically designed as a generic or functional language, has inherent
capabilities to enable similar usage
The techniques used in this chapter stretch the abilities of XSLT quite a bit Not everyone will want to use the examples, some of which are complex and slow Nevertheless, I am reminded of the days before C++ had native support for templates You could fake generic programming by using macros, but the results were awkward However, enough people saw the potential, and templates soon became a first-class feature in C++, and possibly one of C++'s most important characteristics, despite the proliferation of other OO languages Pushing the language envelope in this way puts pressure on the language and possibly makes it evolve faster
This faster development is good because languages that cease to evolve often die out
Before diving into the examples, let's first discuss some of the general techniques used in this chapter This will allow the examples to concentrate on the application of the techniques rather than their mechanics
14.1.1 Extending the Content of Global Variables
This chapter extensively uses XSLT's ability to import (xsl:import) and override templates, variables, and other top-level elements in the importing spreadsheet
I like to use the object-oriented term override when discussing
xsl:import; however, a more technically correct explanation notes that some top-level elements in the importing stylesheet have higher importprecedence than matching elements in the imported stylesheet You can find a complete explanation of how each XSLT top-level element works with respect to xsl:import in Michael Kay's
XSLT Programmer's Reference (Wrox, 2001)
This chapter takes advantage of the ability to combine a global variable's contents defined in an imported stylesheet with one defined in an importing stylesheet
The following stylesheet defines two variables The first, $data1-public-data, is unique
to this stylesheet The second, $data, is defined in terms of the first, but can be overridden:
Trang 17<xsl:variable name="data" select="$data2-public-data"/>
</xsl:stylesheet>
The output of data1.xslt is:
<demo xmlns:d="data">
<d:data value="1"/>
Trang 1814.1.2 Using Template Tags
XSLT provides no direc t way to pass the name of a template to another template so that the second template can invoke the first indirectly In other words, the following code is illegal in XSLT 1.0 and 2.0:
Trang 19<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.ora.com/XSLTCookbook/namespaces/func">
<! Applay templates selecting a tag element that is
unique to the template we want
<!— A tagged template consists of a tag element and a
template that matches that
Trang 20In this particular case, these contortions are pure overkill because you could simply create a template that takes the output string as data The true power of this technique is only realized when the tagged functions compute something the caller can use
When using this technique, use a sanity-checking template that will match when no tagged template matches:
</xsl:template>
<xsl:template name="f:sayGoodbye"
match="xsl:template[@name='f:sayGoodbye']"> <xsl:text>Goodbye!
</xsl:text>
</xsl:template>
By using this technique, you can still call the template by name without any problems, and it still looks like a normal template This chapter does not use this technique because we sometimes like
to associate other data with the template tags and thus prefer them to be separate elements
Generic Programming Versus Functional
Programming
Generic programming is a method of organizing highly reusable components that can be
customized with little or no loss of runtime performance You can reuse generic
components (classes and functions) by instantiating them with particular types and/or
objects
Functional programming programs with higher-order functions: functions that can take
other functions as parameters and return functions as values
You can interpret the template tagging technique in two ways If you put on your
Generic programming hat, you can claim that the sayIt template is a generic template
that is parameterized with another template However, if you put on your functional
programming hat, you can claim that the sayIt template is a higher-order function
that takes another function as an argument Those familiar with C++'s style of generic
programming will probably argue that the second interpretation is more accurate
because the parameterization occurs at a runtime rather than at compile time However, I
do not believe that generic programming language must be a compile-time construct
Later you will see that the element tags can actually do more than serve as a proxy for
the name of a function; these tags can also carry data reminiscent of the traits
technique used in C++-style generic programming
Trang 21thus somewhat more challenging Dimitre has an XSLT library called FXSL - an XSLT functional programming library that can be downloaded from http://topxml.com/xsl/articles/dice
Trang 2214.2 Creating Polymorphic XSLT
14.2.1 Problem
You want to create XSLT that performs the same function on disparate data
14.2.2 Solution
There are two kinds of polymorphic behavior in XSLT The first form is reminiscent of
overloading, and the second is similar to overriding
Some modern languages, notably C++, let you create overloaded functions: functions that have the same name but take different types as arguments The compiler figures out which version of the function to call based on the type of data passed to it at the point of call XSLT does not have this exact capability; however, consider the following stylesheet:
Trang 23Overriding is the second form of polymorphism You can see numerous examples of overriding in this book's examples In XSLT, you use xsl:import to achieve this form of polymorphic behavior The following example is a rewrite of the DocBook stylesheet from Recipe 8.1 It was engineered for extensibility in the following ways:
• It uses variables to define primitive components of otherwise monolithic attribute content The variables can be redefined when importing stylesheets
• It uses attribute sets, which can be augmented with additional attributes or have existing attributes overridden in importing stylesheets
• It uses simple templates for each section of the document that can be overridden in importing stylesheets
• It provides a hook via a call to the named template extra-head-meta-datawhose default implementation does nothing
• <xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
• <xsl:output method="html"/>
• <!— Variables defining various style components —>
• <xsl:variable name="standard-font-family" select=" 'font-family:
• Times serif; font-weight' "/>
Trang 24• <xsl:variable name="sect1-font-size" select="
• <xsl:value -of select="$standard-font-family"/>;
• <xsl:value -of
select="$chapter-label-font-size"/>;
• <xsl:value -of select="$chapter -title-color"/>
• <xsl:text>; padding-bottom:10; font-weight:
• <xsl:value -of select="$standard-font-family"/>;
• <xsl:value -of
select="$chapter-title-font-size"/>;
• <xsl:value -of select="$chapter-title-color"/>
Trang 25• <xsl:text>; padding-bottom:150; font-weight: bold</xsl:text>
• <xsl:value -of select="$sect-common-style"/>;
• <xsl:value -of select="$sect1-font-size"/>
• <xsl:value -of select="$sect-common-style"/>;
• <xsl:value -of select="$sect2-font-size"/>
• <xsl:value -of select="$standard-font-family"/>;
• <xsl:value -of select="$normal-font-size"/>;
• <xsl:value -of select="$normal-text-color"/>
Trang 26• <div xsl:use -attribute-sets="chapter-label">
• <xsl:value -of select="@label"/>
• <div xsl:use -attribute-sets="chapter-title">
• <xsl:value -of select="."/>
• </div>
• </xsl:template>
• <xsl:template match="epigraph/para">
Trang 27• <div xsl:use -attribute-sets="epigraph-para">
• <xsl:value -of select="."/>
• <xsl:template match="copyright" mode="copyright">
• <div style="font-size : 10pt; font-family: Times serif; padding-top : 100">
Trang 28Calling XSLT an object-oriented language would stretch the truth The behavior of
xsl:import is only loosely similar to inheritance, and it operates at the level of the entire stylesheet Furthermore, it has no notion of encapsulation or data abstraction However, XSLT developers who already have an object-oriented mindset can often leverage that experience to the creation of more modular and reusable stylesheets
Consider the example of overloading in the "Solution" section Whenever you need to perform similar operations on disparate data, you will often want to structure your stylesheet in this manner
Of course, you could achieve the same result using conditional logic:
Trang 29Overriding is the key to creating modular and reusable XSLT Nevertheless, reuse does not come for free; it requires planning Specifically, you must think about three things:
1 What clients of your stylesheet might want to alter
2 What you may not want them to alter
3 What they are unlikely to alter
Failing to think about Item 1 results in an inflexible stylesheet that can be reused only by the cut, paste, and edit mechanism Failing to think about Item 2 could create pitfalls for the client of your stylesheet Failing to think about Item 3 results in overly complicated stylesheets with potentially poor performance and low maintainability
In the DocBook stylesheet, we made it convenient to override font and other text attributes individually We made it less convenient to override alignment attributes in a way that would create inconsistent or poor alignment choices between text elements where you desire consistency
or a particular alignment For example, it is more difficult to change the alignment of section and normal text elements by specifying their alignment separately Finally, we did not try to make it easy for particular HTML element names to be overridden because doing so would have
complicated the stylesheet in a way that adds little value
14.2.4 See Also
Chris Rathman has a more extensive example of polymorphic XSLT at
http://www.angelfire.com/tx4/cus/shapes/xsl.html
Trang 3014.3 Creating Generic Element Aggregation Functions
xmlns:aggr="http://www.ora.com/XSLTCookbook/namespaces/aggregate"
Trang 31<xsl:param name="param1" select="@param1"/>
<xsl:value-of select="$x + $param1"/>
<xsl:param name="aggr-func" select=" 'sum' "/>
<xsl:param name="func" select=" 'identity' "/>
<xsl:param name="func-param1"
select="$generic:generics[self::generic:func and
@name = $func]"> <xsl:with-param name="x" select="$nodes[1]"/> <xsl:with-param name="i" select="$i"/>
<xsl:withparam name="param1" select="$func
Trang 32<xsl:variable name="temp">
<xsl:apply-templates
func and
@name = $aggr-func]"> <xsl:with-param name="x" select="$f-of-x"/> <xsl:with-param name="accum" select="$accum"/> <xsl:with-param name="i" select="$i"/>
<xsl:with-param name="func" select="$func"/>
<xsl:with-param name="func-param1" param1"/>
<xsl:with-param name="i" select="$i + 1"/>
<xsl:with-param name="accum" select="$temp"/> </xsl:call-template>
The generic code has three basic parts
The first part consists of tagged generic functions on a single variable x These functions allow performance of aggregation operations on functions of an input set The simplest such function is identity, which is used when you want to aggregate the input set itself Square, cube, and incr functions are also predefined Users of the stylesheet can define other functions
The second part consists of tagged generic aggregator functions You will see two common implemented aggregators: sum and product Again, importing stylesheets can add other forms of aggregation
The third part consists of the generic aggregation algorithm It accepts as parameters a set of nodes
to aggregate, the name of an aggregator function (default is sum), and the name of a single element function (the default is identity) The $i parameter keeps track of the position of the currently processed node and is made available to both the element and aggregation functions, should they desire it The $accum keeps a working value of the aggregation Notice how the default value is initialized from the @identity attribute kept with the aggregate function's tag This initialization demonstrates a powerful feature of the generic approach with which metadata can be associated with the function tags This feature is reminiscent of the way C++-based generic programming uses traits classes
Trang 33The first step to understanding this code is to show a simple application that both uses and extends the aggregation facilities, as shown in Example 14-1
Example 14-1 Using and extending generic aggregation
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"
xmlns:aggr="http://www.ora.com/XSLTCookbook/namespaces/aggregate"
Trang 34<xsl:when test="$accum = @identi ty or $accum < $x"> <xsl:value -of select="$x"/>
<xsl:with-param name="nodes" select="number"/>
<xsl:with-param name="func" select=" 'square' "/> </xsl:call-template>
<xsl:with-param name="nodes" select="number"/>
<xsl:with-param name="aggr-func" select=" 'product'
<xsl:with-param name="nodes" select="number"/>
<xsl:with-param name="aggr-func" select=" 'max' "/> </xsl:call-template>
<xsl:with-param name="nodes" select="number"/>
<xsl:with-param name="aggr-func" select=" 'min' "/> </xsl:call-template>