Запрос python xml get parent

У меня есть большой XML-документ, который выглядит так:

<Node name="foo">
    <Node name="16764764625">
        <Val name="type"><s>3</s></Val>
        <Val name="owner"><s>1</s></Val>
        <Val name="location"><s>4</s></Val>
        <Val name="brb"><n/></Val>
        <Val name="number"><f>24856</f></Val>
        <Val name="number2"><f>97000.0</f></Val>
    </Node>
    <Node name="1764466544">
        <Val name="type"><s>1</s></Val>
        <Val name="owner"><s>2</s></Val>
        <Val name="location"><s>6</s></Val>
        <Val name="brb"><n/></Val>
        <Val name="number"><f>265456</f></Val>
        <Val name="number2"><f>99000.0</f></Val>
    </Node>
    ...
</Node>

Моя задача - получить значение родительского node: 1764466544 (значение имени во 2-м Node), выполнив поиск, чтобы найти, содержит ли подэлемент node Val name= "number" 265456

Я делал кучу чтения на XPath и ElementTree, но я все еще не уверен, с чего начать на самом деле запрашивать это. Ищите примеры... В результате я не могу найти ссылку на родительский node.

Еще новичок в python.. любые предложения будут оценены.

Спасибо

Ответ 1

Этот XPath:

/Node/Node[Val[@name='number']/f='265456']/@name

Выходы:

1764466544

Ответ 2

К сожалению, при использовании API ElementTree каждый объект Element не имеет ссылки на свой родительский элемент, поэтому вы не можете поднять дерево из известной точки. Вместо этого вы должны найти возможные родительские объекты и отфильтровать те, которые вы хотите.

Это обычно делается с выражениями XPath. Однако ElementTree поддерживает только подмножество XPath (см. Документы), наиболее полезные части которого были добавлены только в ElementTree 1.3, который появляется только с Python 2.7+ или 3.2 +.

И даже ElementTree XPath не может работать с вашим файлом, как есть - нет способа выбрать на основе текста node, только его атрибуты (или значения атрибутов).

Мои эксперименты нашли только два способа, которыми вы можете продолжить с помощью ElementTree. Если вы используете Python 2.7+ (или можете загружать и устанавливать более новую версию ElementTree для работы со старыми версиями Python), и вы можете изменить формат XML файла, чтобы поместить числа как атрибуты, например

<Val name="number"><f val="265456" /></Val>

то следующий код Python вытащит интересующие узлы:

import xml.etree.ElementTree as ETree
tree = ETree.ElementTree(file='sample.xml')
nodes = tree.findall(".//Node/Val[@name='number']/f[@val='265456']....")

Для старых Python или если вы не можете изменить формат XML, вам придется отфильтровать недействительные узлы вручную. Для меня работали следующие:

import xml.etree.ElementTree as ETree
tree = ETree.ElementTree(file='sample.xml')
all = tree.findall(".//Node")
nodes = []

# Filter matching nodes and put them in the nodes variable.
for node in all:
    for val in node.getchildren():
        if val.attrib['name'] == 'number' and val.getchildren()[0].text =='265456':
            nodes.append(node)

Ни одно из этих решений - это то, что я бы назвал идеальным, но они единственные, с которыми я смог работать с библиотекой ElementTree (так как вы упомянули об использовании). Возможно, вам лучше использовать стороннюю библиотеку, а не использовать встроенные; см. запись вики Python в XML для списка параметров. lxml - это привязки Python для широко используемой библиотеки libxml2, и я бы предложил сначала посмотреть. Он поддерживает XPath, поэтому вы должны иметь возможность использовать запросы из других ответов.

Ответ 3

В подобных случаях мне помогала следующая функция. Как объясняет docstring, это не работает в общем случае, но если ваши узлы уникальны, это должно помочь.

def get_element_ancestry(root, element):
'''Return a list of ancestor Elements for the given element.

If both root and element are of type xml.etree.ElementTree.Element, and if
the given root contains the given element as a descendent, then return a
list of direct xml.etree.ElementTree.Element ancestors, starting with root
and ending with element. Otherwise, return an empty list.

The xml.etree.ElementTree module offers no function to return the parent of
a given Element, presumably because an Element may be in more than one tree,
or even multiple times within a given tree, so its parent depends on the
context. This function provides a solution in the specific cases where the
caller either knows that the given element appears just once within the
tree or is satisfied with the first branch to reference the given element.
'''
result = []
xet = xml.etree.ElementTree
if not xet.iselement(root) or not xet.iselement(element):
    return result
xpath = './/' + element.tag \
    + ''.join(["[@%s='%s']" % a for a in element.items()])
parent = root
while parent != None:
    result.append(parent)
    for child in parent.findall('*'):
        if child == element:
            result.append(element)
            return result
        if child.findall(xpath).count(element):
            parent = child
            break
    else:
        return []
return result