XPath results from XSLT are incorrect - not going through entire XML
So I have a really confusing problem on my hands..
It seems as though that the entire XML data hierarchy is not being searched through when using an XPath expression in XSL.
Some dummy XML data:
<pets name="myPets" NUM="2">
<dog name="allMyDogs" NUM="5">
<dog name="Frank" NUM="3"/>开发者_Python百科;
<dog name="Spot" NUM="4"/>
<dog name="Rover" NUM="1"/>
<dog name="Rupert" NUM="6"/>
<cat name="Lucy" NUM="4"/>
</dog>
<cat name="allMyCats" NUM="4">
<cat name="Simba" NUM="4"/>
<cat name="Princess" NUM="5"/>
<cat name="Fluffy" NUM="1"/>
<cat name="Lucy" NUM="3"/>
<cat name="Lucy" NUM="35"/>
<cat name="Lucy" NUM="6"/>
<cat name="Lucy" NUM="1"/>
</cat>
<cat name="Lucy" NUM="9"/>
</pets>
The following is the portion of XSLT code I believe is causing the issue:
<xsl:key name="elem_key" match="elem" use="concat(@key, .)" />
<xsl:variable name="all_data">
<xsl:apply-templates select="*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:variable>
<xsl:template match="//*[@NUM<=4]">
<elem key="{name()}">
<xsl:copy-of select="@*" />
<xsl:for-each select="@*">
<xsl:sort select="name()" />
<attribute>|<xsl:value-of select="name()" />|</attribute>
</xsl:for-each>
</elem>
</xsl:template>
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="msxsl:node-set($all_data)">
<xsl:for-each select="*[generate-id()=generate-id(key('elem_key',concat(@key, .))[1])]">
<table >
<tr>
<td>Element Name</td>
<xsl:for-each select="*">
<td>
<xsl:value-of select="translate(.,'|','')" />
</td>
</xsl:for-each>
</tr>
<xsl:for-each select="key('elem_key', concat(@key, .))">
<xsl:variable name="curr_elem" select="." />
<tr>
<td>
<xsl:value-of select="@key" />
</td>
<xsl:for-each select="*">
<td >
<xsl:value-of select="$curr_elem/@*[name()=translate(current(),'|','')]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
<p />
</xsl:for-each>
</xsl:for-each>
</body>
</html>
</xsl:template>
The XPath expression used:
//*[@NUM<=4]
(Above should generate many results)
The incorrect results I get:
Element Name name NUM
pets myPets 2
As you can see it seems to stop at the root.
If I change the XPath to:
//*[@NUM=4]
I get these incorrect results:
Element Name name NUM
dog Spot 4
Element Name name NUM
cat Lucy 4
Element Name name NUM
cat allMyCats 4
What appears to happen is that it will stop searching down into the hierarchy once it has found a match. The first two (Spot and Lucy) are correct, but then it stopped at allMyCats, when there is a child node of allMyCats (Simba) that has a NUM of 4.
Can anyone help me fix this code so that it returns the correct results? I'm quite frustrated! :(
Thanks!
Just change:
<xsl:variable name="all_data">
<xsl:apply-templates select="*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:variable>
To:
<xsl:variable name="all_data">
<xsl:apply-templates select="*/*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:variable>
The first (of many) problem is:
<xsl:apply-templates select="*">
selects all children elements of the current node. The current node is /
and it has only one child -- the top element pets
.
You actually want to collect the data for all children of pets
.
There are other problems, but I don't have the space and time to address them here.
The complete corrected code is below:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:key name="elem_key" match="elem"
use="concat(@key, .)" />
<xsl:variable name="all_data">
<xsl:apply-templates select="*//*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:variable>
<xsl:template match="*[@NUM<=4]">
<elem key="{name()}">
<xsl:copy-of select="@*" />
<xsl:for-each select="@*">
<xsl:sort select="name()" />
<attribute>|<xsl:value-of
select="concat(name(),'=',.)" />|</attribute>
</xsl:for-each>
</elem>
</xsl:template>
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="msxsl:node-set($all_data)">
<xsl:for-each select=
"*[generate-id()
=
generate-id(key('elem_key',concat(@key, .))[1])
]">
<table >
<tr>
<td>Element Name</td>
<xsl:for-each select="*">
<td>
<xsl:value-of select=
"substring-before(translate(.,'|',''),'=')" />
</td>
</xsl:for-each>
</tr>
<tr>
<td>
<xsl:value-of select="@key" />
</td>
<xsl:for-each select="*">
<td>
<xsl:value-of select=
"substring-after
(translate(current(),'|',''),
'='
)"/>
</td>
</xsl:for-each>
</tr>
</table>
<p />
</xsl:for-each>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<pets name="myPets" NUM="2">
<dog name="allMyDogs" NUM="5">
<dog name="Frank" NUM="3"/>
<dog name="Spot" NUM="4"/>
<dog name="Rover" NUM="1"/>
<dog name="Rupert" NUM="6"/>
<cat name="Lucy" NUM="4"/>
</dog>
<cat name="allMyCats" NUM="4">
<cat name="Simba" NUM="4"/>
<cat name="Princess" NUM="5"/>
<cat name="Fluffy" NUM="1"/>
<cat name="Lucy" NUM="3"/>
<cat name="Lucy" NUM="35"/>
<cat name="Lucy" NUM="6"/>
<cat name="Lucy" NUM="1"/>
</cat>
<cat name="Lucy" NUM="9"/>
</pets>
the wanted result (multiple animals) is produced:
<html xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<body>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>Lucy</td>
<td>4</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>allMyCats</td>
<td>4</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>Simba</td>
<td>4</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>Fluffy</td>
<td>1</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>Lucy</td>
<td>3</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>cat</td>
<td>Lucy</td>
<td>1</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>dog</td>
<td>Frank</td>
<td>3</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>dog</td>
<td>Spot</td>
<td>4</td>
</tr>
</table>
<p></p>
<table>
<tr>
<td>Element Name</td>
<td>name</td>
<td>NUM</td>
</tr>
<tr>
<td>dog</td>
<td>Rover</td>
<td>1</td>
</tr>
</table>
<p></p>
</body>
</html>
*[generate-id()=generate-id(key('elem_key',concat(@key, .))[1])]
You've a key doing a selection that matches a few cases, and then you ask for just the first with [1]
I'm not clear on what layout you are wanting as the correct results to advise further.
精彩评论