Форум сайта python.su
-1
Разбор 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>
John Smith
123 Elm St.
(123) 456-7890
Офлайн
253
:):)
Я делаю так. Запускаю утилиту конвертации 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()
Отредактировано doza_and (Авг. 1, 2015 21:39:06)
Офлайн
857
>>> 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') >>>
Офлайн
-1
py.user.nextВ этом случае мы полочим весь блок целиком а как получить только отдельную часть ? к примеру только (156) 789-0123
yaml
doza_andэтот вариант так же красив, но хотелось бы чем проще тем лучше…Я делаю так. Запускаю утилиту конвертации xml в yaml и дальше читаю yaml - это сразу объект питона…Утилита ниже:
Офлайн
857
Inok
а как получить только отдельную часть ? к примеру только (156) 789-0123
>>> doc[1][2].text '(156) 789-0123' >>>
Отредактировано py.user.next (Авг. 2, 2015 09:26:34)
Офлайн
-1
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>
bunish/bunish/name
Офлайн
857
InokВ xml-документе только один корневой элемент.
2 дерева и оба на верхнем уровне
InokТочно так же через индексирование (взятие дочернего элемента) можно дойти до любого элемента.
А если структура файла сложнее?
Офлайн
0
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 дерева и оба на верхнем уровне. тут подобный подход не пройдет…
Офлайн
3
А еще есть много реализаций xmltodict (xml сразу в словарь) например https://github.com/martinblech/xmltodict
Отредактировано Iskatel (Авг. 3, 2015 15:42:34)
Офлайн
253
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.']
Отредактировано doza_and (Авг. 3, 2015 21:14:23)
Офлайн