Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPath and tricky nested nodes

Tags:

xml

xslt

xpath

Given the following tricky XML:

<Type>
    <ID></ID>
    <Name></Name>
    <Child>
        <Type>
            <ID></ID>
            <Name></Name>
            <Child>
                <Type>
                    <ID></ID>
                    <Name></Name>
                    <Child>
                        <Type>
                            <ID></ID>
                            <Name></Name>
                            <Child>
                                <Type>
                                    <ID></ID>
                                    <Name>FIND ME</Name>
                                </Type>
                            </Child>
                        </Type>
                    </Child>
                </Type>
            </Child>
        </Type>
    </Child>
</Type>

Is it possible to obtains the deepest Type's Name field? I've tried constructions like that:

//*not(*)

but with no results..

like image 627
Vladimir Kishlaly Avatar asked Apr 25 '26 11:04

Vladimir Kishlaly


2 Answers

I. This short and simple XPath 1.0 expression:

//*[not(../*/*)]

when evaluated against the provided XML document:

<Type>
    <ID></ID>
    <Name></Name>
    <Child>
        <Type>
            <ID></ID>
            <Name></Name>
            <Child>
                <Type>
                    <ID></ID>
                    <Name></Name>
                    <Child>
                        <Type>
                            <ID></ID>
                            <Name></Name>
                            <Child>
                                <Type>
                                    <ID></ID>
                                    <Name>FIND ME</Name>
                                </Type>
                            </Child>
                        </Type>
                    </Child>
                </Type>
            </Child>
        </Type>
    </Child>
</Type>

selects these two elements:

<ID/>
<Name>FIND ME</Name>

Therefore, in your case one XPath expression that produces the wanted result is:

//*[not(../*/*)]/Name

II. Generic XPath 1.0 expression that selects the elements with maximum depth when it is known that the maximum depth isn't greater than a given number:

   //*[count(ancestor::*) >= 9]
   |
    //*[not(//*[count(ancestor::*) >= 9])]
                    [count(ancestor::*) = 8]
   |
    //*[not(//*[count(ancestor::*) >= 8])]
                    [count(ancestor::*) = 7]
   |
    //*[not(//*[count(ancestor::*) >= 7])]
                    [count(ancestor::*) = 6]
   |
    //*[not(//*[count(ancestor::*) >= 6])]
                    [count(ancestor::*) = 5]
   |
    //*[not(//*[count(ancestor::*) >= 5])]
                    [count(ancestor::*) = 4]
   |
    //*[not(//*[count(ancestor::*) >= 4])]
                    [count(ancestor::*) = 3]
   |
    //*[not(//*[count(ancestor::*) >= 3])]
                    [count(ancestor::*) = 2]
   |
    //*[not(//*[count(ancestor::*) >= 2])]
                    [count(ancestor::*) = 1]

   |
    /*[not(//*[count(ancestor::*) >= 1])]

While this seems a very long and unwieldy, an XML document typically is not more than 4-5 level deep and such expression is actually practical.


III. Generic XPath 2.0 solution:

//*[not(*) and count(ancestor::*) = max(//*/count(ancestor::*))]
like image 199
Dimitre Novatchev Avatar answered Apr 28 '26 15:04

Dimitre Novatchev


Try this XPath:

//Type[not(descendant::Type)]/Name
like image 27
Kirill Polishchuk Avatar answered Apr 28 '26 16:04

Kirill Polishchuk