LXML the .xpath() method context node is not properly recognized in predicates?

Viewed 73

I have a problem with Packet LXML (lxml 4.8.0) for Python. I am using the method .xpath() and it is not behaving as I thought it would.

The following XML file has an element CONTEXT. I find this with a .xpath() expression and store it in the variable tmp (type(tmp) --> lxml.etree._Element)

In my understanding the variable tmp is now the context node. Now when I enter a search path again with tmp.xpath() I expect this FIND element (<FIND Solution="1129"/>) for this search path "tmp.xpath('//REF[@Id = @Ref]/FIND')" but I don't get any result back.

Thus the assumption is obvious that in the predicate the associated attribute @Ref is not recognized. But if I use the following search expression (tmp.xpath('//REF[@Id = //CONTEXT[@Ref = @Ref]/@Ref]/FIND')) the correct FIND element is found. Here the @Ref attribute of the context node is recognized correctly.

Why is that?

XML file:

<?xml version="1.0"?>
<FOO>
    <BAR>
        <CONTEXT Id="SW-1" Ref="Find-3"/>
        <CONTEXT Id="SW-2" Ref="Not-3"/>
    </BAR>      
    <FUU>
        <REF Id="Find-1">
            <FIND Solution="1121"/>
        </REF>
        <REF Id="Find-2">
            <FIND Solution="1222"/>
        </REF>
        <REF Id="Find-3">
            <FIND Solution="1129"/>
        </REF>
        <REF Id="Find-4">
            <FIND Solution="1100"/>
        </REF>
        <REF Id="Find-5">
            <FIND Solution="1205"/>
        </REF>
    </FUU>
</FOO>

Python file:

import os, sys
import lxml
from lxml import etree
import xml.etree.ElementTree as ET

print("Hello")

dir = os.path.dirname(os.path.realpath(__file__))
xml = os.path.join(dir, "test2.xml")

print(f"xml file    : {xml}")
print()

xml_root = lxml.etree.parse(xml)
print(f"xml parse   : {xml_root}")
print(f"xml parse   : {type(xml_root)}")

tmp = xml_root.xpath("//CONTEXT[1]")[0]

print(f"Xpath Result: {tmp}     {type(tmp)}")

res_1 = tmp.xpath("//REF[@Id = //CONTEXT[@Ref = @Ref]/@Ref]/FIND")
print(f"Xpath Result: {res_1}")

res_2 = tmp.xpath("//REF[@Id = @Ref]/FIND")
print(f"Xpath Result: {res_2}")
2 Answers

In this case variable substitution can be used:

from lxml import etree

# parse XML
doc = etree.fromstring(xml)

# create variable "tmp"
tmp = doc.xpath('//CONTEXT[@Id="SW-1"]')[0]

# value of the attribute "Ref" is used as a variable "ref", which is passed to the XPath expression
result = tmp.xpath('//REF[@Id = $ref]/FIND/@Solution', ref = tmp.get('Ref'))

print(result)

The xpath to search any REF whose @Id attribute equals that of a CONTEXT @Ref attribute could be

res_1 = xml_root.xpath("//REF[@Id = //CONTEXT/@Ref]/FIND")
print(f"Xpath Result: {res_1}")

Notice that xpath is searched from root.

To get the first only

//REF[@Id = //CONTEXT[1]/@Ref]/FIND
Related