Разбор пустых тегов XML с помощью LXML и Python - PullRequest
0 голосов
/ 08 марта 2012

При разборе XML-документов в формате:

<Car>
    <Color>Blue</Color>
    <Make>Chevy</Make>
    <Model>Camaro</Model>
</Car>

Я использую следующий код:

carData = element.xpath('//Root/Foo/Bar/Car/node()[text()]')
parsedCarData = [{field.tag: field.text for field in carData} for action in carData]
print parsedCarData[0]['Color'] #Blue

Этот код не будет работать, если тег пуст, например:

<Car>
    <Color>Blue</Color>
    <Make>Chevy</Make>
    <Model/>
</Car>

Используя тот же код, что и выше:

carData = element.xpath('//Root/Foo/Bar/Car/node()[text()]')
parsedCarData = [{field.tag: field.text for field in carData} for action in carData]
print parsedCarData[0]['Model'] #Key Error

Как бы я проанализировал этот пустой тег.

Ответы [ 4 ]

3 голосов
/ 08 марта 2012

Вы вставляете фильтр [text()], который явно запрашивает только элементы, у которых есть текстовые узлы ... и тогда вы недовольны, когда он не дает вам элементы без текстовых узлов?

Оставьте этот фильтр, и вы получите элемент модели:

>>> s='''
... <root>
...   <Car>
...     <Color>Blue</Color>
...     <Make>Chevy</Make>
...     <Model/>
...   </Car>
... </root>'''
>>> e = lxml.etree.fromstring(s)
>>> carData = e.xpath('Car/node()')
>>> carData
[<Element Color at 0x23a5460>, <Element Make at 0x23a54b0>, <Element Model at 0x23a5500>]
>>> dict(((e.tag, e.text) for e in carData))
{'Color': 'Blue', 'Make': 'Chevy', 'Model': None}

Тем не менее, если вашей непосредственной целью является итерация по узлам в дереве, вы можете вместо этого использовать lxml.etree.iterparse(), что позволит избежать попыток построить полное дерево DOM в памяти и в противном случае будет гораздо более эффективным, чем создание дерево, а затем перебирать его с помощью XPath. (Подумайте SAX, но без безумного и болезненного API).

Реализация с iterparse может выглядеть так:

def get_cars(infile):
    in_car = False
    current_car = {}
    for (event, element) in lxml.etree.iterparse(infile, events=('start', 'end')):
        if event == 'start':
            if element.tag == 'Car':
                in_car = True
                current_car = {}
            continue
        if not in_car: continue
        if element.tag == 'Car':
            yield current_car
            continue
        current_car[element.tag] = element.text

for car in get_cars(infile = cStringIO.StringIO('''<root><Car><Color>Blue</Color><Make>Chevy</Make><Model/></Car></root>''')):
  print car

... это больше кода, но (если бы мы не использовали StringIO для примера), он мог бы обработать файл намного больше, чем мог поместиться в памяти.

1 голос
/ 08 марта 2012

Я не знаю, есть ли лучшее решение, встроенное в lxml, но вы можете просто использовать .get():

print parsedCarData[0].get('Model', '')
0 голосов
/ 08 марта 2012

Я бы поймал исключение:

try:
    print parsedCarData[0]['Model']
except KeyError:
    print 'No model specified'

Исключения в Python не являются исключительными в том же смысле, что и в других языках, где они более строго связаны с ошибочными условиями. Вместо этого они часто являются частью нормального использования модулей, в соответствии с дизайном. Например, итератор вызывает StopIteration, чтобы указать, что он достиг конца итерации.

Редактировать: Если вы уверены, что только этот элемент может быть пустым, @CharlesDuffy прав в том, что лучше использовать get() Но в целом я хотел бы рассмотреть возможность использования исключений для простой обработки разнообразных исключительных выходных данных.

0 голосов
/ 08 марта 2012

Решение: используйте блок try/except, чтобы поймать ошибку ключа.

...