tags:

views:

572

answers:

4

All great answers! But the question deserves refinement ...

I've got the following sample XML ...

<objects>
    <object objectId="1123" ... />
    <properties refObjectId="1123" ... />
    <properties refObjectId="1123" refPropertyId="2311" ... />
    <properties refObjectId="1123" refPropertyId="4611" ... />
    <object objectId="2123" ... />
    <properties refObjectId="2123" refPropertyId="4311" ... />
    <properties refObjectId="2123" refPropertyId="8611" ... />
    ....
</objects>

... and the following XPath query ...

//object[//properties[@refObjectId=@objectId and not(@refPropertyId)]]

I thought this query would return all object nodes where there is a properties node that has a refObjectId attribute that equals the objectId attribute of the object node and no 'refPropertyId' attribute ... namely object 1123 only, not object 2123 ... but it doesn't. It seems the @objectId in the nested predicate does not refer to the objectId attribute of the object node.

Any ideas? I know the XML structure isn't nested as you would expect, but there are reasons for this structure.

+4  A: 

Generally you should avoid using // where you can. I'd consider rephrasing:

//object[../properties/@refObjectId=@objectId]

In the expression provided, your nested predicate is actually checking for

//properties/@refObjectId=//properties/@objectId

of which there are none.

I hope this helps!

EDIT: Since the question has been updated here is an updated response: You added "It seems the @objectId in the nested predicate does not refer to the objectId attribute of the object node." You're absolutely right! So let's fix it!!

//object[../properties[not(@refPropertyId)]/@refObjectId=@objectId]

This should be closer to what you're after!

Jweede
Nice XPath, Jweede.
Matthew Flaschen
A: 

Try this:

   //objects[object/@objectId = properties/@refObjectId]/object
aJ
A: 

This should work:

//objects/object[@objectId = ../properties/@refObjectId]

I am not sure how your xml is. However, if it is in the following format:

<objects>
    <object objectId="1111" />
    <properties refObjectId="1111" />
    <object objectId="2111" />
    <properties refObjectId="3111" />
    <object objectId="4111" />
    <properties refObjectId="5111" />
    <object objectId="6111" />
    <properties refObjectId="4111" />
    <object objectId="7111" />
    <properties refObjectId="7111" />
</objects>

Then you should use the following xpath to get only objects 1111 and 7111. The result should not include 4111 because the properties where refObjectId = 4111 does not immediately follow the object whose objectId=4111.

//objects/properties[@refObjectId = preceding::object[1]/@objectId]/preceding::object[1]
Rashmi Pandit
A: 

Assuming that all <properties> nodes that belong to a given <object> actually follow that object (your input seems to imply that), you could do:

/objects/properties[
  @refObjectId = preceding-sibling::object[1]/@objectId
  and 
  not(@refPropertyId)
]/preceding-sibling::object[1]

This should perform pretty well.

If you happen to be in XSLT, things get a lot simpler:

<xsl:key name="kPropertiesByObjectId" match="properties" use="@refObjectId" />

and

<xsl:template match="object">
  <!-- This tests for an empty node-set. Non-empty node sets can only happen
       for objects with at least one <properties> node without @refPropertyId -->
  <xsl:if test="key('kPropertiesByObjectId', @objectId)[not(@refPropertyId)]">
    <xsl:copy-of select="." />
  </xsl:if>
</xsl:template>

In the XSLT case, the order of object and proerties nodes becomes irrelevant.

Tomalak