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

六月ゝ 毕业季﹏ 提交于 2019-12-13 20:58:35

问题


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

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