Reputation: 93
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.
Upvotes: 9
Views: 19246
Reputation: 338316
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.
Upvotes: 0
Reputation: 15816
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!
Upvotes: 10
Reputation: 23828
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]
Upvotes: 0
Reputation: 35460
Try this:
//objects[object/@objectId = properties/@refObjectId]/object
Upvotes: 0