0
votes

So to expand...

I have some XML

<root>
    <list>
        <item value="9"/>
        <item value="3"/>
        <item value="1"/>
        <item value="8"/>
        <item value="4"/>
    </list>
</root>

I have an XPath "/root/list/item [4]" which I believe will retrieve the 5th 'item' element (whose value attribute is 4)

This suggests that the XPath "/root/list/item [5]" will reference the (as yet non existent) 6th item.

What I would like is an XPath expression which will reference the 'Next' (as yet non-existant) 'item' element regardless of how many items there are currently...

I assume that this will pass an expression (which will count the number of current elements) rather than a hard coded number.

I know how I would do it, but simply cannot find how I should do it... frustrating.

Update: To clarify... I would like to return "the item node after the last item node". the attributes were there just to distinguish the existing nodes). I appreciate that this will return an empty nodeset.

Update: Well I have my answer so I guess it's only fair I explain why I wanted this :)

Basically the xPath is used by some .Net code as a write address rather than a read address.

I pass the xPath of a non existent location to an EnsureNodeExists routine. (Note: I cannot alter this EnsureNodeExists routine) The code will iterate each element of the xPath, creating it if it's not there and then setting it as the parent for the next iteration.

Thus I can call this code with "/root/list/item[position() = last()]/following-sibling::item[1]" 3 times and this will result in 3 item nodes being created.

4
I think there may be a general misapprehension on your side: An XPath expression refers to a node set, not to a position. There is no selecting "the one after the last". If your code works now, it would also work with anything else that returns an empty node set, like "/root/list/item[false()]" - Tomalak
Interesting...I will create a new test (duplicate of what is now working), adjust using your new xPath and report back . - Rory Becker
This question shows that you need to read some basic introduction to XPath. The answers in SO cannot be a substitute and provide at least a basic familiarity with the subject. - Dimitre Novatchev
Well it still worked but I don't understand why - Rory Becker
@Rory Becker: Your code appends a node if it is missing. It is as simple as that. You just interpret too much "intelligence" into the simple "Is it missing?" test. It will always just append the missing node at the end, no matter what expression you use to indicate a missing node. - Tomalak

4 Answers

5
votes

First off, your XPath:

/root/list/item[4]

fetches the fourth item (since the [4] predicate is a shorthand for [position() = 4]).

To fetch the item with @value 4, use:

/root/list/item[@value = '4']

if you want to fetch the item after the item with @value 4, use:

/root/list/item[@value = '4']/following-sibling::item[1]

This translates to English as "going from the /root/list/item with @value = '4', look at the following siblings named 'item' and take the first one".

This works even if there is no following item. In this case the selected node set will be empty.

EDIT: Be aware that the results the above expression produces may be unexpected if there is more than one item with value 4. You will get all items that follow an item with value four. This will return one item at maximum:

/root/list/item[@value = '4'][1]/following-sibling::item[1]

To refer to the last node in a list, use:

/root/list/item[position() = last()]

A nice visual that explains the XPath axes can be found here: http://nwalsh.com/docs/tutorials/xsl/xsl/graphics/axes.gif (part of a larger tutorial).

3
votes

If you just want an expression to return an empty nodeset, the general thing to do I think is something like /parent::node(), which will be empty because the root has no parents.

I suppose you could do something like /root/list/item[position() = (last() + 1)], but it would give you the same result as anything that returned no nodes. So I'm not quite sure what you're actually trying to accomplish.

1
votes

try the following-sibling expression

help with this sort of thing can be found here

0
votes

If what you're trying to do is to add a new node, directly after the last one in your set, then in MSXML you can just use appendChild on your parent element (list). It will be appended as the last child of the parent node.