Früher verhinderten proprietäre Dateiformate den Umstieg auf alternative Officesuites. Das hat sich gewaltig geändert, mittlerweile gilt Libre Office gar als VW Golf der Office-Welt, vor allem dank der vielen Importfilter. Sogar zum Zusammenschrauben eigener Importer reichen Hobbykenntnisse.
Je mehr fremde Formate Libre Office sauber unterstützt, umso besser – so lautet wohl ein Credo der Entwickler. Jede neue Version bringt neue Importtools. Erst kürzlich erzählte Michael Meeks in seinem Vortrag auf der Fosdem, wie immer mehr Anwender Libre Office verwenden, um alte Officedateien zu öffnen, deren Programmversionen nirgendwo mehr laufen, und so die Inhalte zu retten [1]. Undokumentiert? Nicht mehr unterstützt? Verwaist? Nichts scheint zu schwer, seit Libre Office 3.3 wächst die Zahl der Formate stetig. Ein Grund dafür ist die Leichtigkeit, mit der Entwickler neue Importfilter erstellen. Wie das geht, zeigt dieser Artikel.
Parsen und verstehen
Ein Importfilter macht eigentlich nichts anderes, als ein fremdes Dokument zu parsen und sinnvolle Informationen herauszusuchen. Mit denen füttert er die Anwendung, sodass sie auf dem Bildschirm Inhalte in hilfreicher Form darstellt. Viele Filter, beispielsweise der für Microsoft Word, kommunizieren jedoch direkt mit Libre Office, indem sie die internen Strukturen der Dokumente zu übernehmen trachten.
Diese Vorgehensweise vermeidet Informationsverluste, indem sie ohne zusätzliche, manchmal sehr rechenintensive Umwandlungen, versucht das Dokument direkt zu verstehen. Aber das Ganze klappt natürlich nur, wenn der Filter mit tiefer gehendem Know-how über das Ausgangsformat ausgestattet ist. Das wiederum erzeugt eine sehr steile Lernkurve bei dem, der solch einen Filter programmieren will.
Open Document Format
Gut, dass es deshalb noch zwei andere Typen von Importfiltern gibt, die weniger hohe Ansprüche stellen und sich auch für Entwickler eignen, die, ohne groß in die Tiefen von Libre Office einzutauchen, Erfolge sehen wollen.
Die Schlüsselrolle nimmt dabei das Open Document Format (ODF, [2]) ein, das sich als Austauschformat für Filterprogrammierer geradezu aufdrängt. Wer es verwendet, muss nur wenige Kernbestandteile der Libre-Office-Interna durchschauen, also einige Hundert Zeilen gut dokumentierten Quelltext verstanden haben, schon kann er loslegen und für sein Format eine ODF-Entsprechung entwickeln. Die öffnet Libre Office dann problemlos.
XSLT-Filter
Auch XML bietet sich für Importfilter an, Libre Office bringt sogar einen eigenen XSLT-Filterdialog mit (siehe Abbildung 1). Damit das klappt, bedarf es nur noch einer XSL-Vorlage, die fremdes XML in ODF konvertieren hilft. Ist die erst einmal angelegt, lässt sich der Rest mit Mausklicks im GUI der Office-Anwendung bewerkstelligen.

Abbildung 1: XML-XSLT ist einer der Wege, fremde Dateiformate in ODF oder Libre Office zu überführen. Das Officepaket hat bereits einige Filter an Bord.
Den passenden Dialog findet der Anwender im Menü unter »Extras | XML-Filtereinstellungen« . Hier zeigt sich eine Liste aller XSLT-Filter zusammen mit der Anwendung, die Libre Office öffnen soll. Außerdem steht hier die Richtung der Umwandlung, also ob es sich um einen Import- oder Export-Filter handelt oder aber beides. Ein Klick auf »Neu« ergibt den Dialog aus Abbildung 2.

Abbildung 2: Der erste Schritt zu einem eigenen Importfilter führt über die Wahl eines eindeutigen Namens.
Auf dem Tab »Allgemein« finden sich die Informationen, die später für den Anwender sichtbar sein werden: der Name, die Libre-Office-Applikation, die das umgewandelte Dokument erhalten wird, beispielsweise Calc für Tabellen jedweder Art. Diese Information verwendet Libre Office übrigens auch, um unterschiedliche Dokumentformate zu gruppieren: Wer im Datei-Öffnen-Dialog »Präsentationen« wählt, dem zeigt es automatisch alle Dateien an, die ein Importfilter umwandeln kann.
»Name des Dateityps« und »Dateiendung« helfen Dateien zu organisieren und mit dem Ausgangsmaterial zu verknüpfen. Als Dateinamen-Erweiterung kann der Anwender eine durch Strichpunkte separierte Liste von Endungen angeben, zum Beispiel »xls« und »xml« für Microsofts Excel-2003-XML-Format.
Transformationen
Auf dem nächsten Reiter »Transformation« (siehe Abbildung 3) finden sich dann die Informationen über die XSL-Transformationen, die die eigentliche Umwandlungsarbeit erledigen. Das Eingabefeld »DocType« bietet sich vor allem für Importfilter an. Die XSLT-Filter-Typerkennung von Libre Office scannt nach dem hier eingegebenen String in den ersten 4000 Byte einer Filterdatei. Wer den Begriff in seinem Filter bereits untergebracht hat, tut sich leichter, ansonsten lässt er das Feld leer, dann bestimmt Libre Office diesen anhand der Dateinamen-Erweiterung.
XSLT 2.0
Wer dagegen Exportfilter schreiben will, sollte im »Export-XSLT« -Feld die Datei nennen, die die Transformation von Open-Document-XML zu dem gewünschten Fileformat übernimmt. Lässt er das Feld leer, dann weiß Libre Office, dass es sich nicht um einen Exportfilter handelt. Das Gleiche gilt natürlich auch für Importfilter, wenn das »Import-XSLT« -Feld leer bleibt.
Libre Office bringt bereits mehrere Filter mit, die beispielsweise nur den Export erlauben, etwa die XHTML- und Mediawiki-Filter. Andere, etwa die Docbook-Extension, enthalten keine Formatvorgaben und verlangen zwingend eine Vorlagendatei, die der Anwender über das Feld »Import-Dokumentvorlage« eingibt. Ohne diese Information verwendet Libre Office seine eigenen Standardvorlagen.
Nur wer einige exklusive XSLT-2.0-Features benötigt, sollte den Schalter »Der Filter benötigt XSLT 2.0-Prozessoren« aktivieren. Sonst ist immer Version 1.0 die bessere Wahl, weil viel einfacher und von der in Libre Office verwendeten Libxslt deutlich besser unterstützt.
Wer sich so weit durch die Dialoge gekämpft hat, kann seine Filterprogramme über die Schaltfläche »XSLTs testen« aus Abbildung 1 ausprobieren, sie im Erfolgsfall direkt als Paket exportieren und über Plattformen [3] mit anderen Anwendern sharen. Diese relativ einfache, aber direkt mit der Community vernetzte Vorgehensweise erklärt, warum in den letzten Jahren so viele Filter rund um Libre Office entstanden sind.
Das Xfilter-Framework
Auch der größte Vorteil der Implementierung als XSLT-Filter liegt in seiner Einfachheit. Allerdings hat die auch Nachteile: Obwohl die Entwickler den zugrunde liegenden XSLT-Prozessor auf die relativ schnelle Libxslt-Engine migriert haben, kann eine Konvertierung mit XSLT besonders bei umfangreichen Dokumenten recht lange dauern. Außerdem mag diese Methode ungeeignet sein, wenn sich die Basiskonzepte des Eingabe- und des Ausgabeformats nicht ohne Weiteres aufeinander übertragen lassen.
Eine Lösung bietet das Xfilter-Framework. (Genau genommen nutzen auch die XSLT-Filter dieses Framework intern als eine Zwischenschicht.) Wer das Xfilter-Framework verwendet, profitiert von den Möglichkeiten höherer Programmiersprachen, was eine leichtere Abbildung von abweichenden Konzepten ermöglicht. Anders als bei XSLT lässt sich ein Dokument in mehreren Durchgängen verdauen, was ein komplexeres Verarbeiten der eingelesenen Daten möglich macht. Bei nicht XML-basierten Formaten ist das Xfilter-Framework ebenfalls angezeigt – ein XSLT-Filter kann mit Binärformaten nichts anfangen.
MS Publisher integrieren
Die Integration in das Xfilter-Framework ist etwas komplizierter als das Nutzen der XSLT-Filterdialoge, aber auch kein Hexenwerk. Das folgende Beispiel verfolgt die nötigen Schritte anhand des in Libre Office 4 neu hinzugekommenen Microsoft-Publisher-Filters. Der Einfachheit halber beginnt es mit den Konfigurationsdaten. Dazu benötigt man zwei XML-Schnipsel: einen für die Filterbeschreibung (Listing 1), einen anderen für die Definition des Dateityps (Listing 2).
Listing 2
Definition des Dateityps
01 <node oor:name="draw_Publisher_Document" oor:op="replace"> 02 <prop oor:name="DetectService"> 03 <value>com.sun.star.comp.Draw.MSPUBImportFilter</value> 04 </prop> 05 <prop oor:name="Extensions"> 06 <value>pub</value> 07 </prop> 08 <prop oor:name="MediaType"> 09 <value>application/x-mspublisher</value> 10 </prop> 11 <prop oor:name="Preferred"> 12 <value>true</value> 13 </prop> 14 <prop oor:name="PreferredFilter"> 15 <value>Publisher Document</value> 16 </prop> 17 <prop oor:name="UIName"> 18 <value>Microsoft Publisher</value> 19 </prop> 20 </node>
Listing 1
Filterbeschreibung
01 <node oor:name="Publisher Document" oor:op="replace"> 02 <prop oor:name="Flags"> 03 <value>IMPORT ALIEN USESOPTIONS 3RDPARTYFILTER PREFERRED</value> 04 </prop> 05 <prop oor:name="FilterService"> 06 <value>com.sun.star.comp.Draw.MSPUBImportFilter</value> 07 </prop> 08 <prop oor:name="UIName"> 09 <value xml:lang="x-default">Microsoft Publisher 97-2010</value> 10 </prop> 11 <prop oor:name="FileFormatVersion"> 12 <value>0</value> 13 </prop> 14 <prop oor:name="Type"> 15 <value>draw_Publisher_Document</value> 16 </prop> 17 <prop oor:name="DocumentService"> 18 <value>com.sun.star.drawing.DrawingDocument</value> 19 </prop> 20 </node>
Das Attribut »oor:name« des Knotens ist die interne Bezeichnung des Filters und von besonderer Bedeutung, dient es doch zur Verknüpfung des Dateityps mit einem zugehörigem Filter. Die meisten »Flags« lassen sich einfach übernehmen: »IMPORT« markiert den Filter als einen Importfilter, ein Exportfilter hätte das Flag »EXPORT« , bei einem bidirektionaler Filter hätte der Entwickler beide angegeben.
Das Flag »ALIEN« weist den Filter (aus Sicht von Libre Office) als einen nicht-nativen Filter aus. Im Fall eines Exportfilters würde Libre Office beim Speichern eine Warnmeldung für einen potenziellen Informationsverlust anzeigen.
Filter-Dienst
Der Wert von »FilterService« nennt den für die Konvertierung des Dokuments verwendeten Service und muss genau mit dem Namen in der Implementierung des Filters übereinstimmen. UNO-Komponenten verwenden Java-ähnliche Bezeichner: Der erste Teil »com.sun.star.comp.Draw« weist auf eine Komponente hin, die eine Zeichnung konvertiert, und »MSPubImportFilter« ist der eigentliche Name des Filters. Der Wert aus »UIName« wird in der grafischen Benutzeroberfläche verwendet, so zum Beispiel im Dialog zur manuellen Filterauswahl, der erscheint, wenn Libre Office für eine Datei keinen bestimmten Importfilter bestimmen konnte.
»DocumentService« benennt jene Komponente, die Empfänger des umgewandelten Dokuments sein soll. In diesem Fall wird eine Microsoft-Publisher-Datei als Zeichnung in Libre Office Draw importiert, deshalb lautet der Wert »com.sun.star.drawing.DrawingDocument« . Würde es sich um ein Textdokument handeln, so ließe sich »com.sun.star.text.TextDocument« verwenden.
Der Wert der »Type« -Eigenschaft benennt den Dateityp, den der Filter versteht. Er muss mit dem »oor:name« -Attribut der entsprechenden Dateitypbeschreibung übereinstimmen und sollte mit dem Namen der entsprechenden Teilapplikation beginnen. Im vorliegenden Beispiel hört er auf den Namen »draw_Publisher_Document« . Zum Vergleich heißt der Dateityp für das Wordperfect-Format in Libre Office »writer_WordPerfect_Document« . Mit diesem Wissen erklärt sich auch das zweite XML-Fragment mit der Dateityp-Definition (Listing 2).
Detektivisch
Der »DetectService« benennt den Service, der entscheidet, ob eine Datei tatsächlich vom passenden Dateityp ist. Im vorliegenden Fall erledigt der Service »com.sun.star.comp.Draw.MSPUBImportFilter« beide Aufgaben, also sowohl Konvertierung als auch die Dateiformaterkennung. Die Eigenschaft »Extensions« listet – getrennt durch ein Semikolon – die möglichen Datei-Endungen auf.
Bei Exportfiltern verwendet Libre Office beim Speichern mit aktivierter Option »Automatische Dateiendung« die an erster Stelle gelistete Erweiterung. »MediaType« entspricht dem Mime-Typ des Dateiformats. Die Eigenschaft »PreferredFilter« verknüpft den Dateityp mit dem entsprechenden Dokumentfilter.
Publisher erkennen
Libre Office übergibt eine zu öffnende Datei dem Filter mit dem internen Namen »Publisher Document« , wenn es den Dateityp mit Hilfe der Typerkennung als »draw_Publisher_Document« identifiziert hat. Den »UIName« bekommt der Anwender dann im Datei-Auswahldialog angezeigt.
Ist die Konfiguration erledigt, geht es an das Bauen des C++-Gerüsts. Da der Filter sowohl die Konvertierung als auch die Typerkennung leisten soll, muss er auch zwei separate Services bereitstellen: »com.sun.star.document.ImportFilter« und »com.sun.star.document.ExtendedTypeDetection« . Bei einem Exportfilter wäre der entsprechende Service »com.sun.star.document.ExportFilter« .
In beiden Fällen muss der Entwickler das Interface »com::sun::star::document::XFilter« implementieren. Bei einem Exportfilter käme das Interface »com::sun::star::document::XExporter« hinzu, bei einem Importfilter »com::sun::star::document::XImporter« . Außerdem bedarf es der Initialisierung des Interface »com::sun::star::lang::XInitialization« . Da es sich bei dem Filter um einen UNO-Service handelt, sollte auch das Interface »com::sun::star::lang::XServiceInfo« bereitgestellt sein.
Filtern oder abbrechen
Für den Importfilter selbst sind zwei Funktionen des Xfilter-Interface nötig: »filter« und »cancel« . Die Cancel-Funktion im vorliegenden Beispiel tut einfach gar nichts, aber die Filter-Funktion übernimmt die eigentliche Konvertierung:
sal_Bool SAL_CALL MSPUBImportFilter::filter(const Sequence<PropertyValue>& aDescriptor)
Zuerst besorgt sich der Entwickler eine Referenz auf den Input-Stream, der dem zu importierenden Dokument entspricht. »aDescriptor« ist eine Sequenz aus Name-Wert-Paaren, der Operator »>>=« extrahiert einen Wert vom UNO-Typ Any (der – wie der Name schon andeutet – verschiedenste Typen beinhalten darf) und konvertiert ihn in den Typ der Variablen auf der Empfängerseite:
sal_Int32 nLength = aDescriptor.getLength(); const PropertyValue *pValue = aDescriptor.getConstArray(); OUString sURL; Reference <XInputStream> xInputStream; for (sal_Int32 i = 0 ; i <nLength; i++) if (pValue[i].Name == "InputStream") pValue[i].Value>>= xInputStream;
Als Nächstes muss der Entwickler den Importservice angeben, der das konvertierte Dokument in Form von SAX-Nachrichten empfängt. SAX ist der XML-Parser in Libre Office. Für jedes XML-Element, sowohl öffnend als auch schließend, für jeden Cdata-Content und so weiter erzeugt er eine Nachricht. Die wird als Funktionsaufruf in dem benutzenden Code abgebildet [4]. Der Service »com.sun.star.comp.Draw.XMLOasisImporter« akzeptiert XML entsprechend der OpenDocument-Graphics-XML-Spezifikation:
OUString sXMLImportService ("com.sun.star.comp.Draw.XMLOasisImporter");
Reference <XDocumentHandler>
xInternalHandler(
comphelper::ComponentContext(mxContext).createComponent(sXMLImportService),
UNO_QUERY);
Der »XImporter« richtet ein leeres Zieldokument ein, in das der »XDocumentHandler« das Ergebnis schreiben kann:
Reference <XImporter> xImporter(xInternalHandler, UNO_QUERY_THROW); xImporter->setTargetDocument(mxDoc);
Jetzt ist alles vorbereitet, der eigentliche Filter lässt sich dazwischenschalten, der die Daten über den »xInputStream« einliest und das erstellte XML dem »xInternalHandler« übergibt. Die »filter()« -Funktion sollte bei erfolgreicher Ausführung den Wert »true« zurückliefern, sonst den Wert »false« . Ist auch das erledigt, fehlt noch die »setTargetDocument()« -Funktion des »XImporter« -Interface:
void SAL_CALL MSPUBImportFilter::setTargetDocument(const Reference<XComponent>& xDoc)
{
mxDoc = xDoc;
}
In diesem Fall speichert der Code die Referenz zu dem »XComponent»« lediglich in einer Member-Variablen, die bereits im vorherigen Codelisting Anwendung fand, um das Ziel für das importierte Dokument einzurichten. Damit ist die Integration eines Importfilters fertig, für Exportfilter müsste der Entwickler noch die Funktion »setSourceDocument()« aus dem »XEporter« -Interface analog zu »setTargetDocument()« implementieren.
Xexport-Filter
Es gibt noch einen alternativen Weg, um Filter in Libre Office zu integrieren, nämlich mit den Interfaces »com::sun::star::xml::XExportFilter« und »com::sun::star::xml::XImportFilter« , den grobschlächtigen Geschwistern der oben beschriebenen Variante. Der Unterschied ist, dass der Filter-Service in der Konfigurationsdatei immer »com.sun.star.comp.Writer.XmlFilterAdaptor« lautet und der eigentliche Filter sowie Quell- und Zielservices in einer »UserData« -Eigenschaft der Konfiguration anzugeben sind.
Die obige Beschreibung der XML-Konfigurationsabschnitte erwähnte bereits, dass die »com.sun.star.comp.Draw.MSPUBImportFilter« -Komponente auch die Dateityperkennung leistet. Daher muss der Entwickler das Interface »com:: sun::star::document::XExtendedFilterDetection« unterstützen und damit auch dessen »detect()« -Funktion (Listing 3). Der Rückgabewert dieser Funktion ist entweder der entsprechende Dateitypname, so wie in der Definition angegeben, oder ein leerer String für den Fall, dass der Dateityp unklar bleibt.
Listing 3
detect
01 OUString SAL_CALL MSPUBImportFilter::detect(Sequence<PropertyValue>& Descriptor)
02 {
03 OUString sTypeName;
04 sal_Int32 nLength = Descriptor.getLength();
05 sal_Int32 location = nLength;
06 const PropertyValue *pValue = Descriptor.getConstArray();
07 Reference <XInputStream> xInputStream;
08 for (sal_Int32 i = 0 ; i <nLength; i++)
09 if (pValue[i].Name == "TypeName")
10 location=i;
11 else if (pValue[i].Name == "InputStream")
12 pValue[i].Value>>= xInputStream;
Descriptoren
Wie bei der »filter()« -Funktion ermittelt das Programm auch hier zunächst eine Referenz auf den Input-Stream, allerdings mit dem Unterschied, dass es bei dieser Gelegenheit auch den Index auf die »TypeName« -Eigenschaft speichert, um im Falle einer erfolgreichen Dateityperkennung (der »sTypeName« bekommt dann den entsprechenden String zugeweisen) den »Descriptor« entsprechend aktualisieren zu können. Rückgabewert ist – wie zuvor erwähnt – entweder der Bezeichner des Dateityps oder der leere String (Listing 4).
Listing 4
Ein Descriptor
01 if (!sTypeName.isEmpty())
02 {
03 if (location == Descriptor.getLength())
04 {
05 Descriptor.realloc(nLength+1);
06 Descriptor[location].Name = "TypeName";
07 }
08 Descriptor[location].Value <<=sTypeName;
09 }
10 return sTypeName;
11 }
Zu behaupten, dass dies schon alles sei, um einen Filter in Libre Office zu integrieren, wäre geprahlt. Ein wenig Programmierarbeit ist noch nötig, es fehlen noch zwischen zehn und 50 Zeilen Code des Standard-UNO-Gerüsts, außerdem eine XML-Datei und ein paar Makefile-Änderungen, um die Filterkomponente während des Build zu integrieren. Aber diese Änderungen sind trivial und lassen sich recht schnell anhand bestehender Filter nachbauen.
Tiefer einsteigen mit Vorlagen und Community
Eine gute Vorlage bietet beispielsweise das »writerperfect« -Modul aus dem Libre-Office-Sourcecode. Wem es jetzt in den Fingern juckt und wer noch Fragen hat, dem stehen zahlreiche Spezialseiten sowohl auf der Entwickler-Mailingliste unter »libreoffice@lists.freedesktop.org« als auch im IRC-Channel »#libreoffice-dev« auf Freenode bereit, um beim Einstieg zu helfen. Einfach vorbeischauen, es gibt nichts zu verlieren.
Infos
- Markus Feilner, “Große Konstruktion”, Linux-Magazin 04/13, S. 24
- ODF Alliance: http://www.odfalliance.org
- Extensions für Libre Office: http://extensions.libreoffice.org
- SAX: http://de.wikipedia.org/wiki/Simple_API_for_XML#Ereignisse_in_SAX







