XSLT: How to check if certain nodes are all equal in one parent
I am trying to think of a XSL snippet that would check if a class has all the students with the same lastname. It yes, then (do anything like) print "all lastnames are same" else print "all lastnames are not same".
It really doesn't matter what to print. I am just trying to find the right logic for it.
Here is my sample XML:
<root>
<class name="Physics">
<student>
<firstname>John</firstname>
<lastname>Doe</lastname>
<age>21</age>
</student>
<student>
<firstname>Mary</firstname>
<lastname>Doe</lastname>
<age>21</age>
</student>
<student>
<firstname>Ralph</firstname>
<lastname>Doe</lastname>
<age>21</age>
</student>
</class>
<class name="Math">
<student>
<firstname>John</firstname>
<lastname>Doe</lastname>
<age>21</age>
</student>
<student>
<firstname>Mary</firstname>
<lastname>Doe</lastname>
<age>21</age>
</student>
<student>
<firstname>Tee</firstname>
<lastname>Rex</lastname>
<age>21</age>
</student>
</class>
</root>
Therefore, for Physics class it would print "all lastnames are same". And for math class, it would print "all lastnames are not same".
(This is not my real XML be开发者_如何学运维cause it was irreducible to a smaller problem, so instead I custom made this XML to represent my issue)
Any help will be greatly appreciated.
regards, Shobhit
Hm. Knowing nothing else about your problem, I would do this:
<xsl:template match="class">
<xsl:choose>
<xsl:when test="
count(student[not(lastname = preceding-sibling::student/lastname)]) = 1
">
<xsl:text>all lastnames are same</xsl:text>
<xsl:when>
<xsl:otherwise>
<xsl:text>all lastnames are not same</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The XPath expression
student[not(lastname = preceding-sibling::student/lastname)]
selects all <student>
nodes whose <lastname>
is unlike any preceding lastname within the same class.
In classes with all the same lastnames, their count is exactly 1 (because the very first student always has a lastname unlike any preceding lastname). If the count is higher than 1, then some students in that class have different lastnames.
The case where a class has no students at all would be recognized as the <xsl:otherwise>
case in the above logic. You might want to handle that case explicitly in some fashion.
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="student"/>
<xsl:template match="student[1]">all lastnames are same</xsl:template>
<xsl:template match="student[1][lastname != ../student/lastname]" priority="1">all lastnames are not same</xsl:template>
</xsl:stylesheet>
Result:
<root>
<class name="Physics">all lastnames are same</class>
<class name="Math">all lastnames are not same</class>
</root>
Note: Nodeset comparison.
Edit: Sorry, miss @name.
Edit 2: Compact one. Note @priority to avoid error recovery.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my"
>
<xsl:output method="text"/>
<xsl:key name="kstudentByName"
match="student" use="lastname"/>
<my:text>
<text> not </text>
</my:text>
<xsl:variable name="vText" select="document('')/*/my:text/*"/>
<xsl:template match="/">
<xsl:variable name="vNumNames" select=
"count(*/*/student[generate-id()
=
generate-id(key('kstudentByName', lastname)[1])
]
)
> 1
"/>
All names are <xsl:value-of select="$vText[$vNumNames]"/> the same.
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document produces the correct result:
All names are not the same.
Do note the use of keys, which makes this transformation much more efficient than the quadratical in time complexity of comparing each name with the names of all preceding siblings
精彩评论