Eigene "config" Dateien gescheid parsen.

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.
Antworten
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Edit (Leonidas): Die Diskussion in den Thread "XML/DTD: Kontextfreiheit?" getrennt.

Angenommen, ihr wollt eine xml-ähnliche Struktur durchparsen, die ungefähr so aussieht:

Code: Alles auswählen

<item>
<metadata>
        id="int"
        creation_time="str|$(date -u)|keywords"
        expiration_time="str|$(date -u)|keywords"
        starting_time="str|$(date -u)|keywords"
        priority="high|medium|low"
        comment="str"
        assignies="str"
        title=""
        custom_key1=""
        custom_key2=""
        ...
</metadata>
message1
</item>
junk
<item>
<metadata>
        id="int"
        creation_time="str|$(date -u)|keywords"
        expiration_time="str|$(date -u)|keywords"
        starting_time="str|$(date -u)|keywords"
        priority="high|medium|low"
        comment="str"
        assignies="str"
        title=""
        custom_key1=""
        custom_key2=""
        ...
</metadata>
message2
</item>
Ihr wollt das so parsen, dann die einzelnen items isoliert werden können, damit später ihre optionale metadaten ausgelesen werden können, der Rest gilt als message und wird ausgegeben oder wie auch immer. Objekte ausserhalb der item-beschränkungen werden schlicht ignoriert. Die tags müssen nicht zwingend einen Zeilenumbruch haben und die metadaten-variablen müssen nicht mit tab eingerückt sein - es kann also auch alles auf einer Zeile stattfinden, eben wie bei html/xml. Letztenendes möchte ich wenn ich alle items habe in einer for schleife all ihre metadaten auslesen und in ein dictionary packen. Dann verpacke ich dieses metadata-dictionary zusammen mit der message in eine classe/objekt item und dann guck ich weiter.

Bisher mache ich das so unter unix-kommandozeile:

$ sed 's;\\;\\\\;g' config-oben | sed 's;\t;\\t;g' | sed 's;$;\\n;g' | tr -d '\n' > config-temp

$ python
Python 2.4.3 (#1, May 18 2006, 07:40:45)
[GCC 3.3.3 (cygwin special)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> file = open('config-temp', 'r')
>>> pattern = re.compile('<item>.*?</item>', re.I)
>>> items = pattern.findall(file.read())
>>> items
['<item>\\n<metadata>\\n id="int"\\n creation_time="str|$(date -u)|keywords"\\n expiration_time="str|$(date -u)|keywords"\\n starting_time="str|$(date -u)|keywords"\\n priority="high|medium|low"\\n comment="str"\\n assignies="str"\\n title=""\\n custom_key1=""\\n custom_key2=""\\n ...\\n</metadata>\\nmessage1\\n</item>', '<item>\\n<metadata>\\n id="int"\\n creation_time="str|$(date -u)|keywords"\\n expiration_time="str|$(date -u)|keywords"\\n starting_time="str|$(date -u)|keywords"\\n priority="high|medium|low"\\n comment="str"\\n assignies="str"\\n title=""\\n custom_key1=""\\n custom_key2=""\\n ...\\n</metadata>\\nmessage2\\n</item>']
>>> len(items)
2
>>>

Soweit so gut - jetzt habe ich die items. Doch ich finde das scheisse so. Ich will alles in Python implementieren, da grep nur greedy arbeitet auf regex ebene. Ich brauch aber grep und co. eh nicht, aber wie stelle ich das gescheit an unter Python?

Eine weitere Frage nebenbei ist, wie verwendet man in diesem zusammenhang die MULTILINE option der re von python? Wenn ich die unix-kommandozeile nicht eingebe und das config-file normal einlese, stattdessen aber vorher das pattern so compiliere: pattern = re.compile('<item>.*?</item>', re.I | re.M), dann kommt es leider nicht so raus wie gedacht, weil dann findet er gar kein item.
Mit file = open(myfle, 'rb') habe ich es auch bereits versucht, aber er scheint trotzdem nicht über zeilenrand hinaus zu gehen. Wo ist da der denkfehler bei der angabe des re.M? Wieso hilft mir das nicht bei meiner anforderung?

Edit (Leonidas): Die Diskussion in den Thread "XML/DTD: Kontextfreiheit?" getrennt.
BlackJack

@akis.kapo: Der Denkfehler ist, das `re.MULTILINE` nur eine Bedeutung für '^' und '$' in regulären Ausdrücken hat, nämlich ob die beiden Zeichen nur auf die zu untersuchende Zeichenkette als Ganzes die Bedeutung “das leere Wort am Anfang/Ende” haben, oder innerhalb der Zeichenkette auch Zeilenanfängen/-enden erkennen.

Was Du suchst ist `re.DOTALL`, damit der '.' auch Zeilenende-Zeichen erkennt.

Andererseits würde ich für XML fast immer einen XML-Parser einem regulären Ausdruck vorziehen.

@edit: Mit ElementTree könnte das so aussehen:

Code: Alles auswählen

from pprint import pprint
from elementtree import ElementTree as etree

source = """<document>
<item>
<metadata>
        id="int"
        creation_time="str|$(date -u)|keywords"
        expiration_time="str|$(date -u)|keywords"
        starting_time="str|$(date -u)|keywords"
        priority="high|medium|low"
        comment="str"
        assignies="str"
        title=""
        custom_key1=""
        custom_key2=""
</metadata>
message1
</item>
junk
<item>
<metadata>
        id="int"
        creation_time="str|$(date -u)|keywords"
        expiration_time="str|$(date -u)|keywords"
        starting_time="str|$(date -u)|keywords"
        priority="high|medium|low"
        comment="str"
        assignies="str"
        title=""
        custom_key1=""
        custom_key2=""
</metadata>
message2
</item>
</document>
"""

METADATA_RE = re.compile(r'(?P<key>[a-zA-Z][_\w]*)="(?P<value>[^"]*)"')

def main():
    root = etree.fromstring(source)
    for item in root.findall('item'):
        print '-' * 40
        metadata = dict(match.group('key', 'value')
                        for match
                        in METADATA_RE.finditer(item.find('metadata').text))
        pprint(metadata)
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

BlackJack hat geschrieben:... Was Du suchst ist `re.DOTALL`, damit der '.' auch Zeilenende-Zeichen erkennt. ...
Genau das war es. Irgendwie meinte ich MULTILINE bringt mir das gewünschte ergebnis statt DOTALL. Nächstes mal lese ich die Beschreibungen genauer durch in der lib doc.

@alle.
ich brauche keinen externen parser da bei mir der fall nicht vorkommen kann, dass </?item> oder </?metadata> in der message vorkommt (und wenn, wirds eben entfernt).
Danke trotzdem.
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

Welchen Sinn hat denn XML für deine Konfig-Datei?
Zum menschlichen Editieren finde ich traditionelle unixmäßige Dateien wesentlich entspannter.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich habe mal den Rest der Diskussion in den Thread "XML/DTD: Kontextfreiheit? verschoben - ein sehr interessantes Thema, aber in diesem Kontext für den OP nicht sonderlich zielführend.

Edit: Und nein, ich kann nichts dafür dass Dills Antwort im falschen Thread ist. DIe hat er gepostet nachdem ich den Thread getrennt habe, was leider ein Problem in phpBB ist. Müsste man überlegen, wie man sowas in Pocoo am besten löst.

Edit: Danke Dill für die Geistesgegenwart den Beitrag zu löschen und ihn im richtigen Thread zu posten.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

mkallas hat geschrieben:Welchen Sinn hat denn XML für deine Konfig-Datei?
Das ist kein XML es ist nur xml-ähnlich. Ich benutze es weil ich </item> schöner finde als \0.
mkallas hat geschrieben:Zum menschlichen Editieren finde ich traditionelle unixmäßige Dateien wesentlich entspannter.
Ich auch. Aber hier gehts ums parsen und nicht ums editieren. Ich will aus meiner config messages extrahieren und diese müssen unterscheidbar sein durch bestimmte "verbotene" wörter.
Ausserdem möchte ich rein optional bestimmte daten (metadaten) auslesen können. Geht bestimmt alles mit 100%igem XML, nur das will ich nicht benutzen, weil "zu viel". Lieber was weniger mächtiges aber dafür auf meine bedürfnisse abgestimmt. Und wer hällt sich schon an standards... ;)
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

akis.kapo hat geschrieben:Und wer hällt sich schon an standards... ;)
ich
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Was auch sinnvoll ist weil es dafür schon fertige Pakete gibt, die auch mit allen möglichen Sonderfällen klar kommen. Zum Beispiel das die äusseren Metatags irgendwo innerhalb der Message als Daten vorkommen, Zeichenkettenbegrenzer in der Zeichenkette selber usw.

Sich da irgendwelche fehleranfälligen Sachen selber zu basteln ist immer die schlechtere Lösung.

Es gibt `csv`, `ConfigParser`, `ConfigObj`, JSON, XML, …
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

BlackJack hat geschrieben:Es gibt `csv`, `ConfigParser`, `ConfigObj`, JSON, XML, …
Und manchmal liefern die sogar gleich noch ihre eigenen Fehler mit, so dass man sich auch die Erzeugung dieser sparen kann :D :D
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich würde dennoch sagen, dass die Parser eine generell höhere Qualität haben als eigener Code. Sie werden ja von wesentlich mehr Entwicklern genutzt.

Insbesondere XML-Parser würde ich als ziemlich zuverlässig einstufen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Leonidas hat geschrieben:Ich würde dennoch sagen, dass die Parser eine generell höhere Qualität haben als eigener Code. Sie werden ja von wesentlich mehr Entwicklern genutzt.

Insbesondere XML-Parser würde ich als ziemlich zuverlässig einstufen.
Dem stimme ich voll zu, aber ab und zu bringen Module doch so ihre Eigenheiten mit, die man gerne umgehen möchte. Sei es nun durch einen Wrapper oder eine eigene Implementierung.

In diesem konkreten Fall scheint mir der Einsatz eines vorhandenen Configparsers aber auch sinnvoller. Es fallen mir so spontan keine noch so abwegigen Konfigurationen ein, die man nicht gut mit einer INI oder XML beschreiben könnte.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

EyDu hat geschrieben:
Leonidas hat geschrieben:Ich würde dennoch sagen, dass die Parser eine generell höhere Qualität haben als eigener Code. Sie werden ja von wesentlich mehr Entwicklern genutzt.

Insbesondere XML-Parser würde ich als ziemlich zuverlässig einstufen.
Dem stimme ich voll zu, aber ab und zu bringen Module doch so ihre Eigenheiten mit, die man gerne umgehen möchte. Sei es nun durch einen Wrapper oder eine eigene Implementierung.
Richtig. Es gibt auch Parser die lange Zeit auf Eis lagen, wie PyYAML, welches inzwischen wieder aktiver ist.

Was auch manchmal Vorteilhaft ist, ist es den Parser zu wechseln. Statt htmllib BeautifulSoup, statt DOM/Minidom/SAX Elementtree/lxml. Solche Parser machen das Programmieren gleich angenehmer.

Wenn man das Parsen allerdings selbst macht, muss man sich selbst auch überlegen, wie man die API macht,damit man damit auch zurechtkommt.

Solche Pseudo-XML-Dateien finde ich hingegen sinnlos, sie kombinieren die Nachteile von XML und lassen die Vorteile davon weg. Eine INI-Datei wäre eine hübsche Alternative. Ebenso ist YAML IMHO gut für Konfigurationsdateien geeignet. Das nutze ich in Ruby recht gerne, weil dort Syck (der YAML-Parser) bereits in der Stdlib dabei ist und ich also dadurch keine weiteren Abhängigkeiten schaffe.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

ich finde folgendes auch nicht schlecht (um mal in eine andere Richtung zu gehen):

config.py:

Code: Alles auswählen

#kommentare sind halt auch für die user da
movie_path = "/var/local/movie"
www_path = "/var/www/mysite"
language = ("de","en","us")
benutzung:

Code: Alles auswählen

import config as c

print c.language[0]
fo = open("%s/%s"%(c.movie_path,"default.mov"),"r")
Wenn man natürlich keine .py als config will kopiert man sich zum start vom Programm die .cfg bzw. .conf in .py

*edit*
code berichtigt
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Sr4l hat geschrieben:Wenn man natürlich keine .py als config will kopiert man sich zum start vom Programm die .cfg bzw. .conf in .py
Was eine recht schlechte Idee ist, wenn man dann so ein Programm systemweit installiert. Als normaler User kann man die Globale-Configdatei nicht umbenennen. Umbenennaktionen sind überhaupt eine eher schwache Idee für Konfigurationsdaten.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
PmanX
User
Beiträge: 123
Registriert: Donnerstag 25. Januar 2007, 13:50
Wohnort: Germany.BB.LOS
Kontaktdaten:

Was spricht dagegen, wenn eine config.py im Homeverzeichnis globale Einstellungen überlädt?
Benutzeravatar
nkoehring
User
Beiträge: 543
Registriert: Mittwoch 7. Februar 2007, 17:37
Wohnort: naehe Halle/Saale
Kontaktdaten:

Ich habe es so aehnlich geloest, wie PmaX vorschlug, aber wollte die config gern von der Systemumgebung "fern halten". Also baute ich es ein wenig anders.

Hier ein kleines Beispiel fuer eine Konfigurationsdatei:

Code: Alles auswählen

_menu_link = Tag(soup, "span", [("class", "menu_btn")])
_menu_header = Tag(soup, "span", [("class", "menu_header")])
_menu_spacer = Tag(soup, "hr", [("class", "menu_spacer")])
Und hier mein Parser, inspiriert durch den von pyGet:

Code: Alles auswählen

class Config(dict):
    def __init__(self, filename, sep='='):
        super(Config,self).__init__()
        self.load(filename, sep)

    def load(self, filename, sep):
        f = file(filename, 'r')
        for line in f:
            line = line[:line.find('#')].strip()
            if line.find(sep) > 0:
                data = [x.strip() for x in line.split('=')]
                data[0] = data[0].lower()
                if data[1][0] in ['"',"'"]:
                    data[1] = data[1].strip('"\'')
                elif data[1].lower() in ['true','false']:
                    data[1] = {'true':True,'false':False}[data[1].lower()]
                else:
                    data[1] = str(data[1])
                self[data[0]] = data[1]
        f.close()
...das "manko" und gleichzeitig aber auch die Staerke hier ist, dass man die Variablen zwar direkt abfragen, aber sie danach noch in ein eval() packen muss. Das sorgt dafuer, dass die Konfigurationsdatei alles verwenden kann, was dem eigentlichen Programm auch zur verfuegung steht.

MfG
[url=http://www.python-forum.de/post-86552.html]~ Wahnsinn ist auch nur eine andere Form der Intelligenz ~[/url]
hackerkey://v4sw6CYUShw5pr7Uck3ma3/4u7LNw2/3TXGm5l6+GSOarch/i2e6+t2b9GOen7g5RAPa2XsMr2
Antworten