In yesterday’s lesson, the focus was on sorting the elements in a source document before sending them to the output. Lesson 12 also concentrated on all kinds of numbering, from simple numbering to composite numbering in different formats.
In today’s lesson, you will learn how to create reusable stylesheets. This helps you to build stylesheets and include or import them into other stylesheets so that those stylesheets can use the functionality of the original stylesheets without having to use much code. As you will see, this enables you to build libraries of templates that perform common operations. You can then reuse these templates across many stylesheets.
Today you will learn the following:
• How to create multifile stylesheets by including or importing other stylesheets
• The difference between including and importing stylesheets
• How you can benefit from including and importing stylesheets
• The rules surrounding template precedence when you’re working with included or imported templates
Within a project or a company, you are likely to have many XML documents. Some of these documents may contain the same elements as other documents, and they may have to be handled in the same manner. Rewriting the code for these elements is probably not your idea of fun, which is why you can create stylesheets consisting of multiple files to reuse templates and other constructs.
Obviously, working with multiple files and being able to reuse them are huge benefits, specifically when you’re working in large stylesheets or with many stylesheets that need to be just slightly different for different purposes. These capabilities also allow you to work with files that have some general purpose, such as inserting a header, footer, menu, or toolbar in an HTML page. In addition, you can create a file that defines global variables to store a color scheme, font formatting, and so on for your company or a specific project. This file then can be reused across stylesheets so that each stylesheet uses the same formatting. When you need to change the formatting, changing the file that defines all the variables changes all stylesheets immediately, so all output is always consistent.
You can work with multifile stylesheets by including or importing other stylesheets. When you include a stylesheet, its contents are copied into the stylesheet including it. When you import a stylesheet, this is not the case, so you can override elements from the stylesheet you import. Depending on the elements in question, elements might be merged instead. This is of benefit, for instance, if your header color is normally blue, but in a specific case it needs to be red; the stylesheet doing the import can override the default color but still use all the other formatting definitions. This capability is very common in object-oriented programming languages such as SmallTalk, C++, and Java. If you’re familiar with these languages or concepts, you are already familiar with many of the benefits.
Being able to reuse already-written functionality is very powerful, but the rules governing including and importing other stylesheets are complex. These rules are complex to avoid problems from duplicated elements as much as possible. So, when you work with multiple files, you need to be very aware of all the rules involved if you want to get the output you want.
Even though these rules prevent most problems, not all includes and imports are allowed. They still cause errors. These errors might not occur at design time because errors can depend on elements in the source document. These errors are a big problem because XSLT was designed, for the most part, to never fail, as long as the syntax of the source XML and the stylesheet are correct. Until now, all failures you encountered were design-time problems, so you could deal with them when you created the stylesheet and rest assured that your stylesheet would always work (although the result might still be wrong, of course). Now you are faced with an element that might cause runtime failures, even if your stylesheet worked properly at design time.
An additional problem is that you need to be aware of the dependencies between stylesheets. If you change a stylesheet that is imported or included by another stylesheet, the changes may cause problems in the other stylesheet. This means that if you change a stylesheet, you need to check whether the stylesheets that depend on it still work, too.
When you include a stylesheet in another stylesheet, you are copying the contents of the xsl:stylesheet
element of the stylesheet being included. Those contents then replace the element that is used to include the other stylesheet. The two (or more) stylesheets then basically act as though they are one stylesheet, and all elements within it have to follow the same rules as though it is just one stylesheet. This makes the order in which you include different stylesheets significant, as well as the position in the stylesheet including other stylesheets.
Note
The stylesheet that includes other stylesheets is called the including stylesheet, whereas the stylesheet being included is called the included stylesheet.
You can include one stylesheet into another stylesheet by using the xsl:include
element. The href
attribute, the only attribute of xsl:include
, is mandatory and must contain a valid Uniform Resource Identifier (URI) to a document. It can be either a relative URI or an absolute URI. In most cases, you have full control over the project, so chances are you will be using only a relative URI that points either to a file within the same directory as the stylesheet including the other stylesheet or in a directory relative to that one. Only if your project involves a set of stylesheets that are distributed around several servers or if you’re using stylesheets provided by a third party do you use absolute URIs. Following are some samples of the different kinds of xsl:include
elements you might use:
<xsl:include href="myinclude.xsl" />
<xsl:include href="../includes/myinclude.xsl" />
<xsl:include href="http://www.somewebsite.com/xslincludes/myinclude.xsl" />
The first two samples are relative URIs. The first points to a file in the same directory, and the second points to a file in a sibling directory named includes
. The last sample is an absolute URI pointing to a file on another Web site. Be aware that if the file for the stylesheet being included can’t be found, the processor will raise an error.
Caution
You can include a stylesheet in a stylesheet that is included itself. The same rules apply in this situation. However, be aware that the URI is relative to the stylesheet doing the include. So, stylesheet A includes stylesheet B, which is in a different directory from stylesheet A. Then, if stylesheet B includes another stylesheet, the URI of the xsl:include
element in that stylesheet is relative to the directory of stylesheet B, not that of stylesheet A.
The best way to get the hang of using xsl:include
and the issues involved is to look at an example. That way, you can quickly see what’s going on and how changes affect the outcome. For these samples, the familiar menu XML is again the base of operations. For quick reference, it is shown in Listing 13.1.
Note
You can download the sample listings in this lesson from the publisher’s Web site.
The stylesheet in Listing 13.2 creates a nice-looking menu from the XML in Listing 13.1. This stylesheet does not use xsl:include
because you first need a stylesheet that can be included in another stylesheet.
ANALYSIS
Listing 13.2 creates an HTML table for each course. In that table, each dish
element is rendered as a table row, with the name and price separated in table cells. So that this is done right, the courses are handled by one template (line 8), and a separate template on line 14 handles all the dish
elements. Note that no template matches the root node. The stylesheet was created this way on purpose so that the template is easier to reuse and will not cause an error because two templates match the root node. Lines 5–6 make sure the output encoding and type are correct and that there is no redundant whitespace.
Caution
One of the most common mistakes with the xsl:include
element is having two templates with the exact same match expression, such as two templates matching the root node of the source document. This situation will cause an error, so you need to create your stylesheets in such a way that this cannot occur.
Listing 13.3 shows the result from applying Listing 13.2 to Listing 13.1. Note that, by default, Saxon creates indented HTML code.
OUTPUT
ANALYSIS
Listing 13.3 shows three HTML tables, one for each course. The dish name and its price are put in a separate table cell.
Listing 13.3 inserts only HTML tables; other elements such as the HTML base elements html
and body
are not inserted. You might conceivably have a different stylesheet that reuses the templates in Listing 13.2 and among other things adds the base HTML elements. The stylesheet in Listing 13.4 does exactly that.
ANALYSIS
Apart from line 5 containing the xsl:include
element, Listing 13.4 is rather straightforward. The template matching the root node inserts the base HTML code and a header. It then invokes other templates, in this case the ones that were included on line 5. Listing 13.5 shows you that the result is basically Listing 13.3 with the HTML base tags surrounding the tables and a header added before the first table starts.
ANALYSIS
Listing 13.5 has been truncated for easier reading. Basically, it shows you that the elements inserted in Listing 13.4 are added to the output of Listing 13.2 just as if Listing 13.4 contains the templates of Listing 13.2. This is, in fact, more or less the case.
As I said earlier, the top-level elements from the included stylesheet are copied over to the including stylesheet, at the position of the xsl:include
element. So, after the xsl:include
element in Listing 13.4 is handled by the processor, the resulting stylesheet, for all intents and purposes, looks like Listing 13.6.
Caution
The position of the xsl:include
element in the including stylesheet is of paramount importance. A change in location will change where the elements from the included stylesheet are placed. This may have an effect on the calculated precedence of templates and other location-related issues.
ANALYSIS
Listing 13.6 contains the top-level elements from Listing 13.2 included in Listing 13.4. Lines 5–19 are new and take the place of the original xsl:include
element.
Because of the way the including stylesheets work, you can apply the same rules to the elements as you would to an entire stylesheet. This means that duplicate attribute-sets are merged into one, just as if you had defined two attribute-sets in the same stylesheet, as explained on Day 5, “Inserting Text and Elements.” The same goes for xsl:output
, xsl:strip-space
, and xsl:preserve-space
elements, as explained on Day 7, “Controlling the Output.”
If you remember the lessons on Days 5 and 7, you also will remember that if a collision occurs between elements or attributes, the last one occurring in the resulting stylesheet wins. This point is very significant because where you include a stylesheet makes a lot of difference. Also, be aware that some processors may choose to raise an error and stop processing when certain collisions occur.
Suppose the including stylesheet defines UTF-8 as output encoding, but the included stylesheet defines UTF-16. If the xsl:include
element occurs before the xsl:output
element in the including stylesheet, the output is in UTF-8. However, if the xsl:include
element comes after the xsl:output
element, the xsl:output
element in the included stylesheet occurs last; hence, UTF-16 is used as output encoding instead. Therefore, it is a very good idea to include other stylesheets before any other top-level elements. This way, you make sure that the elements in the including stylesheet always win if a conflict ever occurs.
One situation that you need to be more careful with is having duplicate templates. Suppose you want to override the template dealing with the separate menu courses, so the HTML table would get a border. In that case, you could add the template in Listing 13.7 to Listing 13.4.
ANALYSIS
When you add Listing 13.7 to Listing 13.6 and apply it to Listing 13.1, the processor can report an error, use one of the templates and continue, or both. If it uses the second approach, it picks the template that is last in the code. This, of course, depends on whether the xsl:include
element is inserted before the template or after it. In the former case, the template defined in Listing 13.7 is used; in the latter case, the template in Listing 13.2 is used.
The problem here is that not all processors operate the same. MSXSL and Xalan, for instance, do not report an error, so how are you to know that something may be wrong? Saxon, on the other hand, reports an error and continues processing, so you would spot the error. It is therefore a good idea to test with several processors before proceeding or, better still, try to avoid these situations by clever design.
Note
Day 21, “Designing XML and XSLT Applications,” will deal with issues surrounding the design of stylesheets for reusability.
One of the more annoying problems with xsl:include
is that duplicate variables and parameters are not allowed. This restriction again conforms to the rules explained on Day 8, “Working with Variables,” and Day 9, “Working with Parameters,” so it doesn’t come as a real surprise. Because the same rules apply, the same scoping rules also apply, so you can still have duplicate variables and parameters in different templates because they do not share the same scope. But you cannot have duplicate global variables and parameters. If you use a central variable to store formatting information, you therefore cannot override it or merge it with another variable like you would do with attribute-sets. So, in these situations, you might want to consider using attribute-sets instead of variables.
Importing stylesheets into other stylesheets is another way to split them up into chunks and reuse them. You may be wondering why you would need another mechanism for this task if you can already include other stylesheets. The answer is that the rules for importing stylesheets are a lot different from those for including stylesheets. Although the basic goal is the same—reusing code—the methods differ, so you can pick the one that best suits your needs.
When you include stylesheets, you basically create a new stylesheet. For the resulting stylesheet, the same rules apply as with any other stylesheets. Therefore, you must make sure that while you include stylesheets, you do not include any stylesheets that will make the resulting stylesheet erroneous. When you import stylesheets, however, something different happens. Instead of just copying over the code for the other stylesheets, the code is copied and given an import precedence or priority.
The stylesheet that imports other stylesheets is called the importing stylesheet, whereas the stylesheet being imported is called the imported stylesheet.
For all the elements in the imported stylesheet, the precedence is lower than the elements in the importing stylesheet. This means that a template in the importing stylesheet cannot collide with a template from the imported stylesheet that matches the same node or nodes. Earlier, you learned that the same principle is not true when you include a stylesheet.
Although importing stylesheets has some advantages over including stylesheets, it is much harder to predict how the resulting stylesheet will operate. The point is that when you include stylesheets, you can just replace the xsl:include
element with the code from the included stylesheet. Importing a stylesheet is not that simple. Predicting what the resulting stylesheet will look like and which precedence rules are in place is therefore much harder. The obvious result is that seeing what will actually happen is much harder.
Tip
Try to use xsl:include
instead of xsl:import
wherever it is applicable. By doing so, you can save yourself a lot of headaches from trying to determine the import precedence.
Importing a stylesheet is as easy as including one, with the sole difference that you use the xsl:import
element. The following samples are similar to those shown for including stylesheets:
<xsl:import href="myimport.xsl" />
<xsl:import href="../imports/myimport.xsl" />
<xsl:import href="http://www.somewebsite.com/xslimports/myimport.xsl" />
As with including stylesheets, the relative paths are always calculated relative to the stylesheet doing the import, even if that stylesheet has been included or imported by another stylesheet that resides in another directory or on another server.
xsl:import
is a top-level element, with the additional restriction that it must be inserted before any other top-level element is used. This is different from including a stylesheet, which may occur at any given point. Importing the same stylesheet more than once, either directly or indirectly, is not an error. This point is actually more significant than you might think. A stylesheet may rely on some other stylesheet when it is the root stylesheet. A new stylesheet that needs both the original stylesheets may elect to import them both, even though one of them is already being imported by the other. In doing so, you can change the import precedence of a stylesheet. This situation is depicted in Figure 13.1.
Figure 13.1 shows two situations. In the first, stylesheet A imports stylesheet B, which in turn imports stylesheet C. In this case, stylesheet C has the lowest import precedence and then stylesheet B. In the second situation, stylesheet A imports stylesheet C again after it has imported stylesheet B. Because stylesheet C is imported after stylesheet B, stylesheet C’s import precedence is higher. So, when stylesheet A imports stylesheet C again, the import precedence changes.
Figure 13.2 depicts a more elaborate importing hierarchy, so you can see how import precedence really works.
In Figure 13.2, many imports are going on. So, how do you decide which imports have a higher precedence? The rule is that the stylesheet doing the importing has a higher precedence than the stylesheet being imported. Also, the stylesheet imported last has a higher precedence than the one imported first. The import precedence for the stylesheets in Figure 13.2 is therefore A, C, E, D, B, and again C, which comes down to A, C, E, D, and B because the extra import of C is never used.
Because of the import precedence, collisions of top-level elements are much less likely, although not impossible. So, in cases in which including a stylesheet fails, importing a stylesheet might still work. Each element is bound by its own set of import rules. The most significant are, of course, the rules surrounding templates.
For templates, the act of importing stylesheets has a lot in common with inheritance in object-oriented programming. Each stylesheet you import probably has several templates that you want to use. However, some templates may not do exactly what you want. You can inherit the templates that work for you and override the templates that don’t by creating a new template with the same matching rule or name.
Overriding templates is much different from including stylesheets, where having duplicate templates can cause big problems. Earlier, Listing 13.7 was added to a stylesheet that included Listing 13.2. With the different processors, including the stylesheet may or may not result in an error. Listing 13.8 shows the same concept, but imports Listing 13.2 rather than includes it.
ANALYSIS
Listing 13.8 imports Listing 13.2 on line 5. The template that creates the HTML table in Listing 13.2 is redefined in 13.8 on line 16 in Listing 13.8. Because this template has a higher import precedence, you can be sure that this template is used instead of the template in Listing 13.8. So, the resulting HTML table now has border=”1” specified because of line 17, which is a change over Listing 13.2.
Because of the import precedence, you can import stylesheets that weren’t specifically designed for code reuse. These stylesheets often contain a template matching the root node, but you can override it with your own templates. You can override all the templates that you don’t want to use and use only those templates for which you wanted to import the stylesheet in the first place.
The fact that you override a template of an imported stylesheet doesn’t necessarily mean that you have to re-create all the functionality of the original template. You can use the xsl:apply-imports element to invoke the template that matches the same node and has the next highest import precedence. This way, you can expand the original template by adding text and elements around the original result. This concept is shown in Listing 13.9.
ANALYSIS
Listing 13.9 is similar to Listing 13.8. It imports Listing 13.2 on line 5, and the template on line 7 inserts the base HTML elements and a header. The template on line 16 is much different, however. This template inserts a horizontal line with the HTML hr element at the start and end of each course. The xsl:apply-imports
element on line 18 then invokes the template matching the same node, which is the template on line 8 of Listing 13.2. That template inserts a table for each of the courses just like before. This table is now surrounded by horizontal lines. Listing 13.10 shows the result from applying Listing 13.9 to Listing 13.1.
ANALYSIS Listing 13.10 is truncated for better reading, but it shows you that the result of Listing 13.9 applied to Listing 13.1 is similar to the result shown in Listing 13.5. The difference is that each table is surrounded by two horizontal lines. Figure 13.3 shows what Listing 13.10 looks like when viewed in a browser, so you can see that the horizontal lines are inserted.
If you want to change what is going on inside an imported template, using xsl:apply-imports
doesn’t help much. In that case, you still have to rewrite the whole template. The xsl:apply-templates
element is only meant to add to the output of imported templates. The more basic the original templates are, the easier it is to alter the final output.
Tip
If you intend to use the xsl:apply-imports
element, you should break up functionality in imported stylesheets into templates performing basic functionality. The templates that invoke the imported templates can then add more specific functionality.
Templates are the most important elements in a stylesheet because they do the actual processing. However, several other top-level elements are available, and each has its own import rules. These elements are discussed next.
Attribute-sets
When you have more than one attribute-set with the same name, the attribute-sets are merged into one. Any attribute that occurs in both attribute-sets gets the value that is defined in the attribute-set with the highest import precedence. This means that you can easily override attribute-sets from imported stylesheets because the values in the importing stylesheet’s attribute-set always win.
When two attribute-sets have the same import precedence and colliding attribute values, the regular rules surrounding attribute-sets apply. This means that the processor can report an error or take the value of the last attribute-set defined. You can see how this principle works in practice by looking at Listing 13.11.
ANALYSIS
Listing 13.11 is much like Listing 13.2, except that on line 8 an attribute-set named table
is defined; this attribute-set is used on line 14 when creating an HTML table for each course.
You can now create another stylesheet that uses the attribute-set from Listing 13.11 and changes it somewhat. Listing 13.12 is such a stylesheet.
ANALYSIS
The stylesheet in Listing 13.12 is very small. Basically, it inherits the stylesheet in Listing 13.11, creating the exact same output, except that the attribute-set named table
is changed on line 7. The bgcolor
attribute on line 8 overrides the bgcolor
attribute on line 9 of Listing 13.11. The width
attribute is still used, and line 9 defines a new attribute named border
. For all intents and purposes, the attribute-set that will be used when the stylesheet is processed is shown in Listing 13.13.
ANALYSIS
In Listing 13.13, the new attribute-set now has three attributes instead of two. Because the bgcolor
attribute is overridden, the table will have a different background color than before. Your ability to merge stylesheets is useful when you have a stylesheet that already does most of what you want, but you want to change some minor details. As you can see, this process is easy, and the new stylesheet is remarkably short.
Import precedence is of no significance to the xsl:decimal-format
element. Having more than one xsl:decimal-format
element with the same name (unless their definitions are identical) causes an error. The processor always reports an error if this is the case, so you can handle this problem at design time.
The xsl:output
element behaves more or less like it would in a single stylesheet, with only one exception: If attributes that have different values are encountered, the one with the highest import precedence wins. If those elements have the same import precedence, the processor can report an error or take the last one specified. In essence, the rules for xsl:output
follow the rules for attribute-sets.
You handle whitespace by using the xsl:strip-space
and xsl:preserve-space
elements. Practically speaking, you will have conflicts if an element is matched by both of these elements. In that case, the one with the higher import precedence wins. If the import precedence rules do not give a clear winner, the matching rules explained on Day 7 are used. These rules are very much the same as template matching rules.
Because of the import precedence, you might get some surprising results: Rules that win when there is no import precedence may not win when there is. Listings 13.14 and 13.15 illustrate this point.
ANALYSIS
The stylesheet in Listing 13.14 copies all the elements from the source XML but strips the whitespace in the appetizers
, entrees
, and desserts
elements.
Listing 13.15 imports this stylesheet.
ANALYSIS
On line 5, the stylesheet in Listing 13.15 imports Listing 13.14. As you can see, this stylesheet is imported before any other elements, as is required. Line 7 contains an xsl:preserve-space
element telling the processor to preserve whitespace for all elements. Because this element is less specific than the xsl:strip-space
element in Listing 13.14, the xsl:strip-space
element would normally win over the xsl:preserve-space
element when it comes to the elements specified in the xsl:strip-space
element. However, because import precedence favors the xsl:preserve-space
element, the xsl:strip-space
element is completely ignored, and Listing 13.14 is copied over without any whitespace stripping.
In the unlikely case that a conflict occurs between elements with the same import precedence, the processor can again report an error or take the last elements specified.
Variables and parameters are fairly straightforward. If two global variables or parameters have the same name, the one with the highest import precedence is used. Having two variables or parameters with the same name and the same import precedence causes an error, so you have to deal with this issue at design time. Because import precedence is hierarchical, this problem can occur only if you defined the same global variable (or parameter) twice or included a stylesheet that defines the same global variable.
In today’s lesson, you learned that you can either include or import existing stylesheets. Including stylesheets is useful for breaking up functionality into reusable pieces. However, you need to make sure that no conflicts occur between elements in the included stylesheet and the including stylesheet. The basic rule you can use is that the whole stylesheet will act as one after all includes have been inserted.
Importing stylesheets is different from including stylesheets in that elements in imported stylesheets get an import precedence that defines if and when these elements are used. This way, you can take an existing, complete stylesheet and override key elements, such as templates and attribute-sets, to change the output.
Including and importing therefore have two different purposes. Including is more useful for making reusable template libraries, whereas importing is more useful when you already have stylesheets that do what you want for the most part.
Tomorrow’s lesson is about the other side of the coin: using multiple source XML documents so that their data can be processed as one document. Like importing and including stylesheets can break up a stylesheet in smaller pieces that are easier to handle, being able to use multiple XML sources does the same for large XML documents.
Q If I can import a stylesheet, I don’t see why I would ever want to include one. When would I include a stylesheet rather than import it?
A When importing a stylesheet, you always have to deal with import precedence. This means that when you have a hierarchy of imports, you might inadvertently override a template you don’t want to override. You can avoid this problem by including instead of importing. Including stylesheets is less complex and therefore preferred if you don’t get any colliding elements.
Q How do I determine the import precedence for an element?
A Keep in mind that imports are always done first in a stylesheet and that the later an element is imported, the higher its import precedence is. The easiest way to determine import precedence is to map out the imports and includes and then determine where conflicting elements occur. Based on the map, you can determine which will win.
Q Can I see the resulting stylesheet before it is used to process a document?
A The existing processors don’t allow you to see the resulting stylesheet. After tomorrow’s lesson, you will be able to create a stylesheet that does this for including stylesheets. For importing stylesheets, seeing the resulting stylesheet is possible but much harder.
This workshop tests whether you understand all the concepts you learned today. It is helpful to know and understand the answers before starting tomorrow’s lesson. You can find the answers to the quiz questions and exercises in Appendix A.
1. True or False: A stylesheet that is included can contain a global variable with the same name as in another included stylesheet or the including stylesheet.
2. True or False: The template occurring last after all imports and includes have been done has the highest import precedence.
3. What happens when an imported stylesheet and the importing stylesheet both have an xsl:output
element?
4. Can you include a template and then import one?
5. Does importing or including have any bearing on local variables?
1. Create a new stylesheet that either includes or imports Listing 13.6. Make sure that it creates output from Listing 13.1 so that each course HTML table is preceded by a header containing the course title.
18.223.170.223