XSLT 1.0 Using position() in <xsl:for-each> and <xsl:template>

匿名 (未验证) 提交于 2019-12-03 01:45:01

问题:

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<title> and <author>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.



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!