Уведомления

Группа в Telegram: @pythonsu

#1 Авг. 1, 2015 17:53:32

Inok
Зарегистрирован: 2013-09-11
Сообщения: 41
Репутация: +  -1  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

Разбор XML при помощи lxml.
День добрый. Ищу примеры разбора XML примерно вот такой структуры.

<?xml version='1.0'?>
<?xml-stylesheet type="text/xsl" href="customers.xsl"?>
<customers>
   <customer>
      <name>John Smith</name>
      <address>123 Elm St.</address>
      <phone>(123) 456-7890</phone>
   </customer>
   <customer>
      <name>Mary Jones</name>
      <address>456 Oak Ave.</address>
      <phone>(156) 789-0123</phone>
   </customer>
</customers>

Вот к примеру как мне выцепить только (156) 789-0123, 1 номер телефона? Или наоборот все значение но только первого customer?
John Smith 
123 Elm St.
(123) 456-7890
вот так к примеру.
Все примеры в lxml идут по html или Atom, а тут самый простой XML и у меня затык

Офлайн

#2 Авг. 1, 2015 21:35:22

doza_and
От:
Зарегистрирован: 2010-08-15
Сообщения: 4138
Репутация: +  253  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

:):)
Я делаю так. Запускаю утилиту конвертации xml в yaml и дальше читаю yaml - это сразу объект питона…
Утилита ниже:

#!/usr/bin/env python
import yaml
from lxml import etree
import sys
import codecs
import re
def convertXml2Yaml(inFileName,outFileName):
    doc = etree.parse(inFileName)
    root = doc.getroot()
    if root.nsmap:
        res={}
        res["nsmap"]=root.nsmap
    # Convert the DOM tree into "YAML-able" data structures.
        nsi= dict([(v,k) for k,v in root.nsmap.iteritems()])
        res["root"]=convertXml2YamlAux(root,nsi)
    else:
        res=convertXml2YamlAux(root,{})
    # Ask YAML to dump the data structures to a string.
    with codecs.open(outFileName,"w",encoding="utf-8") as f:
        yaml.safe_dump(res,f,allow_unicode=True)
def reduce_name(name,nsi):
    fnd=re.match(ur"{([^}]+?)}(.+)",name)
    if fnd:
        if fnd.group(1) in nsi:
            return "{k}:{v}".format(k=nsi[fnd.group(1)],v=fnd.group(2))
        else:
            return "{k}:{v}".format(k=fnd.group(1),v=fnd.group(2))
    return name
u"""elements: [name(tag), attr, text,children]"""
def convertXml2YamlAux(obj,nsi):
    # Add the element name.
    nm=reduce_name(obj.tag,nsi)
    text=obj.text
    if text:
        text=text.strip()
    else:
        text=""
    attr=dict([(reduce_name(k,nsi),v) for k,v in obj.attrib.iteritems()])
    if text:
        attr["t"]=text
    childr = [convertXml2YamlAux(i,nsi) for i in obj.iterchildren()]
    res=[nm]
    if attr:
        res.append(attr) # attr is mapping
    if childr:
        res.append(childr) # children is list
    return res
def convertXml2YamlAux(obj,nsi):
    # Add the element name.
    nm=reduce_name(obj.tag,nsi)
    text=obj.text
    if text:
        text=text.strip()
    else:
        text=""
    attr=dict([(reduce_name(k,nsi),v) for k,v in obj.attrib.iteritems()])
    if text:
        attr["t"]=text
    childr = [convertXml2YamlAux(i,nsi) for i in obj.iterchildren()]
    res=[nm]
    if attr:
        res.append(attr) # attr is mapping
    if childr:
        res.append(childr) # children is list
    return res
def ld(fil):
    with codecs.open(fil,"r",encoding="utf-8") as f:
        x = yaml.load(f)
    return x
def sv(fil,data):
    with codecs.open(fil,"w",encoding="utf-8") as f:
        yaml.safe_dump(data,f,allow_unicode=True)
def main():
    convertXml2Yaml(sys.argv[1],sys.argv[2])
if __name__ == '__main__':
    main()

sv и ld для упрощения чтения yaml
обратите внимание - предполагается что все будет в utf-8



Отредактировано doza_and (Авг. 1, 2015 21:39:06)

Офлайн

#3 Авг. 2, 2015 00:30:25

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 10016
Репутация: +  857  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

>>> import lxml.etree
>>> 
>>> s = """<?xml version='1.0'?>
... <?xml-stylesheet type="text/xsl" href="customers.xsl"?>
... <customers>
...    <customer>
...       <name>John Smith</name>
...       <address>123 Elm St.</address>
...       <phone>(123) 456-7890</phone>
...    </customer>
...    <customer>
...       <name>Mary Jones</name>
...       <address>456 Oak Ave.</address>
...       <phone>(156) 789-0123</phone>
...    </customer>
... </customers>
... """
>>> 
>>> doc = lxml.etree.fromstring(s)
>>> node = doc[0]
>>> t = tuple(n.text for n in node)
>>> t
('John Smith', '123 Elm St.', '(123) 456-7890')
>>>



Офлайн

#4 Авг. 2, 2015 08:51:47

Inok
Зарегистрирован: 2013-09-11
Сообщения: 41
Репутация: +  -1  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

py.user.next
yaml
В этом случае мы полочим весь блок целиком а как получить только отдельную часть ? к примеру только (156) 789-0123
doza_and
Я делаю так. Запускаю утилиту конвертации xml в yaml и дальше читаю yaml - это сразу объект питона…Утилита ниже:
этот вариант так же красив, но хотелось бы чем проще тем лучше…

Офлайн

#5 Авг. 2, 2015 09:25:43

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 10016
Репутация: +  857  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

Inok
а как получить только отдельную часть ? к примеру только (156) 789-0123
>>> doc[1][2].text
'(156) 789-0123'
>>>



Отредактировано py.user.next (Авг. 2, 2015 09:26:34)

Офлайн

#6 Авг. 2, 2015 10:44:33

Inok
Зарегистрирован: 2013-09-11
Сообщения: 41
Репутация: +  -1  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

py.user.next
Хороший вариант. А если структура файла сложнее?
<?xml version='1.0'?>
<?xml-stylesheet type="text/xsl" href="customers.xsl"?>
<customers>
	<customer>
		<name>John Smith</name>
		<address>123 Elm St.</address>
		<phone>(123) 456-7890</phone>
	</customer>
	<customer>
		<name>Mary Jones</name>
		<address>456 Oak Ave.</address>
		<phone>(156) 789-0123</phone>
	</customer>
</customers>
<bunish>
	<bunish>
		<name>John Smith1</name>
		<address>123 Elm St.</address>
		<phone>(123) 456-7890</phone>
	</bunish>
	<bunish>
		<name>Mary Jones1</name>
		<address>456 Oak Ave.</address>
		<phone>(156) 789-0123</phone>
	</bunish>
</bunish>
2 дерева и оба на верхнем уровне. тут подобный подход не пройдет…
И есть возможность обращаться по пути как в XTML
bunish/bunish/name

Офлайн

#7 Авг. 2, 2015 11:23:12

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 10016
Репутация: +  857  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

Inok
2 дерева и оба на верхнем уровне
В xml-документе только один корневой элемент.

Inok
А если структура файла сложнее?
Точно так же через индексирование (взятие дочернего элемента) можно дойти до любого элемента.



Офлайн

#8 Авг. 3, 2015 14:49:38

Constverum
Зарегистрирован: 2015-08-03
Сообщения: 2
Репутация: +  0  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

from lxml import etree
# Если парсим крупный xml-файл
def parse_xmlFile(pathToXmlFile):
    customers = []
    with open(pathToXmlFile, mode='rb') as f:
        context = etree.iterparse(f, tag='customer')
        for _, cNode in context:
            customers.append({el.tag: el.text
                             for el in cNode.getchildren()})
            # Чистим память
            cNode.clear()
            while cNode.getprevious() is not None:
                del cNode.getparent()[0]
        del context
    return customers
print(parse_xmlFile('./test.xml'))
# Простой вариант
def parse_customers(xml):
    customers = []
    for cNode in xml.getchildren():
        customers.append({el.tag: el.text
                          for el in cNode.getchildren()})
    return customers
xml = '''<?xml version='1.0'?>
<?xml-stylesheet type="text/xsl" href="customers.xsl"?>
<customers>
   <customer>
      <name>John Smith</name>
      <address>123 Elm St.</address>
      <phone>(123) 456-7890</phone>
   </customer>
   <customer>
      <name>Mary Jones</name>
      <address>456 Oak Ave.</address>
      <phone>(156) 789-0123</phone>
   </customer>
</customers>
'''
print(parse_customers(etree.fromstring(xml)))
# Обе функции вернут одинаковый результат:
# [{'name': 'John Smith', 'address': '123 Elm St.', 'phone': '(123) 456-7890'},
#  {'name': 'Mary Jones', 'address': '456 Oak Ave.', 'phone': '(156) 789-0123'}]

Inok
2 дерева и оба на верхнем уровне. тут подобный подход не пройдет…
Разве такое возможно? На верхнем уровне может быть только один элемент.

Офлайн

#9 Авг. 3, 2015 15:41:56

Iskatel
Зарегистрирован: 2015-07-29
Сообщения: 291
Репутация: +  3  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

А еще есть много реализаций xmltodict (xml сразу в словарь) например https://github.com/martinblech/xmltodict

Отредактировано Iskatel (Авг. 3, 2015 15:42:34)

Офлайн

#10 Авг. 3, 2015 21:02:36

doza_and
От:
Зарегистрирован: 2010-08-15
Сообщения: 4138
Репутация: +  253  -
Профиль   Отправить e-mail  

Разбор XML при помощи lxml.

Iskatel
А еще есть много реализаций xmltodict

Тогда доводя до логического конца получим код
from xmltodict import parse
import dpath.util as du
dkt = parse(open("a.xml","r"))
print du.values(dkt, 'customers/customer/*/phone')
>>> 
[u'(123) 456-7890', u'(156) 789-0123']
print du.values(dkt, 'customers/customer/0/*')
>>> 
[u'(123) 456-7890', u'John Smith', u'123 Elm St.']

Документ со структурой второго типа правда xmltodict не жрет. :(



Отредактировано doza_and (Авг. 3, 2015 21:14:23)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version