OPENOFFICE-DOKUMENTE SELBST MODIFIZIEREN - EINE ANREGUNG MIT BEISPIELEN PYCON 2012, LEIPZIG

 
WEITER LESEN
OpenOffice-Dokumente selbst
         modifizieren
                                        Eine Anregung mit Beispielen

                                                    PyCon 2012, Leipzig
                                                      Reinhard Wobst
                                                     r.wobst@gmx.de

R.Wobst, OpenOffice-Dokumente selbst modifizieren                         1/18
1. Anliegen
   ODF (Open Document Format) ist ein schönes Format, und ich arbeite
   gern mit LibreOffice ☺
   Leider ist unakzeptabel viel Handarbeit nötig, wenn
       man viele Dokumente in gleicher Weise bearbeiten muss oder
       ein Dokument an vielen Stellen zu modifizieren ist und das nicht mit
       einfachem Suchen+Ersetzen oder Makros erledigt werden kann,
       oder Fehler wie nicht aufgelöste Querverweise gesucht werden
       müssen.
   Es gibt bereits eine Reihe Tools, die Open/LibreOffice steuern (wie
   pyUNO) oder ODF-Dokumente bearbeiten (vgl. 8), aber diese sind
   schwierig zu nutzen, oder noch in Entwicklung, oder leisten nicht das
   Gewünschte.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                             2/18
Dabei sind ODF-Files nichts anderes als zip-Archive von XML-Files, wie
   man z.B. mit dem vim leicht sehen kann - BEISPIEL: t.zip
   Idee: Das Modifizieren vor allem von content.xml so bequem wie
   möglich machen und das spezielle Anliegen selbst programmieren.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                       3/18
2. Vorgehen
   content.xml wird mit xml.etree.ElementTree geparsed
   man stützt sich auf die recht gut lesbare ODF-Dokumentation [3]
   Vorgehen vorerst vorwiegend heuristisch, Analyse des Formats zuerst mit
   Editor, dann mittels Standard
   Auf Optimierungen (RAM, CPU) braucht man kaum Rücksicht zu nehmen
   vorerst nur Lösen konkreter Probleme, daraus könnte sich später ein
   bequemeres Interface ergeben.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                        4/18
2.1.              Klasse ResolveODF
Die Klasse übernimmt nur das Bereitstellen und Rückschreiben eines XML-
Baums; die Modifikation des etree's erfolgt durch das aufrufende
Programm.
Methoden:
__init__(ODF_file, zipentry='content.xml')
       Konstruktor

getIterator()
       Liefert Iterator über den etree-Baum, kann mehrfach gerufen werden
       und liefert dabei unabhängige Bäume - vgl. 6.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                      5/18
writeBack(backup=True)
       schreibt den ggf. modifizierten etree-Baum zurück, überschreibt das
       alte ODF-File und legt optional noch ein Backup ODF_file.bck an.

2.2.              Besonderheiten zu beachten!
Es muss einen Eintrag mimetype im zip-Archiv geben mit einem Eintrag
wie

     application/vnd.oasis.opendocument.text
- aber dieser darf nicht komprimiert sein, damit der Filetyp erkannt werden
kann. Bis Python2.6 konnte man die Kompressionsmethode bei
ZipFile.writestr() nicht angeben, daher wird mit einem
temporären File gearbeitet:

R.Wobst, OpenOffice-Dokumente selbst modifizieren                         6/18
fdo = zipfile.ZipFile(self.name, 'w',
                           zipfile.ZIP_DEFLATED)
       for mem in self.members:
         if mem == 'mimetype':
           # the zipfile.write method forces a
           # named file as argument
           _fd = tempfile.NamedTemporaryFile()
           _fd.write(self.entries[mem])
           _fd.seek(0) #!
           fdo.write(_fd.name, 'mimetype',\
             zipfile.ZIP_STORED)
           _fd.close()
         else:
           fdo.writestr(mem, self.entries[mem])
     ...
fd.seek(0): temporäres File muss gelesen, darf aber noch nicht
geschlossen werden!

R.Wobst, OpenOffice-Dokumente selbst modifizieren                7/18
3. Beispiel: Tabellenspalten
vertauschen
Sicher von beschränktem praktischen Wert: In einem Office-Writer-File
sollen bei allen Tabellen die ersten beiden Spalten vertauscht werden:

     handle = ResolveODF(sys.argv[1])
     ...
     for elem in handle.getIterator():
       if stripns(elem.tag) == 'table-row':
         ch = elem.getchildren()
         h = ch[0]; ch[0] = ch[1]; ch[1] = h
     handle.writeBack()
VORFÜHRUNG: Tabcolex.py

R.Wobst, OpenOffice-Dokumente selbst modifizieren                        8/18
Hinderlich ist, dass beim Tagname immer die Namensräume (bei ODF recht
lang) vorangesetzt werden - hier per einfacher Funktion stripns()
herausgefiltert.
Bemerkungen
   Ab Python2.7 kann elem.getchildren() auch durch
   list(elem) ersetzt werden.
   Der ElementTree-Baum sollte beim Parsen nicht modifiziert werden, aber
   es funktioniert hier ☺ (vgl. a. 5).
   Ebenso könnte man nach Spalten sortieren. Das kann LibreOffice auch -
   aber nicht mehr bei lexikografischer Sortierung nach mehreren Spalten
   oder schwierigen Sortierkriterien. Sortieren nach Zeilen wird schwieriger.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                           9/18
4. Beispiel: manuelle Änderungen
finden
"Änderungen von Hand" (Setzen von Fonts, Auszeichnung wie kursiv, fett,
Fontfarbe) mitten im Text sind eine weit verbreitete Unsitte, die geforderte
Korrektur wird sehr teuer bis fast unmöglich.
Spätestens ein Setzer verlangt konsequente Anwendung von Styles, auch
bei Zeichen!
→ Wünschenswert wäre wenigstens eine Markierung der "Sünden",
Korrektur von Hand ist sowieso nötig.
Das Skript FindManual.py leistet das: VORFÜHRUNG
Hier wird heuristisch ausgenutzt, dass die automatisch generierten
Textstyles Namen der Form Tnn haben, nn = Ziffernfolge, haben (nicht Teil

R.Wobst, OpenOffice-Dokumente selbst modifizieren                          10/18
vom Standard!), und Formatierungen mitten im Text mit dem Tag span
markiert sind.
Leider können damit von Hand markierte Absätze nicht so einfach erfasst
werden.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                         11/18
5. Beispiel: Unerwünschte Kapitel
löschen
Dieses Beispiel ist nicht trivial und zeigt prinzipielle Probleme des ODF-
Designs: Kapitel sind hierarchisch strukturiert, das XML-File spiegelt das
nicht wieder.
Anliegen ist, alle Kapitel einer vorgebenen Gliederungsebene, deren
Überschrift einen gegeben Text enthält, zu löschen - samt aller Unterkapitel.
Das ist mit dem Navigator nicht möglich; man muss die zu löschenden
Kapitel von Hand markieren und löschen - keine Option bei 100
Dokumenten.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                            12/18
Es gibt drei Probleme:
   ElementTree erlaubt das Löschen von Knoten nur vom Elternknoten
   aus, dieser wieder aber nicht mitgeführt.
   Der Überschriftstext muss nicht das Text-Attribut des Überschrifts-
   Knotens sein, wenn dort ein z.B. Bookmark gesetzt ist (wird auch
   automatisch eingefügt).
   Die fehlende Hierarchie im XML, wie oben erwähnt.
Das zwingt zu Tricks, wie in Remove.py gezeigt: VORFÜHRUNG
   Das Löschen wird über ein Flag delete vorgemerkt, das via
   Überschriften-Level ein- und ausgeschaltet wird.
   Die Elternknoten werden hier recht umständlich gesucht.

Problem:
Im Beispiel wird eine Referenz zerstört - Anregung für ...

R.Wobst, OpenOffice-Dokumente selbst modifizieren                        13/18
6. Beispiel: Nicht aufgelöste
Referenzen markieren
Referenzen werden intern durch Tags wie bookmark-start gesetzt,
der automatisch vergebene Wert steht im Attribut name. Der Verweis auf
die Referenz geschieht in Tags bookmark-ref, ebenfalls über das
Attribut name. Dazu muss das Dokument zweimal geparsed werden - das
ist mit der Methode getIterator() kein Problem, da sie auch
mehrfach aufgerufen werden kann.
VORFÜHRUNG: MissingRef.py
Anmerkung:
Der Text "Referenz nicht gefunden" kann nicht gesucht werden, er wird von
LibreOffice erst bei der Anzeige erzeugt.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                      14/18
7. Anregungen, Probleme
   Besonders einfach: Textstellen oder Attribute (z.B. Farben, Schnitt) finden
   und ändern - auch Anpassung an vorgegebene Farbtabelle bei großer
   Zahl von Dokumenten ist denkbar.
   Änderung kann kontextabhängig sein (z.B. nur bestimmte Tabellen
   sortieren) - vgl. Beispiel in 5.
   Modifikation der Baumstruktur ist u.U. heikel und nicht ganz einfach; ganz
   sicher ist das parallele Erzeugen eines neuen Baums (der
   handle.tree ersetzt).
   Es ist hinderlich, dass LibreOffice automatische Styles ohne
   Notwendigkeit einfügt - daher auch ein "XXX" zuviel im Ergebnis von
   FindManual.py.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                          15/18
Es wäre schön, wenn es ElementTree-Methoden gäbe, die bei der
   Suche Namensräume ausblenden. Oder man wenigstens an eine Liste
   von Paaren Kürzel-Namensraum herankäme.
   Draw-Dokumente sind leider nicht so einfach zu bearbeiten - intern
   werden zu oft neues Styles erzeugt, oder es ist zusätzliche Intelligenz
   nötig (etwa beim Umwandeln von Linien- in Kurvenverbinder).
   Ebenso ist die Font-Verwaltung nicht trivial.
   Weder ODF-Standard noch dessen Umsetzung sind "strikt". Derzeit sollte
   man immer prüfen, ob die eigenen Annahmen z.B. über Stylenamen
   zutreffen.
   Zunächst mehr Erfahrungen und Anregungen aus der Praxis gesucht, erst
   dann Entwicklung von Tools - oder auch z.B. von OOopy, s. 8.
   Wunder soll man nicht erwarten: Für optimale Spaltenbreiten bei Tabellen
   gibt es z.B. keinen optimalen Algorithmus ☹.

R.Wobst, OpenOffice-Dokumente selbst modifizieren                            16/18
8. Konkurrenz
   Makros zielen in anderen Richtung
   pyUNO ist kompliziert, wenig dokumentiert, dient zur "Fernsteuerung von
   LibO/OO"; wird z.B. vom Konvertierungstool unoconv genutzt.
   ODFpy als Paket in Distr, sehr schlechte Doku, Ergebnis z.B. von
   odf2xhtml unakzeptabel: VORFÜHRUNG
   OOopy ist offenbar noch rudimentär (http://ooopy.sourceforge.net/),
   obwohl Substitutionen implementiert sind.
   ? (noch zahlreiche andere Referenzen, aber bisher nichts Nützliches
   gefunden)

R.Wobst, OpenOffice-Dokumente selbst modifizieren                        17/18
9. Links
[1]     Quellcode und Dokumentation:
        https://bitbucket.org/ReinhardW/resolveodf
[2]     Einführung in ODF:
        http://www.langintro.com/odfdom_tutorials/odf_internals.html
[3]     ODF Standard:
        https://www.oasis-open.org/ > Standards > ODF1.1 >
        Open Document v1.1 as PDF
        (der Standard von 1.2 ist in mehrere Teile zerlegt)

R.Wobst, OpenOffice-Dokumente selbst modifizieren                      18/18
Sie können auch lesen