3

I would like to retrieve all nodes with a node, subnode and subsubnode condition applied

I tried to receive it with this xpath command:

xmlDoc.SelectNodes("//WorkItem[WorkItemType[text()='Product Backlog Item'] and ./Childern/WorkItem/WorkItemType[text() = 'Task'] and /Childern/WorkItem/WorkItemType[text() != 'Product Backlog Item'] and ./Childern/WorkItem/WorkItemType[text() = 'Task']]");

This provides me no nodes. But I expect the WorkItem nodes with Id's: 719 and 720 but NOT 717

How can I express this in XPath?

The given xml looks like this:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <id>716</id>
    <WorkItemType>Product Backlog Item</WorkItemType>
    <Children>
        <WorkItem>
            <id>717</id>
            <WorkItemType>Product Backlog Item</WorkItemType>
            <Children>
                <WorkItem>
                    <id>719</id>
                    <WorkItemType>Product Backlog Item</WorkItemType>
                    <Children>
                        <WorkItem>
                            <id>721</id>
                            <WorkItemType>Task</WorkItemType>
                            <Children>
                                <WorkItem>
                                    <id>722</id>
                                    <WorkItemType>Task</WorkItemType>
                                    <Children/>
                                </WorkItem>
                            </Children>
                        </WorkItem>
                    </Children>
                </WorkItem>
                <WorkItem>
                    <id>720</id>
                    <WorkItemType>Product Backlog Item</WorkItemType>
                    <TreeLevel>2</TreeLevel>
                    <Children>
                        <WorkItem>
                            <id>724</id>
                            <WorkItemType>Task</WorkItemType>
                            <Children>
                                <WorkItem>
                                    <id>726</id>
                                    <WorkItemType>Task</WorkItemType>
                                    <Children/>
                                </WorkItem>
                            </Children>
                        </WorkItem>
                    </Children>
                </WorkItem>
                <WorkItem>
                    <id>723</id>
                    <WorkItemType>Task</WorkItemType>
                    <Children>
                        <WorkItem>
                            <id>744</id>
                            <WorkItemType>Task</WorkItemType>
                            <Children/>
                        </WorkItem>
                    </Children>
                </WorkItem>
            </Children>
        </WorkItem>
    </Children>
</root>

Update

Due to Rolf Rander's input I add some more explanation:

  • I want to have WorkItem-elements of WorkItemType 'Product Backlog Item' which has ONLY WorkItem-Elements of WorkItemType 'Task' as any kind of children (children, grandchildren grandgrandchildren and so on...).

The level how deep are children capsuled is not clear at design time. I've updated my question. (and I corrected my typo Childern -> Children in the xml file).

Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92

2 Answers2

3

It is not completely clear what you try to achieve, but reformatting your code it seems you have several reduntant tests:

//WorkItem[
WorkItemType[text()='Product Backlog Item'] and
./Childern/WorkItem/WorkItemType[text() = 'Task'] and
/Childern/WorkItem/WorkItemType[text() != 'Product Backlog Item'] and
./Childern/WorkItem/WorkItemType[text() = 'Task']
]

You test for "equal to 'Task'" twice, and it shouldn't be neccessary to both check that it is equal to 'Task' and different from 'Product Backlog Item'.

You can check for "any children or grandchildren" with //, thus:

  • Any WorkItem
  • with WorkItemType = 'Product Backlog Item', and
  • no nodes under Children with name WorkItemType and contents 'Task'

can be translated to:

//WorkItem[
     WorkItemType[text()='Product Backlog Item'] and
     not(Children//WorkItemType[text() != 'Task'])
]
Rolf Rander
  • 3,221
  • 20
  • 21
  • Hello Rolf, thanks for your answer. You got my question completely right. I want to have WorkItem-elements of type 'Product Backlog Item' which has ONLY Task as any kind of childern (childern, grandchildern grandgrandchildern and so on...). The level how deep are childern is not clear at design time. I've updated my question. – Bruno Bieri Dec 07 '15 at 07:11
  • 1
    This does not return id 723, I assume that is what you want – Rolf Rander Dec 07 '15 at 08:56
1

Instead of this..

./Childern/WorkItem/WorkItemType[text() != 'Product Backlog Item']

use this one :

not(./Childern/WorkItem/WorkItemType[text() = 'Product Backlog Item'])

The former evaluates to true if there is at least one WorkItemType match the criteria (has the first text node child not equals to 'Product Backlog Item'). Contrast it with the latter which evaluates to true only when there is no WorkItemType match the criteria (has the first text node child equals to 'Product Backlog Item').

So the complete XPath would look like this (formatted for readability) :

//WorkItem[
    WorkItemType[text()='Product Backlog Item'] 
        and 
    ./Childern/WorkItem/WorkItemType[text() = 'Task'] 
        and 
    not(./Childern/WorkItem/WorkItemType[text() = 'Product Backlog Item']) 
        and 
    ./Childern/WorkItem/WorkItemType[text() = 'Task']
]

UPDATE:

In response to the updated question, I would do something like this :

//WorkItem[WorkItemType[text()='Product Backlog Item']]
          [not(.//WorkItem/WorkItemType/text() != 'Task')]
har07
  • 88,338
  • 12
  • 84
  • 137
  • Hi har07 thanks for your reply. I studied Rolf Rander's anwer too. It seems both work. Am I right that the ./Childern (the dot befor the / means the rule coming is applied to the node element which was filtered??). Please check my updated question maybe there is even a smarter way to do what I seek. – Bruno Bieri Dec 07 '15 at 08:11
  • 1
    Yes, the dot at the beginning indicates that the following XPath expression should be processed from current context element. Alternatively, you can just say `Children/.....` directly – har07 Dec 07 '15 at 09:40