badperson
badperson

Reputation: 1614

xpath : test if node is the only child other than specified ok elements

I want to write an xsl template that checks to see if a given node is the only child node, other than certain specified elements :

in this example, <target/> will be changed to <hit/> as it is the only <target/> node, and only <ok/> nodes precede it

<root>
<!-- this is ok, the ok nodes are at the top, followed by only 1 target -->
<mynode>
    <ok1/>
    <ok2/>
    <target/>
</mynode>

<!-- will fail, bad element before target -->
<mynode>
    <ok1/>
    <ok2/>
    <bad/>
    <target/>
</mynode>

<!-- no match, multiple target nodes -->
<mynode>
    <ok1/>
    <ok2/>
    <target/>
    <target/>
</mynode>
</root>

I was using this xpath :

<xsl:template match="target[not(following-sibling::*)]
                       [not(preceding-sibling::target)]
                       [not(preceding-sibling::*[starts-with(name(), 'bad' or 'hello')])]
                 ">
    <hit>
        <xsl:apply-templates/>
    </hit>
</xsl:template>

In that last predicate, do I have to indicate specifically any node I don't want? Can I something like

not(preceding-sibling::*[not(starts-with(name(), 'ok'))])

thanks

Upvotes: 1

Views: 1345

Answers (2)

JLRishe
JLRishe

Reputation: 101682

How about this:

<xsl:template match="target[count(../*) = 
                            count(../*[starts-with(name(), 'ok')]) + 1]">
    <hit>
        <xsl:apply-templates/>
    </hit>
</xsl:template>

The interpretation is to match target if:

  • The number of all child elements of its parent is equal to
  • The number of all good child elements of its parent plus one (itself)

Edit If you only want to match the element if it's the last child of its parent (you didn't say so in your question, but your examples suggest that), you can add and not(following-sibling::*) to the predicate above, or here is an alternative approach:

<xsl:template match="target[not(following-sibling::*) and 
                            not(preceding-sibling::*[not(starts-with(name(), 'ok'))])
                           ]">

but you seem to have already figured that one out on your own.

Lastly, if what you actually want to do is allow certain specific OK elements and not match the names based on a prefix, you can use self:: for this:

<xsl:template match="target[count(../*) = 
                            count(../*[self::allgood or self::great]) + 1]">

<xsl:template match="target[not(following-sibling::*) and 
                            not(preceding-sibling::*[not(self::allgood or
                                                         self::great     )]
                               )]">

Upvotes: 3

Lech Rzedzicki
Lech Rzedzicki

Reputation: 435

[not(preceding-sibling::*[starts-with(name(), 'bad' or 'hello')])]

won't work as 'bad' or 'hello' is boolean or string You also don't need to use double not() and simply do

preceding-sibling::*[starts-with(name(),'ok')]

You can also create a whitelist or a blacklist and iterate over it with a contains() XPath function, for example:

<xsl:variable name="oks" select="ok1 ok2 ok3"/>

and then match

preceding-sibling::*[contains($oks, name())]

Upvotes: 0

Related Questions