XML-Problem: XML zu CSV

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
BlackJack

Der Sinn ist Rückwärtskompatibilität. Das ``as`` an der Stelle ist relativ neu.

Eine `expression` kann auch ein Tupel ergeben. Die Forderung nach den Klammern kommen, zumindest informell, daher, dass man bei einem Tupel die Klammern braucht, falls es sonst zu Mehrdeutigkeiten kommen kann. Das ist ja hier der Fall wenn man ein ``,`` statt dem ``as`` verwendet.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

ok, klar. in python 3 ist es ja auch eindeutig ("," statt "as" ist nichtmehr erlaubt)
http://www.kinderpornos.info
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Dill hat geschrieben:ok, klar. in python 3 ist es ja auch eindeutig ("," statt "as" ist nichtmehr erlaubt)
Und ``as`` ist sowieso erst ab Python 2.6 möglich.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
problembär

Hallo,

hier mein Vorschlag:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: iso-8859-1 -*-

import xml.dom.minidom

def getUglyString(fname): 

    fh = file(fname, "r")
    a = fh.readlines()
    fh.close()

    b = ""

    for i in a: 
        i = i.strip()
        b += i

    return b

dom = xml.dom.minidom.parseString(getUglyString("file.xml"))

lines = []

def getLine(merkmalnode):

    line = merkmalnode.attributes["ID"].nodeValue

    for node in merkmalnode.childNodes:

        if node.nodeType == node.ELEMENT_NODE and node.nodeName == "Name":
            for u in node.childNodes:
                if u.nodeType == u.TEXT_NODE:
                    line += ";" + u.nodeValue.strip()

        if node.nodeType == node.ELEMENT_NODE and node.nodeName == "AddData":
            for u in node.childNodes:

                if u.nodeType == node.ELEMENT_NODE and u.nodeName == "Wert" and u.attributes["MerkmalID"].nodeValue == "Kurzbezeichnung":
                    for u2 in u.childNodes:
                        if u2.nodeType == u.TEXT_NODE:
                            line += ";" + u2.nodeValue.strip()

                if u.nodeType == node.ELEMENT_NODE and u.nodeName == "Wert" and u.attributes["MerkmalID"].nodeValue == "Beschreibung":
                    for u2 in u.childNodes:
                        if u2.nodeType == u.TEXT_NODE:
                            line += ";" + u2.nodeValue.strip()
    return line

def walkOver(domina):

    for node in domina.childNodes:

        if node.nodeType == node.ELEMENT_NODE and node.nodeName == "Merkmal" and node.attributes.has_key("ID"):

            lines.append(getLine(node))

        walkOver(node)

walkOver(dom)

for i in lines:
    print i
Geht das?

Gruß
BlackJack

Also rein aus ästhetischen Gründen und aus "pythonischer" Sicht geht das ja mal gar nicht. Pfui, ist das *hässlich*!

Edit: So sieht das IMHO etwas hübscher aus:

Code: Alles auswählen

import csv
import sys
from lxml import etree


def parse(filename):
    root = etree.parse(filename)
    xpath_template = 'AddData/Wert[@MerkmalID="%s"]/text()'
    short_description = xpath_template % 'Kurzbezeichnung'
    description = xpath_template % 'Beschreibung'
    for merkmal in root.xpath('//Merkmal'):
        yield [merkmal.attrib['ID'],
               merkmal.find('Name').text,
               merkmal.xpath(short_description)[0],
               merkmal.xpath(description)[0]]


def main():
    writer = csv.writer(sys.stdout, delimiter=';')
    writer.writerows(parse('test.xml'))
Zuletzt geändert von BlackJack am Freitag 22. Mai 2009, 20:34, insgesamt 1-mal geändert.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Na wobei `walkOver(domina)` zumindest Humor zeigt :)
`minidom` statt `ElementTree` oder `lxml` zu benutzen zeigt auch von masochistischen Ausprägungen ;)
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

blackjack, hast du eigentlich einen generator fetisch? :)

hier eine version mit xpath, lxml.objectify und csv.DictWriter

Code: Alles auswählen

import csv
from lxml import objectify

def main(xml_file, csv_file):

    tree = objectify.parse(xml_file)

    writer = csv.DictWriter(csv_file, ("ID", "Name", "Kurzbezeichnung", "Beschreibung", "Noch_ein_Wert"))

    for m in tree.xpath("//Merkmal"):
        row = dict()
        row.update({"ID": m.get("ID")})
        row.update({"Name": m.Name})
        for wert in m.AddData.xpath("Wert"):
            row.update({wert.get("MerkmalID") : wert})
        writer.writerow(row)


if __name__ == "__main__":
    
    import StringIO
    
    xml_file = StringIO.StringIO("""
        <root>
            <Merkmal ID="34747"> 
                <Name>Volumen</Name> 
                <AddData> 
                    <Wert MerkmalID="Kurzbezeichnung">Volumen</Wert> 
                    <Wert MerkmalID="Beschreibung">= Inhalt einer Verpackung in Liter</Wert> 
                </AddData> 
            </Merkmal>
            <Merkmal ID="4711"> 
                <Name>Gewicht</Name> 
                <AddData> 
                    <Wert MerkmalID="Kurzbezeichnung">Gewicht</Wert> 
                    <Wert MerkmalID="Beschreibung">= Inhalt einer Verpackung in kg</Wert> 
                    <Wert MerkmalID="Noch_ein_Wert">Hallo, Welt!</Wert> 
                </AddData> 
            </Merkmal>
        </root>""")
    
    csv_file = StringIO.StringIO()
    
    main(xml_file, csv_file)
    
    print csv_file.getvalue()

Code: Alles auswählen

34747,Volumen,Volumen,= Inhalt einer Verpackung in Liter,
4711,Gewicht,Gewicht,= Inhalt einer Verpackung in kg,"Hallo, Welt!"
http://www.kinderpornos.info
BlackJack

@Dill: Ich denke halt gerne in Datenströmen und Funktionen die welche erzeugen, verändern, oder akkumulieren. Da bieten sich Generatorfunktionen und die `itertools` einfach an, um daraus flexibel Lösungen zusammen zu stecken.

Bei mir ist zum Beispiel die Datenquelle sauber von der Senke getrennt. Die Daten könnten genauso gut aus einer Funktion kommen die eine Datenbank befragt, also auch in eine Funktion gesteckt werden, die die Daten in eine Datenbank schreibt. Oder als JSON schreibt, oder…

Und man könnte auch noch Filterfunktionen dazwischen schalten wenn man möchte.
problembär

:lol:

Der Name "walkOver()" war zwar meine Idee (in Anlehnung an die "TreeWalker"-Klasse).
Das mit (der) "domina" aber nicht; das kommt von hier:

http://de.wikibooks.org/wiki/Python_unter_Linux:_XML

unter "XML lesen".

Gruß
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

@blackjack: dein code ist wirklich sehr sexy. ich werde mal drauf achten in zukunft wo sich generatoren anbieten. trotzdem finde ich meinen code hier einfach lesbarer. (was hautpsächlich an DictWriter liegt, dieser verhindert aber auch eine sinnvolle die trennenung von writer und parser und macht daher ein yield(row) weniger sinnig)

wie wärs damit: (das geht sicher auch klarer??)

Code: Alles auswählen

 l = [list(chain(*[
	    (m.get("ID"), m.Name), [wert for wert in m.AddData.xpath("Wert")]
	    	      ])) for m in tree.xpath("//Merkmal")]
http://www.kinderpornos.info
PhantomWorks
User
Beiträge: 18
Registriert: Samstag 25. April 2009, 11:11

Hallo!

Vielen Dank für Eure Antworten! Ich hatte vergangenes Wochenende ZEit und bin kurz vor der Lösung.

Es gibt nur noch ein einziges Problem:

Wenn ich die Elemente der CSV-Datei die ich in Variablen gespeichert habe mit dem CSV-Writer über writerow schreiben will passiert folgendes: Es steht in jedem Feld nur ein einziger Buchstabe, d.h. ein Wort mit 10 bestehend aus 10 Buchstaben erstreckt sich momentan über 10 Spalten.

Habe schon hin und her probiert, bin jedoch noch auf keinen grünen Zweig gekommen.

Hat einer Rat an was das liegen kann?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Dann solltest du mal den Code zeigen, der die CSV-Datei schreibt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich rate mal: du übergibst die Zeile nicht als Tupel sondern irgendwie als String?
Das Leben ist wie ein Tennisball.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

strings sind tupel von zeichen, daher das seltsame verhalten...

Code: Alles auswählen

In [23]: w.writerow("hallo")

In [24]: w.writerow( ("hallo",) )

In [25]: print o.getvalue()
h,a,l,l,o
hallo
http://www.kinderpornos.info
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Du meinst, Strings sind Sequenzen aus Strings ;)
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

wenn schon, dann (immutable) sequenz von chars und escapeseqs (mit evtl. prefix)

Code: Alles auswählen

stringliteral   ::=  [stringprefix](shortstring | longstring)
stringprefix    ::=  "r" | "u" | "ur" | "R" | "U" | "UR" | "Ur" | "uR"
shortstring     ::=  "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring      ::=  "'''" longstringitem* "'''"
                     | '"""' longstringitem* '"""'
shortstringitem ::=  shortstringchar | escapeseq
longstringitem  ::=  longstringchar | escapeseq
shortstringchar ::=  <any source character except "\" or newline or the quote>
longstringchar  ::=  <any source character except "\">
escapeseq       ::=  "\" <any ASCII character>
du hast angefangen :P
http://www.kinderpornos.info
BlackJack

@Dill: Nein, denn es gibt in Python keine `char`\s [1]_. Die Beschreibung von Zeichenkettenliteralen im Quelltext und die Eigenschaften von `str`-Objekten sind zwei verschiedene paar Schuhe.

Zeichenketten sind eine ("immutable") Sequenz von Zeichenketten der Länge 1, ist denke ich eine treffende Beschreibung, die das Phänomen erklärt.

.. [1] Jaja, ich weiss, es gibt `ctypes.c_char`.
problembär

PhantomWorks hat geschrieben:Vielen Dank für Eure Antworten! Ich hatte vergangenes Wochenende ZEit und bin kurz vor der Lösung.
Schön. Und wie jetzt, mit minidom oder lxml/etree?
(Bei letzterem bin ich raus.)

Gruß
PhantomWorks
User
Beiträge: 18
Registriert: Samstag 25. April 2009, 11:11

@problembär: Ich verwende jetzt lxml/etree. Ich habe zwar auch einen Versuch mit minidom gestartet, diesen jedoch bald wieder verworfen.

@all:
Ich habe mir nun die csv.DictWriter Funktion angesehen
csv.DictWriter(csvfile, fieldnames[, restval=''[, extrasaction='raise'[, dialect='excel'[, *args, **kwds]]]])
als auch das was dahinter steckt

Code: Alles auswählen

class DictWriter:
    def __init__(self, f, fieldnames, restval="", extrasaction="raise",
                 dialect="excel", *args, **kwds):
        self.fieldnames = fieldnames    # list of keys for the dict
        self.restval = restval          # for writing short dicts
        if extrasaction.lower() not in ("raise", "ignore"):
            raise ValueError, \
                  ("extrasaction (%s) must be 'raise' or 'ignore'" %
                   extrasaction)
        self.extrasaction = extrasaction
        self.writer = writer(f, dialect, *args, **kwds)
        self.writer.writerow(fieldnames)

    def _dict_to_list(self, rowdict):
        if self.extrasaction == "raise":
            for k in rowdict.keys():
                if k not in self.fieldnames:
                    raise ValueError, "dict contains fields not in fieldnames"
        return [rowdict.get(key, self.restval) for key in self.fieldnames]

    def writerow(self, rowdict):
        return self.writer.writerow(self._dict_to_list(rowdict))

    def writerows(self, rowdicts):
        rows = []
        for rowdict in rowdicts:
            rows.append(self._dict_to_list(rowdict))
        return self.writer.writerows(rows)
Mein Code:

Code: Alles auswählen

fieldnames=['A1','A2', 'A3', 'A4', 'A5']
csv.DictWriter((csv_file, 'w'), fieldnames, restval="", extrasaction="raise", dialect="excel", *args, **kwds)
Ich bekomme folgenden Error:
File "C:\Python26\lib\csv.py", line 133, in __init__
self.writer = writer(f, dialect, *args, **kwds)
TypeError: argument 1 must have a "write" method
Wozu brauche ich dieses *args, **kwds? ich habe schon hier im Forum danach gesucht und auch etwas gefunden doch meiner Meinung nach ist dieses für meinen Anwendungsfall nicht notwendig.

Ich habe inzwischen eine Liste erzeugt aus allen Merkmalen, wobei jedes Merkmal (eindeutig durch die ID identifiziert) ein Dictionary darstellt. Ziel ist es nun die Dictionaries anhand der Fieldnames die ich mit den Schlüsseln des Dicts gemappt habe (also z.B. entspricht der Schlüssel Merkmal ID --> A1, Name --> A2 usw.), zeilenweise in die CSV zu schreiben, sodass letztendlich jedes einzelne Dict der Liste eine Zeile der CSV darstellt.

Viele Grüße
BlackJack

@PhantomWorks: In der Doku zu csv.DictWriter kannst Du nachlesen, dass als erstes Argument eine Datei ewartet wird. So wie's aussieht übergibst Du aber ein Tupel mit, keine Ahnung was `csv_file` ist, und einer Zeichenkette. Und so ein Tupel hat im Gegensatz zu einer Datei keine `write()`-Methode.

Wieso übergibst Du die ganzen Argumente mit den gleichen Werten wie sie sowieso schon vorbelegt sind? Was ist an ``*args`` und ``**kwds`` gebunden und *warum*?

Vielleicht magst Du mal das Tutorial zum Thema “Defining functions“ durcharbeiten.
Antworten