Tải bản đầy đủ (.pdf) (76 trang)

xslt cookbook phần 4 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (147.63 KB, 76 trang )

<xsl:import href="tree-control.xslt"/>

<xsl:param name="collapse"/>
<xsl:variable name="collapse-test" select="concat('
',$collapse,' ')"/>

<xsl:template match="*" mode="name">
<xsl:if test="not(ancestor::*[contains($collapse-test,
concat(' ',local-
name(.),' '))])">
<xsl:apply-imports/>
</xsl:if>
</xsl:template>

<xsl:template match="*" mode="value">
<xsl:if test="not(ancestor::*[contains($collapse-test,
concat(' ',local-
name(.),' '))])">
<xsl:apply-imports/>
</xsl:if>
</xsl:template>

<xsl:template match="*" mode="line-break">
<xsl:if test="not(ancestor::*[contains($collapse-test,
concat(' ',local-
name(.),' '))])">
<xsl:apply-imports/>
</xsl:if>
</xsl:template>

<xsl:template match="*" mode="indent">


<xsl:choose>
<xsl:when test="self::*[contains($collapse-test,
concat(' ',local-
name(.),' '))]">
<xsl:for-each select="ancestor::*">
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text> x-</xsl:text>
</xsl:when>
<xsl:when test="ancestor::*[contains($collapse-test,
concat(' ',local-name(.),'
'))]"/>
<xsl:otherwise>
<xsl:apply-imports/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>
Example 5-34. Output with $collapse="Employee PayPeriod"
o-[ExpenseReport]
x-[Employee]
x-[PayPeriod]
o-[Expenses]
o-[Expense]
| +-[Date] 12/20/01
| +-[Account] 12345
| +-[Desc] Goofing off instead of going to confrence.
| +-[Lodging] 500.00
| +-[Transport] 50.00

| +-[Fuel] 0
| +-[Meals] 300.00
| +-[Phone] 100
| +-[Entertainment] 1000.00
| `-[Other] 300.00
o-[Expense]
+-[Date] 12/20/01
+-[Account] 12345
+-[Desc] On the beach
+-[Lodging] 500.00
+-[Transport] 50.00
+-[Fuel] 0
+-[Meals] 200.00
+-[Phone] 20
+-[Entertainment] 300.00
`-[Other] 100.00
There is literally no end to the variety of custom tree formats you can create from overrides to the
basic stylesheet. In object-oriented circles, this technique is called the template-method pattern. It
involves building the skeleton of an algorithm and allowing subclasses to redefine certain steps
without changing the algorithm's structure. In the case of XSLT, importing stylesheets take the
place of subclasses. The power of this example does not stem from the fact that creating tree-like
rendering is difficult; it is not. Instead, the power lies in the ability to reuse the example's structure
while considering only the aspects you want to change.
5.5 Numbering Textual Output
5.5.1 Problem
You want to create sequentially numbered output.
5.5.2 Solution
Since output can be numbered in many ways, this example presents a series of increasingly
complex examples that address the most common (and a few uncommon) numbering needs.
5.5.2.1 Number siblings sequentially

This category is the simplest form of numbering. For example, you can produce a numbered list of
people using the stylesheet in Example 5-35 and Example 5-36.
Example 5-35. Stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>

<xsl:template match="person">
<xsl:number count="*" format="1. "/>
<xsl:value-of select="@name"/>
</xsl:template>

</xsl:stylesheet>
Example 5-36. Output
1. Al Zehtooney
2. Brad York
3. Charles Xavier
4. David Willimas
5. Edward Ulster
6. Frank Townsend
7. Greg Sutter
8. Harry Rogers
9. John Quincy
10. Kent Peterson

You can use the justify template discussed in Recipe 5.3 if you want right-justified numbers.
5.5.2.2 Start from a number other than one
xsl:number does not provide a standard facility for starting from or incrementing by a
number other than one, but you can handle this task with a little math. Example 5-37 and Example
5-38 start from ten and increment by five, just to be different.

Example 5-37. Stylesheet using nonsequential numbering
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="person">
<xsl:variable name="num">
<xsl:number count="*"/>
</xsl:variable>
<xsl:number value="($num - 1) * 5 + 10" format="1. "/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
Example 5-38. Output
10. Al Zehtooney
15. Brad York
20. Charles Xavier
25. David Willimas
30. Edward Ulster
35. Frank Townsend
40. Greg Sutter
45. Harry Rogers
50. John Quincy
55. Kent Peterson

This scenario works even if you want the final output to use a non-numerical format. For example,
Example 5-39 and Example 5-40 use the same technique to start numbering at L.

Example 5-39. Stylesheet for numbering from L
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="person">
<xsl:variable name="num">
<xsl:number count="*"/>
</xsl:variable>
<xsl:number value="$num + 11" format="A. "/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
Example 5-40. People numbered successively from letter L
L. Al Zehtooney
M. Brad York
N. Charles Xavier
O. David Willimas
P. Edward Ulster
Q. Frank Townsend
R. Greg Sutter
S. Harry Rogers
T. John Quincy
U. Kent Peterson

5.5.2.3 Number elements globally
Sometimes you want to number elements sequentially without regard to their context. The most

common example involves a document that contains footnote elements. The footnotes can appear
at any level in the document's structure, yet they should be numbered sequentially. However, to
continue the theme of your example, here is a document that divides people into various groups
and subgroups:
<people>
<group>
<person name="Al Zehtooney" age="33" sex="m"
smoker="no"/>
<person name="Brad York" age="38" sex="m" smoker="yes"/>
<person name="Charles Xavier" age="32" sex="m"
smoker="no"/>
<person name="David Willimas" age="33" sex="m"
smoker="no"/>
<person name="Edward Ulster" age="33" sex="m"
smoker="yes"/>
<person name="Frank Townsend" age="35" sex="m"
smoker="no"/>
</group>
<group>
<person name="Greg Sutter" age="40" sex="m"
smoker="no"/>
<person name="Harry Rogers" age="37" sex="m"
smoker="no"/>
<group>
<person name="John Quincy" age="43" sex="m"
smoker="yes"/>
<person name="Kent Peterson" age="31" sex="m"
smoker="no"/>
<person name="Larry Newell" age="23" sex="m"
smoker="no"/>

<group>
<person name="Max Milton" age="22" sex="m"
smoker="no"/>
<person name="Norman Lamagna" age="30" sex="m"
smoker="no"/>
<person name="Ollie Kensinton" age="44" sex="m"
smoker="no"/>
</group>
<person name="John Frank" age="24" sex="m"
smoker="no"/>
</group>
<group>
<person name="Mary Williams" age="33" sex="f"
smoker="no"/>
<person name="Jane Frank" age="38" sex="f"
smoker="yes"/>
<person name="Jo Peterson" age="32" sex="f"
smoker="no"/>
<person name="Angie Frost" age="33" sex="f"
smoker="no"/>
<person name="Betty Bates" age="33" sex="f"
smoker="no"/>
<person name="Connie Date" age="35" sex="f"
smoker="no"/>
<person name="Donna Finster" age="20" sex="f"
smoker="no"/>
</group>
<group>
<person name="Esther Gates" age="37" sex="f"
smoker="no"/>

<person name="Fanny Hill" age="33" sex="f"
smoker="yes"/>
<person name="Geta Iota" age="27" sex="f"
smoker="no"/>
<person name="Hillary Johnson" age="22" sex="f"
smoker="no"/>
<person name="Ingrid Kent" age="21" sex="f"
smoker="no"/>
<person name="Jill Larson" age="20" sex="f"
smoker="no"/>
<person name="Kim Mulrooney" age="41" sex="f"
smoker="no"/>
<person name="Lisa Nevins" age="21" sex="f"
smoker="no"/>
</group>
</group>
</people>
The only necessary change is to use the xsl:number attribute level="any". This attribute
instructs the XSLT processor to consider all preceding occurrences of the person element when
determining numbering. See Example 5-41 and Example 5-42.
Example 5-41. Stylesheet for level="any"
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="person">
<xsl:number count="person" level="any" format="1. "/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>

</xsl:template>

</xsl:stylesheet>
Example 5-42. Output with level="any"
1. Al Zehtooney
2. Brad York
3. Charles Xavier
4. David Willimas
5. Edward Ulster
6. Frank Townsend
7. Greg Sutter
8. Harry Rogers
9. John Quincy
10. Kent Peterson
11. Larry Newell
12. Max Milton
13. Norman Lamagna
14. Ollie Kensinton
15. John Frank
16. Mary Williams
17. Jane Frank
18. Jo Peterson
19. Angie Frost
20. Betty Bates
21. Connie Date
22. Donna Finster
23. Esther Gates
24. Fanny Hill
25. Geta Iota
26. Hillary Johnson

27. Ingrid Kent
28. Jill Larson
29. Kim Mulrooney
30. Lisa Nevins
5.5.2.4 Number elements globally within a subcontext
Sometimes you want to restrict global numbering to a specific context. For example, suppose you
want to number people within their top-level group and ignore subgroups:
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="people/group">
<xsl:text>Group </xsl:text>
<xsl:number count="group"/>
<xsl:text>&#xa;</xsl:text>
<xsl:apply-templates/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

<xsl:template match="person">
<xsl:number count="person" level="any"
from="people/group" format="1. "/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>

Group 1

1. Al Zehtooney
2. Brad York
3. Charles Xavier
4. David Willimas
5. Edward Ulster
6. Frank Townsend

Group 2
1. Greg Sutter
2. Harry Rogers
3. John Quincy
4. Kent Peterson
5. Larry Newell
6. Max Milton
7. Norman Lamagna
8. Ollie Kensinton
9. John Frank
10. Mary Williams
11. Jane Frank
12. Jo Peterson
13. Angie Frost
14. Betty Bates
15. Connie Date
16. Donna Finster
17. Esther Gates
18. Fanny Hill
19. Geta Iota
20. Hillary Johnson
21. Ingrid Kent
22. Jill Larson

23. Kim Mulrooney
24. Lisa Nevins
5.5.2.5 Number hierarchically
In formal and legal documents, items are often numbered based on both their sequence and level
within a hierarchy. As shown in Example 5-43 to Example 5-45, xsl:number supports this via
attribute level="multiple".
Example 5-43. Hierarchical numbering based on group and person
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="people/group">
<xsl:text>Group </xsl:text>
<xsl:number count="group"/>
<xsl:text>&#xa;</xsl:text>
<xsl:apply-templates/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

<xsl:template match="person">
<xsl:number count="group | person" level="multiple"
format="1.1.1 "/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
The numbering achieved by the stylesheet in Example 5-45 is somewhat odd, but it effectively
illustrates the effect of attribute count when it is used with level = "multiple". The

count attribute is simply a specification for determining what ancestor elements should be
included when composing a hierarchical number. The stylesheet assigned numbers to people
based on group or person elements. Bard York is assigned 1.2 because he is in Group 1 and is the
second person in the group. Likewise, Max Milton is assigned 2.3.4.1 because he is in Group 2
when considering only top-level groups; he is in Group 3 when considering both top- and second-
level groups; he is in Group 4 when considering all top-, second-, and third-level groups; and he is
the first person within his own group:
Group 1
1.1 Al Zehtooney
1.2 Brad York
1.3 Charles Xavier
1.4 David Willimas
1.5 Edward Ulster
1.6 Frank Townsend

Group 2
2.1 Greg Sutter
2.2 Harry Rogers
2.3.1 John Quincy
2.3.2 Kent Peterson
2.3.3 Larry Newell
2.3.4.1 Max Milton
2.3.4.2 Norman Lamagna
2.3.4.3 Ollie Kensinton
2.3.5 John Frank
2.4.1 Mary Williams
2.4.2 Jane Frank
2.4.3 Jo Peterson
2.4.4 Angie Frost
2.4.5 Betty Bates

2.4.6 Connie Date
2.4.7 Donna Finster
2.5.1 Esther Gates
2.5.2 Fanny Hill
2.5.3 Geta Iota
2.5.4 Hillary Johnson
2.5.5 Ingrid Kent
2.5.6 Jill Larson
2.5.7 Kim Mulrooney
2.5.8 Lisa Nevins
In typical applications, you expect a numbering scheme in which the number at any level is
relative to the number at the next higher level. You can achieve this relationship by using multiple
and adjacent xsl:number elements, as shown in Example 5-44 and 5-45.
Example 5-44. Stylesheet for creating muliple ordered levels
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="group">
<xsl:text>Group </xsl:text>
<xsl:number count="group" level="multiple"/>
<xsl:text>&#xa;</xsl:text>
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="person">
<xsl:number count="group" level="multiple"
format="1.1.1."/>
<xsl:number count="person" level="single" format="1 "/>

<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
Example 5-45. Output
Group 1
1.1 Al Zehtooney
1.2 Brad York
1.3 Charles Xavier
1.4 David Willimas
1.5 Edward Ulster
1.6 Frank Townsend
Group 2
2.1 Greg Sutter
2.2 Harry Rogers
Group 2.1
2.1.1 John Quincy
2.1.2 Kent Peterson
2.1.3 Larry Newell
Group 2.1.1
2.1.1.1 Max Milton
2.1.1.2 Norman Lamagna
2.1.1.3 Ollie Kensinton
2.1.4 John Frank
Group 2.2
2.2.1 Mary Williams
2.2.2 Jane Frank
2.2.3 Jo Peterson
2.2.4 Angie Frost

2.2.5 Betty Bates
2.2.6 Connie Date
2.2.7 Donna Finster
Group 2.3
2.3.1 Esther Gates
2.3.2 Fanny Hill
2.3.3 Geta Iota
2.3.4 Hillary Johnson
2.3.5 Ingrid Kent
2.3.6 Jill Larson
2.3.7 Kim Mulrooney
2.3.8 Lisa Nevins
5.5.3 Discussion
Almost any numbering scheme is realizable by using one or more xsl:number elements with
the appropriate attribute settings. However, extensive use of xsl:number (especially with
level="multiple") can slow down your stylesheets. With very deeply nested hierarchical
numbers, you can achieve a performance boost by passing the parent-level numbering down to the
children via a parameter. Notice how you can achieve a hierarchal numbering in this fashion
without using xsl:number at all:
<xsl:stylesheet version="1.0"
xmlns:xsl="
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="group">
<xsl:param name="parent-level" select=" '' "/>

<xsl:variable name="number" select="concat($parent-
level,position( ))"/>


<xsl:text>Group </xsl:text>
<xsl:value-of select="$number"/>
<xsl:text>&#xa;</xsl:text>

<xsl:apply-templates>
<xsl:with-param name="parent-level"
select="concat($number,'.')"/>
</xsl:apply-templates>

</xsl:template>

<xsl:template match="person">
<xsl:param name="parent-level" select=" '' "/>

<xsl:variable name="number">
<xsl:value-of select="concat($parent-
level,position( ),' ')"/>
</xsl:variable>

<xsl:value-of select="$number"/>
<xsl:value-of select="@name"/>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
This use of position is less convenient when the numbering scheme requires letters for roman
numerals.
5.6 Wrapping Text to a Specified Width and Alignment
5.6.1 Problem
You want to format multilined text within an XML document into a fixed-width-aligned format,

insuring that lines wrap at word boundaries.
5.6.2 Solution
Here is a solution that handles both wrapping and alignment by reusing the text:justify
template constructed in Recipe 5.3. For added flexibility, you can allow the alignment width to be
specified separately from wrapping width, but default to it when unspecified:
<xsl:stylesheet version="1.0"
xmlns:xsl="
id="text.wrap"

xmlns:str=" />s"

xmlns:text="
exclude-result-prefixes="text">

<xsl:include href=" /strings/str.find-last.xslt"/>
<xsl:include href="text.justify.xslt"/>

<xsl:template match="node( ) | @*" mode="text:wrap"
name="text:wrap">
<xsl:param name="input" select="normalize-space( )"/>
<xsl:param name="width" select="70"/>
<xsl:param name="align-width" select="$width"/>
<xsl:param name="align" select=" 'left' "/>

<xsl:if test="$input">
<xsl:variable name="line">
<xsl:choose>
<xsl:when test="string-length($input) > $width">
<xsl:call-template name="str:substring-before-
last">

<xsl:with-param name="input"
select="substring($input,1,$width)"/>
<xsl:with-param name="substr" select=" ' ' "/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:if test="$line">
<xsl:call-template name="text:justify">
<xsl:with-param name="value" select="$line"/>
<xsl:with-param name="width" select="$align-width"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
<xsl:text>&#xa;</xsl:text>
</xsl:if>

<xsl:call-template name="text:wrap">
<xsl:with-param name="input"
select="substring($input, string-length($line) +
2)"/>
<xsl:with-param name="width" select="$width"/>
<xsl:with-param name="align-width" select="$align-
width"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:if>


</xsl:template>
The solution reuses the str:substring-before-last template created in Recipe 1.4.
The basic idea is to extract a line containing up to $width characters, extracting less if the line
would not end in a space. The rest of the input is then processed recursively. The tricky part is to
make sure that if a word with $width characters is encountered, you allow it to be split.
The following example shows how you can use this recipe to wrap and center some sample. It
uses different alignment and wrapping widths to demonstrate the effect of these parameters:
<xsl:stylesheet version="1.0"
xmlns:xsl="

xmlns:text=" />>

<xsl:include href="text.wrap.xslt"/>

<xsl:strip-space elements="*"/>
<xsl:output method="text"/>

<xsl:template match="p">
<xsl:apply-templates select="." mode="text:wrap">
<xsl:with-param name="width" select="40"/>
<xsl:with-param name="align" select=" 'center' "/>
<xsl:with-param name="align-width" select="60"/>
</xsl:apply-templates>
<xsl:text>&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
Input:
<doc>

<p>In the age of the internet, formats such HTML, XHTML
and PDF clearly dominate
the application of XSL and XSLT. However, plain old text
will never become obsolete
because it is the lowest common denominator in both human
and machine-readable
formats. XML is often converted to text to be imported into
another application that
does not know how to read XML or does not interpret it the
way you would prefer. Text
output is also used when the result will be sent to a
terminal or post processed in a
Unix pipeline.</p>
<p>Many recipes in this section place stress on XSLT
techniques that create very
generic XML to text converters. Here generic means that the
transformation can easily
be customized to work on many different XML inputs or
produce a variety of outputs or
both. The techniques employed in these recipes have
application beyond specifics of a
given recipe and often beyond the domain of text processing.
In particular, you may
want to look at recipes 5.2 through 5.5 even if they do not
address a present need.
</p>
</doc>
Output:
In the age of the internet, formats
such HTML, XHTML and PDF clearly

dominate the application of XSL and
XSLT. However, plain old text will
never become obsolete because it is the
lowest common denominator in both human
and machine-readable formats. XML is
often converted to text to be imported
into another application that does not
know how to read XML or does not
interpret it the way you would prefer.
Text output is also used when the
result will be sent to a terminal or
post processed in a Unix pipeline.

Many recipes in this section place
stress on XSLT techniques that create
very generic XML to text converters.
Here generic means that the
transformation can easily be customized
to work on many different XML inputs or
produce a variety of outputs or both.
The techniques employed in these
recipes have application beyond
specifics of a given recipe and often
beyond the domain of text processing.
In particular, you may want to look at
recipes 5.2 through 5.5 even if they do
not address a present need.
5.6.3 Discussion
In many text-conversion scenarios, the final output device cannot handle text of arbitrary line
length. Most devices (such as terminals) wrap the text that overflows its horizontal display area.

This wrapping results in a sloppy-looking output. This example allows you to deal with fixed-
width formatting more intelligently.
5.6.4 See Also
A similar text-wrapping template can be found in Jeni Tennison's XSLT and XPath on the Edge
(M&T, 2001). However, this solution adds alignment capabilities and handles the case in which
words are longer than the desired width.
Chapter 6. XML to XML
To change and to change for the better are two different things.
—German proverb
One of the beauties of XML is that if you don't like something, you can change it. Since it is
impossible to please everyone, transforming XML to XML is extremely common. However, you
will not transform XML only to improve the structure of a poorly designed schema. Sometimes
you need to merge disparate XML documents into a single document. At other times you want to
break up a large document into smaller subdocuments. You might also wish to preprocess a
document to filter out only the relevant information, without changing its structure, before sending
it off for further processing.
A simple but important tool in many XML-to-XML transformations is the identity transform. This
tool is a stylesheet that copies an input document to an output document without changing it. This
task may seem better suited to the operating systems copy operation, but as the following
examples demonstrate, this simple stylesheet can be imported into other stylesheets to yield very
common types of transformations with little added coding effort.
Example 6-1 shows the identity stylesheet. I actually prefer calling this stylesheet the copying
stylesheet, and I call the techniques that utilize it the overriding copy idiom.
Example 6-1. copy.xslt
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:template match="node( ) | @*">
<xsl:copy>
<xsl:apply-templates select="@* | node( )"/>

</xsl:copy>
</xsl:template>

</xsl:stylesheet>
6.1 Converting Attributes to Elements
6.1.1 Problem
You have a document that encodes information with attributes, and you would like to use child
elements instead.
6.1.2 Solution
This problem is tailor-made for what the introduction to this chapter calls the overriding copy
idiom. This example transforms attributes to elements globally:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes"/>

<xsl:template match="@*">
<xsl:element name="{local-name(.)}" namespace="{namespace-
uri( )}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>

</xsl:stylesheet>
The stylesheet works by overriding the copy behavior for attributes. It replaces the behavior with a
template that converts an attribute into an element (of the same name) whose value is the
attribute's value. It also assumes that this new element should be in the same namespace as the

attribute's parent. If you prefer not to make assumptions, then use the following code:
<xsl:template match="@*">
<xsl:variable name="namespace">
<xsl:choose>
<! Use namespsace of attribute, if there is one >
<xsl:when test="namespace-uri( )">
<xsl:value-of select="namespace-uri( )" />
</xsl:when>
<! Otherwise use parents namespace >
<xsl:otherwise>
<xsl:value-of select="namespace-uri( )" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{name( )}" namespace="{$namespace}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
You'll often want to be selective when transforming attributes to elements (see Example 6-2 to
Example 6-4).
Example 6-2. Input
<people which="MeAndMyFriends">
<person firstname="Sal" lastname="Mangano" age="38"
height="5.75"/>
<person firstname="Mike" lastname="Palmieri" age="28"
height="5.10"/>
<person firstname="Vito" lastname="Palmieri" age="38"
height="6.0"/>
<person firstname="Vinny" lastname="Mari" age="37"
height="5.8"/>

</people>
Example 6-3. A stylesheet that transforms person attributes only
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes"/>

<xsl:template match="person/@*">
<xsl:element name="{local-name(.)}" namespace="{namespace-
uri( )}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>

</xsl:stylesheet>
Example 6-4. Output
<people which="MeAndMyFriends">

<person>
<firstname>Sal</firstname>
<lastname>Mangano</lastname>
<age>38</age>
<height>5.75</height>
</person>

<person>
<firstname>Mike</firstname>

<lastname>Palmieri</lastname>
<age>28</age>
<height>5.10</height>
</person>

<person>
<firstname>Vito</firstname>
<lastname>Palmieri</lastname>
<age>38</age>
<height>6.0</height>
</person>

<person>
<firstname>Vinny</firstname>
<lastname>Mari</lastname>
<age>37</age>
<height>5.8</height>
</person>

</people>
6.1.3 Discussion
This section and Recipe 6.2 address the problems that arise when a document designer makes a
poor choice between encoding information in attributes versus elements. The attribute-versus-
element decision is one of the most controversial aspects of document design.
[1]
These examples
are helpful because they allow you to correct your own or others' (perceived) mistakes.
[1]
The only other stylistic issue I have seen software developers get more passionate about is where to
put the curly braces in C-like programming languages (e.g., C++ and Java).

6.2 Converting Elements to Attributes
6.2.1 Problem
You have a document that encodes information using child elements, and you would like to use
attributes instead.
6.2.2 Solution
As with Recipe 6.1, you can use the overriding copy idiom. However, when transforming
elements to attributes, you must selectively determine where the transformation will be applied.
This is because the idea of transforming all elements to attributes is nonsensical. The following
stylesheet reverses the attribute-to-element transformation we performed in Recipe 6.1:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"/>

<xsl:template match="person">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
6.2.3 Discussion
Converting from elements to attributes is not always as straightforward as transforming in the
opposite direction. If the elements being converted to attributes have attributes themselves, you

must decide what will become of them. In the preceding solution, they would be lost. Another
alternative would be to promote them to the new parent:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"/>

<xsl:template match="person">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:copy-of select="@*"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
However, this works only if all the attributes names in question are unique. If this is not the case,
you will have to rename attributes, perhaps as follows:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"/>


<xsl:template match="person">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:variable name="elem-name" select="local-
name(.)"/>
<xsl:for-each select="@*">
<xsl:attribute name="{concat($elem-name,'-',local-
name(.))}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
Another complication arises if the siblings elements do not have unique names, because in this
case, they would clash upon becoming attributes. Another possible strategy is to create an attribute
from an element only if the element does not have attributes or element children of its own, does
not repeat in its parent element, and has parents without attributes:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" indent="yes" version="1.0"
encoding="UTF-8"/>


<! Match elements that are parents >
<xsl:template match="*[*]">
<xsl:choose>
<! Only convert children if this element has no
attributes >
<! of its own >
<xsl:when test="not(@*)">
<xsl:copy>
<! Convert children to attributes if the child has
>
<! no children or attributes and has a unique name
>
<! amoung its siblings >
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="not(*) and not(@*) and
not(preceding-sibling::*[name( )
=

name(current( ))])
and
not(following-sibling::*[name( )
=

name(current( ))])">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:when>

<xsl:otherwise>
<xsl:apply-templates select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>
6.3 Renaming Elements or Attributes
6.3.1 Problem
You need to rename or re-namespace elements or attributes in an XML document.
6.3.2 Solution
If you need to rename a small number of attributes or elements, use a straightforward version of
the overriding copy idiom, as shown in Example 6-5.
Example 6-5. Rename person to individual
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"/>


<xsl:template match="person">
<individual>
<xsl:apply-templates/>
</individual>
</xsl:template>

</xsl:stylesheet>
Or, alternatively, use xsl:element:

<xsl:template match="person">
<xsl:element name="individual">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>

Renaming attributes is just as straightforward:
<xsl:stylesheet version="1.0"
xmlns:xsl="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"/>

<xsl:template match="@lastname">
<xsl:attribute name="surname">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>

</xsl:stylesheet>

Sometimes you need to re-namespace rather than rename, as shown in Example 6-6.
Example 6-6. A document using the namespace foo
<foo:someElement
xmlns:foo="
<foo:aChild>
<foo:aGrandChild/>
<foo:aGrandChild>
</foo:aGrandChild>
</foo:aChild>
</foo:someElement>
For each element in the foo namespace, create a new element in the bar namespace, as shown
in Example 6-7 and Example 6-8.
Example 6-7. A stylesheet that maps foo to bar
<xsl:stylesheet version="1.0"
xmlns:xsl="
xmlns:foo="
xmlns:bar="

<xsl:import href="copy.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes"/>

<xsl:strip-space elements="*"/>

<xsl:template match="foo:*">
<xsl:element name="bar:{local-name( )}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>


</xsl:stylesheet>
Example 6-8. Output
<bar:someElement
xmlns:bar="
<bar:aChild>
<bar:aGrandChild/>
<bar:aGrandChild/>
</bar:aChild>
</bar:someElement>
6.3.3 Discussion

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×