Удаление XML элементов на основе значения атрибута с использованием Python дерева элементов - PullRequest
0 голосов
/ 25 января 2020

Я работал с этим большую часть дня, но не смог найти решение. У меня довольно большой XML файл, который мне нужен для удаления некоторых данных. «Поля» аннотируются с использованием атрибутов Id (имя поля) и Num (уникальный номер для имени поля).

<?xml version="1.0" encoding="UTF-8"?>
<Data>
    <Item Id="Type" Num="30">3</Item>
    <Item Id="Version" Num="50">180</Item>
    <Owner>
        <Item Id="IdNumber" Num="20">00000000</Item>
        <Item Id="race1" Num="160">01</Item>
        <Item Id="race2" Num="161">88</Item>
        <Item Id="race3" Num="162">88</Item>
        <Dog>
            <Item Id="Breed" Num="77">Mutt</Item>
            <Item Id="Weight" Num="88">88</Item>
        </Dog>
        <Dog>
            <Item Id="Breed" Num="77">Retriever</Item>
            <Item Id="Weight" Num="88">77</Item>
        </Dog>
    </Owner>
    <Owner>
        <Item Id="IdNumber" Num="20">00033000</Item>
        <Item Id="race1" Num="160">03</Item>
        <Item Id="race2" Num="161">88</Item>
        <Item Id="race3" Num="162">88</Item>
        <Dog>
            <Item Id="Breed" Num="77">Poodle</Item>
            <Item Id="Weight" Num="88">21</Item>
        </Dog>
    </Owner>
</Data>

Вот довольно простой код python, который, как я предполагал, сработает, но он удалит данные, как и ожидалось.

#!/usr/bin/env python3

import xml.etree.ElementTree as ET

# list of "Nums" to drop from each Owner and Dog
drops = ['160', '161', '162', '88']

# read in XML file
with open('dogowners.xml') as xmlin:
    tree = ET.parse(xmlin)
    root = tree.getroot()
    for x in root:
        for y in x:
            # checking to make sure it's an Owner
            if y.attrib:
                # if the value for attribute Num is in the list of drops then remove it
                if y.attrib["Num"] in drops:
                    x.remove(y)

# finally output new tree
tree.write('output.xml')

output. xml

Проблема, с которой я сталкиваюсь, заключается в том, что она не удаляет все перечисленные капли / числа. В случае с этим маленьким XML, он только делает первое и третье значение на уровне владельца, что согласуется с моим большим файлом, потому что он, кажется, удаляет только один другой.

<Data>
    <Item Id="Type" Num="30">3</Item>
    <Item Id="Version" Num="50">180</Item>
    <Owner>
        <Item Id="IdNumber" Num="20">00000000</Item>
        <Item Id="race2" Num="161">88</Item>
        <Dog>
            <Item Id="Breed" Num="77">Mutt</Item>
            <Item Id="Weight" Num="88">88</Item>
        </Dog>
        <Dog>
            <Item Id="Breed" Num="77">Retriever</Item>
            <Item Id="Weight" Num="88">77</Item>
        </Dog>
    </Owner>
    <Owner>
        <Item Id="IdNumber" Num="20">00033000</Item>
        <Item Id="race2" Num="161">88</Item>
        <Dog>
            <Item Id="Breed" Num="77">Poodle</Item>
            <Item Id="Weight" Num="88">21</Item>
        </Dog>
    </Owner>
</Data>

Я чувствую, что могу упустить что-то вроде очевидного, но разбор XML не моя сильная сторона, и я боролся с этим пару часов. Любая помощь очень ценится.

1 Ответ

1 голос
/ 25 января 2020

Это кажется хорошим кандидатом на шаблон преобразования идентификаторов. Следующее скопирует документ xml, но исключит элементы Item, которые соответствуют пустому шаблону в конце строки xsl.

owner-dog.py

#!/usr/bin/env python3

from lxml import etree

# list of "Nums" to drop from each Owner and Dog
drops = ('160', '161', '162', '88')

# we turn it into an xsl attribute pattern:
#    @Num = '160' or @Num = '161' or @Num = '162' or @Num = '88'
attr_vals = list(map(lambda n: f'@Num = \'{n}\'', drops))
attr_expr = ' or '.join(attr_vals)

xsl = etree.XML('''<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent='yes'/>
    <xsl:strip-space elements="*" />

<!-- copy all nodes ... -->
<xsl:template match="@*|node()">
    <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<!-- ... except for any elements matching the following template -->
<xsl:template match="//Item[ {attr_vals} ]" />
</xsl:stylesheet>'''.format(attr_vals=attr_expr))
transform = etree.XSLT(xsl)

with open('owner-dog.xml') as xml:
    print(transform(etree.parse(xml)))

Вывод

<?xml version="1.0"?>
<Data>
  <Item Id="Type" Num="30">3</Item>
  <Item Id="Version" Num="50">180</Item>
  <Owner>
    <Item Id="IdNumber" Num="20">00000000</Item>
    <Dog>
      <Item Id="Breed" Num="77">Mutt</Item>
    </Dog>
    <Dog>
      <Item Id="Breed" Num="77">Retriever</Item>
    </Dog>
  </Owner>
  <Owner>
    <Item Id="IdNumber" Num="20">00033000</Item>
    <Dog>
      <Item Id="Breed" Num="77">Poodle</Item>
    </Dog>
  </Owner>
</Data>

Сравнение оригинала xml с выводом

diff <(xmllint --format owner-dog.xml) <(./owner-dog.py)
1c1
< <?xml version="1.0" encoding="UTF-8"?>
---
> <?xml version="1.0"?>
7,9d6
<     <Item Id="race1" Num="160">01</Item>
<     <Item Id="race2" Num="161">88</Item>
<     <Item Id="race3" Num="162">88</Item>
12d8
<       <Item Id="Weight" Num="88">88</Item>
16d11
<       <Item Id="Weight" Num="88">77</Item>
21,23d15
<     <Item Id="race1" Num="160">03</Item>
<     <Item Id="race2" Num="161">88</Item>
<     <Item Id="race3" Num="162">88</Item>
26d17
<       <Item Id="Weight" Num="88">21</Item>
29a21
>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...