Question

I am having trouble when using "except" in xpath. Here is the chunk of problem code. (I tried to simplify as much as possible without obscuring the whole problem).:

<!--First, create a variable containing some nodes that we want to filter out. 
 (I'm gathering elements that are missing child VALUE elements
 and whose child DOMAIN and VARIABLE elements only occur once
 in the parent list of elements.)
 I've confirmed that this part does generate the nodes I want,
 but maybe this is the incorrect result structure?-->

<xsl:variable name="badValues">
 <xsl:for-each select="$root/A[not(VALUE)]">
  <xsl:choose>
   <xsl:when test="count($root/A[DOMAIN=current()/DOMAIN and VARIABLE=current()/VARIABLE])=1">
    <xsl:copy-of select="."/>
   </xsl:when>
  </xsl:choose>
 </xsl:for-each>
</xsl:variable>

<!--Next Loop over the original nodes, minus those bad nodes.
 For some reason, this loops over all nodes and does not filter out the bad nodes.-->

<xsl:for-each select="$root/A except $badValues/A"> ...
Était-ce utile?

La solution

When you create an xsl:variable without using @select and do not specify the type with the @as, it will create the variable as a temporary tree.

You want to create a sequence of nodes, so that when they are compared in the except operator, they are "seen" as the same nodes. You can do this by specifying as="node()*" for the xsl:variable and by using xsl:sequence instead of xsl:copy-of:

<xsl:variable name="badValues" as="node()*">
    <xsl:for-each select="$root/A[not(VALUE)]">
        <xsl:choose>
            <xsl:when test="count($root/A[DOMAIN=current()/DOMAIN 
                                   and VARIABLE=current()/VARIABLE])=1">
                <xsl:sequence select="."/>
            </xsl:when>
        </xsl:choose>
    </xsl:for-each>
</xsl:variable>

Alternatively, if you were to use a @select and eliminate the xsl:for-each it would also work. As Martin Honnen suggested, you could use an xsl:key and select the values like this:

 <xsl:key name="by-dom-and-var" match="A" use="concat(DOMAIN, '|', VARIABLE)"/>

Then change your badValues to this:

<xsl:variable name="badValues" 
     select="$root/A[not(VALUE)]
                    [count(key('by-dom-and-var', 
                               concat(DOMAIN, '|', VARIABLE))/VARIABLE) = 1]"/>>

You can see the difference in the identity of the nodes by using the generate-id() function as you iterate over the items by executing this stylesheet:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>

    <xsl:template match="/">
    <xsl:variable name="root" select="*" as="item()*"/>       
    <xsl:variable name="originalBadValues">
        <xsl:for-each select="$root/A[not(VALUE)]">
            <xsl:choose>
                <xsl:when test="count($root/A[DOMAIN=current()/DOMAIN 
                                      and VARIABLE=current()/VARIABLE])=1">
                    <xsl:copy-of select="."/>
                </xsl:when>
            </xsl:choose>
        </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="badValues" as="node()*">
        <xsl:for-each select="$root/A[not(VALUE)]">
            <xsl:choose>
                <xsl:when test="count($root/A[DOMAIN=current()/DOMAIN 
                                      and VARIABLE=current()/VARIABLE])=1">
                    <xsl:sequence select="."/>
                </xsl:when>
            </xsl:choose>
        </xsl:for-each>
    </xsl:variable>

        <!--These are the generated ID values of all the A elements-->
        <rootA>
            <xsl:value-of select="$root/A/generate-id()" 
                          separator=", "/>
        </rootA>
        <!--These are the generated ID values for 
            the original $badValues/A --> 
        <originalBadValues>
            <xsl:value-of select="$originalBadValues/A/generate-id()" 
                          separator=", " />
        </originalBadValues>
        <!--These are the generated ID values for 
            the correct selection of $badValues-->
        <badValues>
            <xsl:value-of select="$badValues/generate-id()" 
                          separator=", " />
        </badValues>
        <!--The generated ID values for the result of 
            the except operator filter-->
        <final>
            <xsl:value-of select="($root/A except $badValues)/generate-id()" 
                          separator=", "/>
        </final>
    </xsl:template>        
</xsl:stylesheet>

Executed against this XML file:

<doc>
    <A>
        <VALUE>skip me</VALUE>
        <DOMAIN>a</DOMAIN>
        <VARIABLE>a</VARIABLE>
    </A>
    <A>
        <DOMAIN>a</DOMAIN>
        <VARIABLE>a</VARIABLE>
    </A>
    <A>
        <DOMAIN>b</DOMAIN>
        <VARIABLE>b</VARIABLE>
    </A>
    <A>
        <DOMAIN>c</DOMAIN>
        <VARIABLE>c</VARIABLE>
    </A>
    <A>
        <DOMAIN>a</DOMAIN>
        <VARIABLE>a</VARIABLE>
    </A>
</doc>

It produces the following output:

<rootA>d1e3, d1e15, d1e24, d1e33, d1e42</rootA>
<originalBadValues>d2e1, d2e9</originalBadValues>
<badValues>d1e24, d1e33</badValues>
<final>d1e3, d1e15, d1e42</final>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top