问题
My understanding is that the usage of <xsl:template />
and <xsl:for-each>
almost serve the same purpose and <xsl:for-each >
is a sort of "anonymous inline template".
Question: However, considering the below scenario, i think using <xsl:for-each>
is more appropriate.
Please validate my understanding, or is there a way the output can be achieved through <xsl:template>
as well?
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="book1">
<title>charithram</title>
<author>sarika</author>
</book>
<book id="book2">
<title>doublebell</title>
<author>psudarsanan</author>
</book>
</books>
XSLT1 [using <xsl:for-each >
] - This gives the expected output
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<newbooks>
<xsl:for-each select="books/*">
<newbook id="book{position()}">
<title><xsl:value-of select="title" /></title>
<author> <xsl:value-of select="author" /></author>
</newbook>
</xsl:for-each>
</newbooks>
</xsl:template>
</xsl:stylesheet>
XSLT2 [using <xsl:template >
]
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<newbooks>
<xsl:apply-templates/>
</newbooks>
</xsl:template>
<xsl:template match="books/*" >
<newbook id="book{position()}">
<title><xsl:value-of select="title" /></title>
<author> <xsl:value-of select="author" /></author>
</newbook>
</xsl:template>
</xsl:stylesheet>
This does not give the expected output, Instead, the output obtained is,
<?xml version="1.0" encoding="UTF-8"?>
<newbooks>
<newbook id="book2">
<title>charithram</title>
<author>sarika</author>
</newbook>
<newbook id="book4">
<title>doublebell</title>
<author>psudarsanan</author>
</newbook>
</newbooks>
The only reason i can think of getting 2 and 4, could be the position of id
node inside the context.
"The position()
function returns the position of the context node in a selected set of nodes". As per this definition, it works in the case of for-each
as the context is each <book>
element. But why it is not applicable in the case of template?
I also tried with <xsl:number>
, but cannot make it to work as expected
<xsl:template match="/">
<newbooks>
<xsl:apply-templates/>
</newbooks>
</xsl:template>
<xsl:template match="books/*" >
<newbook >
<xsl:attribute name="id">
<xsl:text>book</xsl:text><xsl:number/>
</xsl:attribute>
<title><xsl:value-of select="title" /></title>
<author> <xsl:value-of select="author" /></author>
</newbook>
</xsl:template>
I am getting the output of book1, book1 [not incrementing]
Please help.
Note: I use XSLT to transform into output XML which has complete different set of tags compared to input XML, so I don't use a template to copy.
回答1:
I think your understanding of the differences is a bit faulty, but that is not the issue here, since your <xsl:apply-templates />
essentially does the same as an <xsl:for-each>
.
But: You have a major difference in the selectors you use in the two code snippets. Your first example uses <xsl:for-each select="books/*">
, which creates a node set of cardinality two, numbered, unsurprisingly, 1 and 2. Your second code snippet uses <xsl:apply-templates />
, which is a shorthand for <xsl:apply-templates select="node()">
, and that creates a node set of cardinality five(!), with nodes 1, 3, and 5 being text nodes (that happen to only contain whitespace), and nodes 2 and 4 being the ones you actually want to target.
Solution? Add your selector to the apply-templates
element, as in <xsl:apply-templates select="books/*" />
.
EDIT So, why is the node set in the second case that big? Because it picks up all child nodes of books
, and the way XML works is that that includes not only elements – it also includes text nodes (you do want your <title>
and <author>
to have a text node child) – including the text nodes that stem from your indenting, which only contain things like end-of-line and spaces and/or tabs, collectively known as whitespace. Try the original second code on these input variations:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
.
<?xml version="1.0" encoding="UTF-8"?>
<books><book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
.
<?xml version="1.0" encoding="UTF-8"?>
<books><book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1><book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
Also note that elements and text are not the only types of nodes:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<!-- first book -->
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<!-- second book -->
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
If you had templates matching these (such as an <xsl:template match='text()'>
or maybe something more specific), you’d find the corresponding output there. XSLT just defaults to ignoring unmatched nodes in apply-templates.
来源:https://stackoverflow.com/questions/21470578/xslt-1-0-using-position-in-xslfor-each-and-xsltemplate