2017-07-12 1 views
1

Je dispose d'un fichier xml avec cette structure:Python lire xml avec des éléments enfants connexes

<?DOMParser ?> 
<logbook:LogBook xmlns:logbook="http://www/logbook/1.0" version="1.2"> 
<product> 
    <serialNumber value="764000606"/> 
</product> 
<visits> 
<visit> 
    <general> 
     <startDateTime>2014-01-10T12:22:39.166Z</startDateTime> 
     <endDateTime>2014-03-11T13:51:31.480Z</endDateTime> 
    </general> 
    <parts> 
     <part number="03081" name="WSSA" index="0016"/> 
    </parts> 
</visit> 
<visit> 
<general> 
    <startDateTime>2013-01-10T12:22:39.166Z</startDateTime> 
    <endDateTime>2013-03-11T13:51:31.480Z</endDateTime> 
</general> 
<parts> 
    <part number="02081" name="PSSF" index="0017"/> 
</parts> 
</visit> 
</visits> 
</logbook:LogBook> 

Je veux avoir deux sorties de ce xml:

1 visite, y compris le numéro de série, donc je a écrit:

import pandas as pd 
import xml.etree.ElementTree as ET 
tree = ET.parse(filename) 
root=tree.getroot() 
visits=pd.DataFrame() 
for general in root.iter('general'): 
    for child in root.iter('serialNumber'): 
     visits=visits.append({'startDateTime':general.find('startDateTime').text , 
        'endDateTime': general.find('endDateTime').text, 'serialNumber':child.attrib['value'] }, ignore_index=True) 

La sortie de ce code suit dataframe:

serialNumber | startDateTime   | endDateTime    
-------------|------------------------|------------------------| 
764000606 |2014-01-10T12:22:39.166Z|2014-03-11T13:51:31.480Z| 
764000606 |2013-03-11T13:51:31.480Z|2013-01-10T12:22:39.166Z| 

2 pièces

Pour parts, je veux avoir la sortie suivante, d'une manière que je distingue des visites de l'autre par startDateTime et je veux montrer les parties liées à la chaque visite:

serialNumber | startDateTime|number|name|index| 
-------------|--------------|------|----|-----| 

pour les parties I écrit:

parts=pd.DataFrame() 
for part in root.iter('part'): 
    for child in root.iter('serialNumber'): 
      parts=parts.append({'index':part.attrib['index'], 
         'znumber':part.attrib['number'], 
         'name': part.attrib['name'], 'serialNumber':child.attrib['value'], 'startDateTime':general.find('startDateTime').text}, ignore_index=True) 

Voici ce que je reçois de ce code:

index |name|serialNumber| startDateTime   |znumber| 
------|----|------------|------------------------|-------| 
0016 |WSSA| 764000606 |2013-01-10T12:22:39.166Z| 03081 | 
0017 |PSSF| 764000606 |2013-01-10T12:22:39.166Z| 02081 | 

Alors que je veux ceci: regarder startDateTime:

index |name|serialNumber| startDateTime   |znumber| 
------|----|------------|------------------------|-------| 
0016 |WSSA| 764000606 |2014-01-10T12:22:39.166Z| 03081 | 
0017 |PSSF| 764000606 |2013-01-10T12:22:39.166Z| 02081 | 

Toute idée? J'utilise XML ElementTree

+0

La balise de terminaison "" ne devrait-elle pas se trouver à la fin du fichier? Parce que votre fichier _XML_ ne doit contenir que __one__ _root_ node. – CristiFati

+0

Est-ce que 'visits' est un dataframe pandas? – mzjn

+0

@mzjn yes visit = pandas.DataFrame() – Safariba

Répondre

2

est ici un morceau de code qui récupère les données de xml:

import xml.etree.ElementTree as ET 
from pprint import pprint as pp 


file_name = "a.xml" 


def get_product_sn(product_node): 
    for product_node_child in list(product_node): 
     if product_node_child.tag == "serialNumber": 
      return product_node_child.attrib.get("value", None) 
    return None 


def get_parts_data(parts_node): 
    ret = list() 
    for parts_node_child in list(parts_node): 
     attrs = parts_node_child.attrib 
     ret.append({"number": attrs.get("number", None), "name": attrs.get("name", None), "index": attrs.get("index", None)}) 
    return ret 


def get_visit_node_data(visit_node): 
    ret = dict() 
    for visit_node_child in list(visit_node): 
     if visit_node_child.tag == "general": 
      for general_node_child in list(visit_node_child): 
       if general_node_child.tag == "startDateTime": 
        ret["startDateTime"] = general_node_child.text 
       elif general_node_child.tag == "endDateTime": 
        ret["endDateTime"] = general_node_child.text 
     elif visit_node_child.tag == "parts": 
      ret["parts"] = get_parts_data(visit_node_child) 
    return ret 


def get_node_data(node): 
    ret = {"visits": list()} 
    for node_child in list(node): 
     if node_child.tag == "product": 
      ret["serialNumber"] = get_product_sn(node_child) 
     elif node_child.tag == "visits": 
      for visits_node_child in list(node_child): 
       ret["visits"].append(get_visit_node_data(visits_node_child)) 
    return ret 


def main(): 
    tree = ET.parse(file_name) 
    root_node = tree.getroot() 
    data = get_node_data(root_node) 
    pp(data) 


if __name__ == "__main__": 
    main() 

Remarques:

  • Il traite la xml d'une manière semblable à l'arbre, il cartes (si vous voulez) sur le xml (si le xml changements de structure, le code devrait être ad Apted ainsi)
  • Il est conçu pour être général: get_node_data pourrait être appelé sur un nœud qui a 2 enfants: < produit> et < visites>.Dans notre cas c'est le nœud racine lui-même, mais dans le monde réel il pourrait y avoir une séquence de tels nœuds avec les 2 enfants que j'ai listés ci-dessus
  • Il est conçu pour être facile à utiliser, donc si le xml est incomplet, il obtiendra autant de données qu'il le peut; J'ai choisi cette approche (gourmande) sur celui qui lorsqu'il rencontre une erreur, il lance simplement une exception
  • Comme je ne travaille avec pandas, au lieu de peupler l'objet je retourne simplement un Python dictionnaire (json); Je pense convertir en un DataFrame ne devrait pas être difficile
  • J'ai couru avec python2.7 et Python3.5

La sortie (un dictionnaire contenant 2 clés) - pour indenté lisibilité:

  • serialNumber - le numéro de série (évidemment)
  • visites (car il est un dictionnaire, je devais placer ces données « sous » une clé) - une liste de dictionnaires chacun contenant des données à partir d'une < visite> noeud
{'serialNumber': '764000606', 
'visits': [{'endDateTime': '2014-03-11T13:51:31.480Z', 
      'parts': [{'index': '0016', 'name': 'WSSA', 'number': '03081'}], 
      'startDateTime': '2014-01-10T12:22:39.166Z'}, 
      {'endDateTime': '2013-03-11T13:51:31.480Z', 
      'parts': [{'index': '0017', 'name': 'PSSF', 'number': '02081'}], 
      'startDateTime': '2013-01-10T12:22:39.166Z'}]} 

EDIT0: ajouté plusieurs partie la gestion des nœuds comme demandé dans l'un des commentaires. Cette fonctionnalité a été déplacée vers get_parts_data. Maintenant, chaque entrée dans la liste visites aura une parties clé dont la valeur sera une liste constituée de dictionnaires extraites de chaque noeud partie (pas le cas pour le fourni xml).

+1

Dans ce code, lorsqu'il y a plus d'une partie pour chaque visite, seule la dernière partie est renvoyée. Il ne retourne pas toutes les pièces pour chaque visite. – Safariba

+2

C'est vrai. Je pensais qu'il ne pouvait y avoir qu'un seul nœud _part_ par _visit_ (comme dans l'exemple _xml_). Voulez-vous gérer plusieurs nœuds _part_? (les changements sont triviaux) – CristiFati

+1

oui je veux gérer plusieurs parties, je suis moins expérimenté dans la manipulation des dictionnaires, pouvez-vous m'aider avec ça? Merci. – Safariba

0

essayez ce qui suit,

import xml.dom.minidom as minidom 
doc = minidom.parse('filename') 
memoryElem = doc.getElementsByTagName('part')[0] 

print memoryElem.getAttribute('number') 
print memoryElem.getAttribute('name') 
print memoryElem.getAttribute('index') 

espère que cela aidera u.