OOWriter Dokument manipulieren

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
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Hallo,

ich habe in einem OOWriter-Dokument Platzhalter definiert (Name, Adresse etc.), die ich jetzt programmatisch ersetzen möchte. Ziel ist eine neue Datei, die die "Vorlage" durchaus mehrfach, allerdings mit unterschiedlich ersetzten Platzhaltern enthalten kann. Vergleichbar einem Serienbrief also.

Ich möchte die eingebaute Python-"Unterstützung" und UNO nicht verwenden.

Nun sind ODF-Dokumente ja im Prinzip ein Haufen gezippter XML-Dateien. Die, die mich interessiert, ist die content.xml . Bisher öffne ich diese, lese den eigentlichen Inhalt aus, kopiere ihn oft genug und ersetze die Platzhalter mittels RE. Anschließend setze ich aus dem neuen Inhalt und den alten Kopfinformationen eine neue Content.xml zusammen und rödele mit zipfile alles wieder zusammen. Das funktioniert so lala.

Probleme treten z.B. auf, wenn der Name Zeichen enthält, die in XML besondere Bedeutung haben ("&"). Abgesehen davon ist das Auseinandernehmen des Dokumentes mittels RE auch etwas fehleranfällig.

Meine Frage(n) wäre nun: Würde der Code stabiler und weniger umständlich, wenn ich ein XML-Toolkit verwenden würde? Würde mir dieses das Escapen von "Sonderzeichen" abnehmen? Welches Modul könnt ihr für diese Aufgabe empfehlen? Oder hat jemand noch eine Idee, für einen völlig anderen Ansatz?
BlackJack

Ja, der Code würde auf jeden Fall robuster werden, wenn Du XML mit einem XML-Parser statt regulären Ausdrücken verarbeitest. :-)

`xml.etree` bzw. `lxml` würde ich empfehlen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Andererseits: Ersetzen musst du einzig "&" und "<", das kann man auch schnell mit zwei `replace()`-Aufrufen erledigen und wenn du als Markierung für die zu ersetzenden Bereiche etwas wie `{{ }}` wählst, was im restlichen Text wahrscheinlich nicht vorkommt, scheint es mir einfacher, das XML-Dokument als flaches Textdokument zu betrachten als wenn man da jetzt systematisch über den DOM laufen muss und alle Textknoten finden und untersuchen muss. Ein SAX-artiger Ansatz wäre da besser geeignet. Kann lxml auch, aber statt xml.etree wäre dann xml.sax das bessere Paket.

Wenn es noch unwahrscheinlicher machen willst, dass du eine Markierung mit normalem Text verwechselst, dann highlighte zum Beispiel den Hintergrund. Das führt dazu, dass der markierte Name in ein `<text:span text:style-name="...">...</text:span>` eingeschlossen wird. Wenn du's ganz richtig machen willst, dann suche auch noch aus den `<office:automatic-styles>` den Stylenamen heraus, der z.B. den gelben Hintergrund darstellt und merke dir seinen Namen. Der muss dahin, wo ich die ersten `...` hingeschrieben habe.

Code: Alles auswählen

import re, zipfile

z = zipfile.ZipFile("/Users/sma/Desktop/test.odt", "a")
c = z.read("content.xml").decode("utf-8")

m = re.search(
    r'<style:style style:name="(\w+)" style:family="text">'+
    r'<style:text-properties fo:background-color="#e6ff00"/>'+
    r'</style:style>(?u)', c)
name = m.group(1)

replacements = {'name': 'Stefan'}

c = re.sub(
    r'{{<text:span text:style-name="%s">(\w+)</text:span>}}(?u)' % name, 
    lambda m: escape(replacements[m.group(1)]), c)

z.writestr("content.xml", c.encode("utf-8"))
z.close()
Stefan
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Vielen Dank euch beiden für eure Antworten. Ich habe mich mal kurz mit lxml.etree beschäftigt und bin eigentlich ganz angetan, aber auch noch nicht restlos überzeugt. Bei meinem jetzigen Wissensstand kann ich keiner der beiden Methoden den Vorzug geben. Muß also noch ein wenig mit rumspielen und die Möglichkeiten erkunden.

Zu Stefans Beispiel habe ich noch eine Frage: Du verwendest da eine Funktion 'escape', von der ich nicht so recht weiß, wo die herkommt. Das ist nicht die aus dem re-Modul, oder? Weil damit kann ich leider kein gültiges XML erzeugen. Mein Problem ist auch weniger das erkennen und ersetzen der Platzhalter (ich verwende da '[%platzhaltername%]' ), als das in den Ersatztexten bisweilen halt Zeichen auftreten, die in XML eine besondere Bedeutung haben. Natürlich kann ich die ohne großen Aufwand über replace ersetzen, aber wenn mir irgendein Modul / eine Funktion diese Verantwortung abnähme, wäre ich natürlich noch zufriedener. Zusammen mit dem Duplizieren bestimmter Teile des Baumes könnte das den Einsatz einer entsprechenden Bibliothek schon rechtfertigen.

Könnte.

Bin ich mir halt noch nicht so sicher. Werde mich noch weiter mit lxml beschäftigen und schauen, ob ich mir damit nicht andere Probleme ins Haus hole. Aber zwei Ansätze zu vergleichen ist schon mal um Längen besser, als im Nebel zu stochern! :)

Vielen Dank noch mal und bis zum nächsten Mal
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Code: Alles auswählen

def escape(s): return s.replace("&", "&").replace("<", "<")
Stefan
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Ah, danke. :) So macht das natürlich mehr Sinn.
lunar

Code: Alles auswählen

from xml.sax.saxutils import escape

;)

Ich persönlich würde lxml nutzen, ich halte nicht viel davon, Markup mit regulären Ausdrücken zu verarbeiten.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

lunar hat geschrieben:Ich persönlich würde lxml nutzen, ich halte nicht viel davon, Markup mit regulären Ausdrücken zu verarbeiten.
Ich würde sagen: Es kommt darauf an. In diesem einfachen Fall gewichtige ich "keine externen Abhängigkeiten" als wichtiger. Zudem wäre IMHO eine Lösung mit einem SAX-Eventhandler nicht weniger aufwendig und man gewinnt nicht wirklich etwas. Das XML benutze ich ja nur als zusätzliche "tags" um das Auftreten der "magischen" Markierungen unwahrscheinlicher bzw. in meinem Fall unmöglich zu machen. Man muss nur den richtigen Gelbton treffen :)

Stefan
BlackJack

Es kommt auch darauf an wie robust die Lösung sein soll. Zum Beispiel ob man sich darauf verlässt, dass beim `style`-Tag immer `name` vor `family` kommst, und das der Namensraumpräfix immer `style` lautet. Das wäre mir persönlich schon zu zerbrechlich.
lunar

sma hat geschrieben:Zudem wäre IMHO eine Lösung mit einem SAX-Eventhandler nicht weniger aufwendig und man gewinnt nicht wirklich etwas.
Wer SAX in Python nutzt, ist selbst schuld ...
Das XML benutze ich ja nur als zusätzliche "tags" um das Auftreten der "magischen" Markierungen unwahrscheinlicher bzw. in meinem Fall unmöglich zu machen. Man muss nur den richtigen Gelbton treffen :)
Dein "Parser" kann das Auftreten der magischen Markierung auch in gewollten Fallen unmöglich machen, wenn der Serializer der Textverarbeitung die Attribute zufälligerweise mal in einer anderen Reihenfolge schreibt, oder aus irgendwelchen Gründen ein zusätzliches Attribut hinzufügt.

Warum unbedingt muss man immer externe Abhängigkeiten vermeiden? Die Installation von lxml ist doch trivial ...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

lunar hat geschrieben:Wer SAX in Python nutzt, ist selbst schuld ...
SAX ist aber noch die einfachste Lösung, denn ich brauche ja nur die Textknoten und muss den Rest einfach unverändert weiterreichen. Ich bin so trainniert, dass ich Daten möglichst immer als Strom verarbeite und nicht wie ein DOM er erfordert, erstmal alles lese.
lunar hat geschrieben:Dein "Parser" kann das Auftreten der magischen Markierung auch in gewollten Fallen unmöglich machen, wenn der Serializer der Textverarbeitung die Attribute zufälligerweise mal in einer anderen Reihenfolge schreibt, oder aus irgendwelchen Gründen ein zusätzliches Attribut hinzufügt.
In der Tat verlasse ich mich darauf, dass OpenOffice immer das selbe XML erzeugt - kanonisch ist es leider nicht wie ich gerade sehe, da style:family nach style:name kommt. Der Kram mit dem Raussuchen des Stilnamens per RE ist Müll. Bei den Markierungen jedoch den text:span mit zu berücksichtigen finde ich jedoch nach wie vor "clever" :)
lunar hat geschrieben:Warum unbedingt muss man immer externe Abhängigkeiten vermeiden? Die Installation von lxml ist doch trivial ...
Kein Stück, dat Teil lässt sich z.B. unter OS X nicht installieren... man müsste wohl selbst patchen und da kann mir die Lib dann gestohlen bleiben. Und selbst wenn man's macht, muss man gcc und co installiert haben... da sage ich dann einem anderen Mac-User er möge sich erstmal XCode installieren? Nein, der Preis ist mir wie ich ja schon sagte, viel zu hoch. Daher lieber ohne externe Abhänigkeiten, wenn möglich.

Das Nachinstallieren ist nicht immer so einfach wie unter Linux und auch dort mag noch ein Admin dazwischen stehen...

Stefan
lunar

sma hat geschrieben:
lunar hat geschrieben:Wer SAX in Python nutzt, ist selbst schuld ...
SAX ist aber noch die einfachste Lösung, denn ich brauche ja nur die Textknoten und muss den Rest einfach unverändert weiterreichen. Ich bin so trainniert, dass ich Daten möglichst immer als Strom verarbeite und nicht wie ein DOM er erfordert, erstmal alles lese.
Bei SAX muss man den Handler implementieren, evtl. den Kontext mitschleppen, und anschließend noch manuell serialisieren. Bei lxml.etree dagegen suche ich mit XPath das Objekt des entsprechenden Knotens, ändere dessen Attribute (im Python-Sinn) und schreibe das Ganze dann wieder aus. Mit den Niederungen von XML wird man gar nicht konfrontiert.

Der Code dürfte sicherlich ein bisschen länger sein als die RE-Variante, ist aber ungleich robuster. Natürlich liest man mit einem DOM erstmal das ganze Dokument, aber das tust du ja bei deiner RE-Variante auch.
lunar hat geschrieben:Warum unbedingt muss man immer externe Abhängigkeiten vermeiden? Die Installation von lxml ist doch trivial ...
Kein Stück, dat Teil lässt sich z.B. unter OS X nicht installieren... man müsste wohl selbst patchen und da kann mir die Lib dann gestohlen bleiben. Und selbst wenn man's macht, muss man gcc und co installiert haben... da sage ich dann einem anderen Mac-User er möge sich erstmal XCode installieren? Nein, der Preis ist mir wie ich ja schon sagte, viel zu hoch. Daher lieber ohne externe Abhänigkeiten, wenn möglich.
Tja ... Mac OS X eben ;) Allerdings musst du das imho doch auch nur einmal durchexerzieren. Den Anwendern kannst du dann doch ein statisch gelinktes Egg mitgeben, oder sehe ich das falsch? Stefan freut sich bestimmt auch, wenn du OS X Eggs für den Cheeseshop bereitstellst ;)
Antworten