Simple XPath 2.0:
empty(
(for $parentA-Dubled in /*/*[a = following-sibling::*/a]
return
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
)
[not(.)]
)
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select=
"empty(
(for $parentA-Dubled in /*/*[a = following-sibling::*/a]
return
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
)
[not(.)]
)
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document, it evaluates the XPath expression and outputs the result of this evaluation.
When applied on the first provided XML document, the wanted, correct result is produced:
true
When applied on the second provided XML document, again the wanted, correct result is produced:
false
Explanation:
This sub-expression:
(for $parentA-Dubled in /*/*[a = following-sibling::*/a]
return
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
evaluates to a sequence of boolean values: true()
/ false()
true()
is returned when this is true:
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
This means that true()
is returned for every occasion when there is an $parentA-Dubled/a
that has no other a
(a child of a following sibling of $parentA-Dubled
with the same value as $parentA-Dubled/a
but the value of its b
sibling is different than the value of $parentA-Dubled/b
.
To summarize: true()
is returned when for all a
elements with the same value, their b
siblings also have (all b
s) the same value
Then when is the case when false()
is returned?
Returning false()
means that empty()
returned false()
-- that is, there exists at least one occasion of two a
elements that have the same value, but their b
siblings have different values.
Thus, the sub-expression above returns a sequence such as:
true(), true(), true(), ..., true()
-- all values are true()
or
true(), true(), true(), ..., false), ..., true()
-- at least one of the values is false()
The original problem requires us to return true()
in the first case and to return false()
in the second case.
This is easy to express as:
empty($booleanSequence[. eq false()])
-- and this is equivalent to the shorter:
empty($booleanSequence[not(.)])
Now, we just need to substitute in the above expression $booleanSequence
with the first sub-expression that we analyzed above:
(for $parentA-Dubled in /*/*[a = following-sibling::*/a]
return
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
Thus we obtain the complete XPath expression that solves the original problem:
empty(
(for $parentA-Dubled in /*/*[a = following-sibling::*/a]
return
empty($parentA-Dubled/following-sibling::*
[$parentA-Dubled/a eq a and $parentA-Dubled/b ne b])
)
[not(.)]
)