Use XPath to find nodes whose attribute is missing from another part of XML document

I've got an XML document similar to this:

<pipeline>
    <log>
        <error stageName="One" message="Ack!" />
        <error stageName="Two" message="Ack!" />
        <error stageName="Three" message="Ack!" />
    </log>
    <stages>
        <stage name="One" />
        <stage name="Two" />
    </stages>
</pipeline>

I'm trying to use XSLT to make a report of all the errors grouped by stage. At the end of the report, I want all the errors not-associated with any stage. I'm able to report on each stage's errors. Now I need a way to conditionally show all the other errors.

This is the closest I've come:

<xsl:template match="/pipeline">
    <xsl:variable name="unknown.stages" 
                  select="log/error[@stageName != stages/stage/@name]" />

    <xsl:if test="count($unknown.stages) > 0">
        <h1>Other Errors</h1>
        <xsl:apply-templates select="$unknown.stages" />
    </xsl:if>
</xsl:template>

But it ends up showing all the errors instead of just the errors not associated with a stage. Is there an XPath query I can use in my xsl:variable declaration to select all the error nodes whose stageName attribute has a value that doesn't exist in the list of stages/stage elements?

Update: Here is the correct XPath query. I have to create a key on the stages element, and then use that key to find errors that don't match:

<xsl:key name="stageName" match="/pipeline/stages/stage" use="@name" />

<xsl:template match="/pipeline">
    <xsl:variable name="unknown.stages" 
                  select="log/error[not(key('stageName',@stageName)]" />

    <xsl:if test="count($unknown.stages) > 0">
        <h1>Other Errors</h1>
        <xsl:apply-templates select="$unknown.stages" />
    </xsl:if>
</xsl:template>

Answers


Are you sure you are getting all errors shown? With your current xpath it will be looking for a stages element that is a child of the current error element you are selecting. I would have expected the expression to look more like this

<xsl:variable name="unknown.stages" select="log/error[@stageName != //stages/stage/@name]" />

Having said that, this returns all error elements because it is return error elements for which there exists a stage with a different name.

Anyway, to solve this, consider using a key to look up the stage elements

<xsl:key name="stages" match="stage" use="@name" />

Then you can write your expression like this, which should check for existence as your require:

<xsl:variable name="unknown.stages"select="log/error[not(key('stages', @stageName))]" />

Need Your Help

Documentation for @UiHandler

java gwt uibinder

I started to look into using GWT in combination with UiBuilder. I'm a bit puzzled about how you can use the @UiHandler(..) directive to make simple event handle code as written down in the GWT

Convert Hashes to CSV

ruby csv hash nokogiri saxparser

I have a CSV that I like to save all my hash values on it. I am using nokogiri sax to parse a xml document and then save it to a CSV.