Aus Linux-Magazin 10/2003

Open-Office-Dokumente mit Java verarbeiten

Ohne Kaffee läuft in deutschen Büros nichts. Der Beitrag beweist, dass auch Open Office mit Java viel leistet: Das Open-Office-SDK liefert ein Rezept für das belebende Gebräu.

Office-Suiten, hier ist Open Office zuerst zu nennen, sind Funktionsmonster: Der Anwender braucht zwar nur fünf Prozent aller Funktionen – aber jeder andere fünf Prozent. Bei recht speziellen Aufgaben in Firmenumgebungen – und das ist nicht selten – reichen selbst die vielen in Open Office eingebauten Funktionen nicht. Wie beim Stiefbruder von Microsoft hilft gewöhnlich ein Basic-Programm, das innerhalb des Office-Programms abläuft.

Das Verfahren setzt aber einen Benutzer voraus, der Office manuell bedient – widersinnig eingedenk der Automatisierungsfreundlichkeit und Flexibilität von Linux. Das “Open Office Software Development Kit” bringt Abhilfe, denn es schafft die Möglichkeit, sowohl interne Funktionen (beispielsweise zusätzliche Import/Export-Filter) zu erweitern als auch Office aus Fremdprogrammen heraus fernzusteuern.

Dieser Coffee-Shop erklärt am Beispiel eines Java-Programms, das ein einfaches Spreadsheet-Dokument erzeugt, die zugrunde liegende Architektur. Eine Erweiterung zu einem Servlet, das komplexere Szenarien abbildet, ist mit geringem Aufwand machbar.

Open Office muss als Objekt-Server laufen

Damit eigene Programme mit ihm kommunizieren können, muss Open Office im Servermodus laufen (siehe unten). Als Server stellt Office seine Funktionalität über Objekte bereit, deren Methoden remote aufrufbar sind. Die Objekte und Methoden bilden das API des Open-Office-SDK. In Open-Office-Sprache heißen diese Objekte Universal Network Objects, kurz UNO. Wer RMI und insbesondere CORBA kennt, bemerkt sofort die große Ähnlichkeit.

Genau genommen ist die UNO-Architektur nur eine proprietäre Version der CORBA-Architektur. Das geht so weit, dass UNO seine Objekte wie auch CORBA über eine Interface Definition Language (IDL) definiert. Programmierer haben damit die Wahl zwischen Java, C++, Open Office.org Basic, Python und OLE für die Implementation (Language Bindings).

Eigene Begriffe im API für Factories & Co.

Das Open-Office-API bedarf der Gewöhnung, weil es eine ganze Reihe eigener Begrifflichkeiten verwendet: Factories heißen darin zum Beispiel Service Manager. Ein »context« ist die logische Ablaufumgebung von Objekten. Wie bei allen verteilten Anwendungen muss das lokale Programm einen lokalen Stub für ein entferntes Objekt erzeugen, damit dies die Methodenaufrufe für den Programmierer transparent weiterleitet. Die erwähnten Service Manager haben dazu geeignete »create«-Methoden.

Erster Schritt jedes UNO-Programms ist es daher, einen Service Manager für Remote-Objekte zu erhalten. Weitere Remote-Objekte sind dann über dessen Factory-Methoden zugänglich. Auch wenn die Architektur im ersten Moment kompliziert klingt: Die Praxis ist einfach, insbesondere bei vorhandenen Codebeispielen. Den Beweis dafür will dieser Coffee-Shop antreten. Der vorgestellte Code ist hauptsächlich per Cut & Paste entstanden, die Vorlagen sind Teil des Developer-Guide des SDK.

Abbildung 1: Konfiguration der JVM für Open Office per »jvmsetup«.

Abbildung 1: Konfiguration der JVM für Open Office per »jvmsetup«.

Download und Installation

Die Open-Office-Website[1] stellt das SDK zum Download bereit (UDK-Projekt). Das Paket umfasst 26 MByte, ausgepackt sind es 105 MByte. Startpunkt für die sehr gute Dokumentation ist die Datei »index.html« im SDK-Wurzelverzeichnis. Wer eigene Programme entwickeln will, ist jetzt schon fertig, denn die eigentlichen Programmbibliotheken (für Java: JAR-Dateien) bringt Open Office mit und nicht das SDK (mit einer Ausnahme, die unten erläutert wird).

Entwickler, die allerdings ein mitgeliefertes Beispiel ausprobieren wollen, sollten den Installationsanweisungen folgen. Der wesentliche Ablauf umfasst den Aufruf des »configure«-Skripts, das ein Startskript erzeugt, das wiederum alle notwendigen Umgebungsvariablen setzt. Ausgangspunkt für die Beschäftigung mit dem SDK ist der Developers Guide. Er liegt sowohl im HTML-, als auch im gut druckbaren PDF-Format mit 898 Seiten vor.

Vorteil der HTML-Version ist, dass sie mit der restlichen API-Dokumentation sauber verlinkt ist – in beiden Richtungen. Achtung Konqueror-Benutzer: Einige Seiten der SDK-Dokumentation sind so groß, dass sie zuerst den Konqueror und dann den gesamten KDE-Desktop lahm legen (beim Autor KDE 3.1.1 und Konqueror 3.1.1 auf SuSE 8.2).

Abbildung 2: In Open Office muss Java freigeschaltet sein, damit das Beispiel funktioniert.

Abbildung 2: In Open Office muss Java freigeschaltet sein, damit das Beispiel funktioniert.

Vorarbeiten: Java aktivieren, Pfad setzen, Servermodus

Drei Voraussetzungen sind für ein erfolgreiches Java-OO-Projekt notwendig: In Open Office muss Java aktiviert sein – was meist schon während der Installation geschehen ist -, die JAR-Dateien von Open Office gehören in den »CLASSPATH« und außerdem muss Open Office im Servermodus laufen.

Java soll in einer Version 1.3.1_02 oder höher vorliegen, das gezeigte Beispiel funktioniert beim Autor ohne Probleme mit Java 1.4.1_02. Für das Java-Setup gibt es ein eigenes Programm: »/opt/ OpenOffice.org/program/jvmsetup«; je nachdem, wo Open Office installiert ist, heißt das Wurzelverzeichnis anders als »/opt/OpenOffice.org«. Die Abbildung 1 zeigt den Konfigurationsdialog.

Nachdem Java konfiguriert ist, muss es der Bediener noch freischalten. Das geschieht im laufenden Open Office über das Menü »Extras | Optionen« und dann im Baum »OpenOffice | Sicherheit« (siehe Abbildung 2).

Pfad der IDE bekannt geben

Die für Open Office erforderlichen JAR-Dateien liegen unter »/opt/OpenOffice .org/program/classes/«. Das Verzeichnis muss man dem Projekt in der IDE (etwa Eclipse) bekannt geben. Wer Ant verwendet, kann auch den Codeschnipsel aus Listing 1 verwenden, um den »CLASSPATH« zu definieren. Das komplette Build-Skript ist zusammen mit dem Java-Programm auf dem Linux-Magazin-Server[2] verfügbar.

Listing 1: »build.xml«

042 <!-- ===== Compilation Classpath ==== -->
043
044   <path id="compile.classpath">
045     <pathelement location="${build.home}/classes"/>
046     <fileset dir="${oo.home}/program/classes">
047       <include name="*.jar"/>
048     </fileset>
049   </path>

Listing 2: »Setup.xml«: Start im Servermodus

01 <ooSetupConnectionURL cfg:type="string">
02   socket,port=8100;urp;
03 </ooSetupConnectionURL>

Server-Start

Zum Schluss bleibt der Start von Open Office als Objekt-Server. Wichtig ist es, vorher alle Instanzen zu beenden, besonders solche von schlummernden Schnellstartprogrammen, die Open Office im Hintergrund laufen lassen. Mit

/opt/OpenOffice.org/program/soffice 
"-accept=socket,port=8100;urp;"

startet Open Office dann im Servermodus. Das Fenster mit einem leeren Textdokument kann man getrost in den Hintergrund schicken. Ein

netstat -an | grep 8100

zeigt, ob ein Programm (hoffentlich Open Office) am Port 8100 lauscht. Office könnte prinzipiell auch auf einem anderen Rechner im Netzwerk laufen. Der Zugriff erfolgt wie bei CORBA transparent über das Netz.

Wer Open Office immer im Servermodus starten will, kann die interne Konfiguration ändern, entweder global oder für einzelne Benutzer. Dazu muss er den Abschnitt aus Listing 2 in die Datei »/opt/OpenOffice.org/share/config/regis-try/instance/org/openoffice/Setup.xml« beziehungsweise die »~/OpenOffice.org /user/config/registry/instance/org/open- office/Setup.xml« einpflegen.

Jetzt ist alles vorbereitet und es geht endlich ans Programmieren: Das Java-Programm aus Listing 3 soll ein Spreadsheet erzeugen, in die beiden oberen Zellen von Spalte A jeweils die Zahl 42 eintragen (sie ist bekanntlich die Antwort auf alle Fragen) und von der Tabellenkalkulation addieren lassen. Anschließend speichert das Programm die Tabelle. Die nächsten Abschnitte erläutern den Programmcode.

Der Remote Service Manager

Der Konstruktor in den Zeilen 64 bis 67 ruft die »getRemoteContext()«-Methode auf. Darin (Zeilen 75 bis 99) passiert die gesamte UNO-Magie. Dieser Teil des Programms ist am schwersten zu verstehen: Zuerst erzeugt das Programm mit »initContext« einen lokalen Kontext und daraufhin einen lokalen Service Manager. Wie beschrieben sind Service Manager Factory-Objekte, damit dient der lokale Service Manager nur dem Erzeugen eines URL-Resolvers, der letztlich in Zeile 85 den Kontakt zum Open-Office-Server aufnimmt.

Damit ist ein erstes entferntes Objekt vorhanden. Über eine Reihe von Zwischenschritten erhält der Entwickler letztlich den gewünschten Remote Context. In dieser (und auch in den anderen) Methode(n) gibt es immer wieder mysteriöse Aufrufe von »UnoRuntime .queryInterface()«. Sie entsprechen den »narrow()«-Aufrufen in CORBA-Programmen und dienen dazu, den richtigen Typ des Objekts zu liefern. (Das erste Argument fungiert dabei als Klassenobjekt der gewünschten Klasse).

Spreadsheets

Nachdem mittels »RemoteContext« der »RemoteServiceManager« erzeugt ist, ist das Spreadsheet Gegenstand des Interesses. Das Objekt-Modell von Open Office ist zunächst etwas verwirrend: Es kennt eine »XComponent« samt zugeordne- tem »XModel«, ein »XSpreadsheetDocument«, die Kollektion aller Tabellen eines Spreadsheets »XSpreadsheets« sowie eine einzelne Tabelle (»XSpreadsheet«). In der »createSpreadsheet()«-Methode hangeln sich die Zeilen 107 bis 129 von Objekt zu Objekt.

Interessant wird es in der »useSpreadsheet()«-Methode. Die Zeilen 137 bis 160 füllen Zellen mit Werten und formatieren die Zellen. Die letzten vier Zeilen holen die neu erstellte Tabelle in den Vordergrund – für ein echtes Batch-Programm eher unwichtig.

Sichern und Speichern

Die letzten beiden Methoden, also »store-Spreadsheet()« und »closeDocument()« sind generell nutzbar. Natürlich muss der Filter (Zeilen 177 bis 178) dem Dokumenttyp entsprechen. Das Spreadsheet wäre durch Ändern einer einzigen Zeile (178) auch als HTML-Datei oder auch im Excel-Format abspeicherbar. Mit »closeDocument()« verschwindet das Dokument vom Bildschirm.

Das ganze Programm läuft auf einem betagten 700-MHz-Duron so schnell, dass man gerade nur das Öffnen und Schließen des Office-Dokuments mitbekommt. Deshalb darf dieses Beispielprogramm auch auf eine ausgefeilte Fehlerbehandlung verzichten. Jede Methode geht davon aus, dass die erzeugten und in privaten Feldern gespeicherten Referenzen auf die verschiedenen Objekte stets gültig sind. Im Allgemeinen ist dies aber nicht der Fall, so könnte ein Benutzer ein eben erzeugtes Dokument über Maus oder Tastatur schließen. Die Methoden des Open-Office-API werfen dann Exceptions.

Mit diesem Handwerkszeug und den auführlichen Beispielen aus dem SDK lassen sich viele Anwendungen des Typs “Automatische Dokumenterzeugung und -verarbeitung” erstellen. Stöbern lohnt sich! Open Office bietet den Java-Programmierern aber mehr.

Office Beans

In den letzten Jahren wurde immer mal wieder der Versuch unternommen, eine Office-Suite rein in Java zu implementieren. Alle derartige Experimente sind gescheitert. Den Grund darf man in der schieren Größe der Aufgabe suchen, denn eine Office-Suite, die annähernd mit den vorhandenen Alternativen konkurriert will, schreibt sich nicht von heute auf morgen. Letztlich lohnt sich der Aufwand für eine Firma, die Geld verdienen muss, nicht.

Dazu kommt, dass es einfach Aufgaben gibt, für die Java ungeeignet ist. Schon das in C++ implementierte Open Office ist manchmal träge, eine Java-Version würde die Geduld überstrapazieren. Java-Entwickler, die bislang in ihrer Desktop-Anwendung eine leistungsfähige Tabellenkalkulation oder Textverarbeitung brauchten, konnten auf keine generische Lösung zurückgreifen, die austauschbare Dokumente erzeugt (es gab nur einige auf Swing-Komponenten aufsetzende Klassen für Tabellen und Texte). Diese Lücke füllt Office Bean, eine Java-Bean-Komponente.

Office Bean kapselt die ganze Komplexität des Open-Office-API. Darüber hinaus können Office-Dokumente in normale Java-Programme eingebettet werden. Die Abbildung 3 zeigt das Resultat. Nur wer genau hinsieht (am besten auf die Buttons am unteren Bildschirmrand), erkennt, dass es sich überhaupt um ein Java-Programm handelt.

Der Screenshot zeigt das von dem Programm oben erzeugte Spreadsheet und verwendet ein Beispiel des SDK. Die Office-Bean-Komponenten, eine JAR-Datei sowie eine Reihe nativer Bibliotheken sind nicht in der normalen Open-Office-Distribution enthalten, sondern kommen mit dem SDK. Installation, Konfiguration und Gebrauch der Office Bean dokumentiert das SDK detailliert.

Abbildung 3: Ein Open-Office-Dokument, in ein Java-Programm eingebettet - was nur bei genauem Hinsehen erkennbar ist. Wieder handelt es sich um ein modifiziertes Programmierbeispiel aus dem SDK.

Abbildung 3: Ein Open-Office-Dokument, in ein Java-Programm eingebettet – was nur bei genauem Hinsehen erkennbar ist. Wieder handelt es sich um ein modifiziertes Programmierbeispiel aus dem SDK.

finally{}

Dieser Coffee-Shop hat gezeigt, dass das leistungsfähige Office-Paket der Open-Source-Community einfach in eigene Programme integrierbar ist. Wie immer wurden die Möglichkeiten nur kurz gestreift, was aber kein Problem ist, denn das SDK kommt mit sehr guter Dokumentation und vielen Beispielen.

Trotzdem bleibt auch etwas zu kritisieren: Wer Open-Office-Dokumente im Batch – etwa übers Web – erzeugen und verarbeiten will, braucht eine laufende Office-Instanz und somit auch einen X-Server. Ersteres kann kritisch werden, denn bei Versuchen des Autors verabschiedete sich Open Office gern hin und wieder. Und einen X11 will nicht jeder auf seiner Server-Maschine laufen haben – X-Server sind schließlich Programme für Client-Rechner. Eventuell schafft der virtuelle X-Server hier Abhilfe.

Die beste Alternative wäre das Erzeugen und Verarbeiten von Open Office außerhalb einer Open-Office-Umgebung. Das ist prinzipiell möglich. Die gespeicherten Daten liegen dann letztlich (komprimiert) als XML-Dateien vor und Java-Programme könnten sie mit SAX oder DOM verarbeiten, auch XSLT ist möglich. Auf[3] gibt es in dieser Richtung schon einige Ergebnisse, jedoch keine, die dem Java-API zum HSSF (Microsofts Horrible Spreadsheet Format) entsprächen. (jk)

Listing 3: »JavaSpreadsheet.java«

023 package de.bablokb.oo;
024
025 import com.sun.star.bridge.*;
026 import com.sun.star.uno.*;
027 import com.sun.star.sheet.*;
028 import com.sun.star.table.*;
029 import com.sun.star.lang.*;
030 import com.sun.star.connection.*;
031 import com.sun.star.comp.helper.*;
032 import com.sun.star.beans.*;
033 import com.sun.star.frame.*;
034 import com.sun.star.util.*;
035
044 public class JavaSpreadsheet {
045
046   private static final String UNO_URL =
047     "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager";
048   private static final String TABLE_NAME = "Java";
049   private static final String DOCUMENT_FILENAME = "file:///tmp/oo-java.sxc";
050
051   private XComponentContext      iRemoteContext = null;
052   private XMultiComponentFactory iRemoteServiceManager = null;
053   private XComponent             iSpreadsheetComponent = null;
054   private XSpreadsheetDocument   iSpreadsheetDocument = null;
055   private XModel                 iSpreadsheetModel = null;
056   private XSpreadsheets          iSpreadsheets = null;
057
064   public JavaSpreadsheet() throws java.lang.Exception {
065     getRemoteContext();
066     iRemoteServiceManager = iRemoteContext.getServiceManager();
067   }
068
075   private void getRemoteContext() throws java.lang.Exception {
076     try {
077       XComponentContext initContext = Bootstrap.createInitialComponentContext(null);
078       XMultiComponentFactory manager = initContext.getServiceManager();
079       Object urlResolver  = manager.createInstanceWithContext(
080        "com.sun.star.bridge.UnoUrlResolver",initContext);
081
082       XUnoUrlResolver xUnoUrlResolver = (XUnoUrlResolver) UnoRuntime.
083         queryInterface(XUnoUrlResolver.class,urlResolver);
084
085       Object initialObject = xUnoUrlResolver.resolve(UNO_URL);
086       XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
087         XPropertySet.class,initialObject);
088       Object context = xPropertySet.getPropertyValue("DefaultContext");
089       iRemoteContext = (XComponentContext)UnoRuntime.queryInterface(
090         XComponentContext.class,context);
091     } catch(NoConnectException nce) {
092       System.err.println( "No process listening on the resource" );
093       nce.printStackTrace();
094       throw nce;
095     } catch(DisposedException de) {
096       iRemoteContext = null;
097       throw de;
098     }
099   }
100
107   private void createSpreadsheet() throws java.lang.Exception {
108     Object desktop =
109       iRemoteServiceManager.createInstanceWithContext(
110         "com.sun.star.frame.Desktop", iRemoteContext);
111
112     XComponentLoader componentLoader =
113       (XComponentLoader) UnoRuntime.queryInterface(
114         XComponentLoader.class, desktop);
115
116     PropertyValue[] loadProps = new PropertyValue[0];
117     iSpreadsheetComponent = componentLoader.loadComponentFromURL(
118       "private:factory/scalc", "_blank", 0, loadProps);E
119
120     iSpreadsheetDocument =
121       (XSpreadsheetDocument) UnoRuntime.queryInterface(
122         XSpreadsheetDocument.class, iSpreadsheetComponent);
123
124     iSpreadsheets = iSpreadsheetDocument.getSheets();
125     iSpreadsheets.insertNewByName(TABLE_NAME, (short)0);
126
127     iSpreadsheetModel =
128       (XModel) UnoRuntime.queryInterface(XModel.class,iSpreadsheetComponent);
129   }
130
137   private void useSpreadsheet() throws java.lang.Exception {
138     Object sheet = iSpreadsheets.getByName(TABLE_NAME);
139     XSpreadsheet spreadsheet =
140       (XSpreadsheet) UnoRuntime.queryInterface(
141         XSpreadsheet.class, sheet);
142
143     XCell cell = spreadsheet.getCellByPosition(0, 0);
144     cell.setValue(42);
145     cell = spreadsheet.getCellByPosition(0, 1);
146     cell.setValue(42);
147     cell = spreadsheet.getCellByPosition(0, 2);
148     cell.setFormula("=sum(A1:A2)");
149
150     XPropertySet cellProps =
151       (XPropertySet) UnoRuntime.queryInterface(
152         XPropertySet.class, cell);
153     cellProps.setPropertyValue("CellStyle", "Result");
154
155     XController spreadsheetController = iSpreadsheetModel.getCurrentController();
156     XSpreadsheetView spreadsheetView =
157       (XSpreadsheetView) UnoRuntime.queryInterface(
158         XSpreadsheetView.class,spreadsheetController);
159     spreadsheetView.setActiveSheet(spreadsheet);
160   }
161
168   private void storeSpreadsheet() throws java.lang.Exception {
169     XStorable store =
170       ( XStorable ) UnoRuntime.queryInterface(XStorable.class,iSpreadsheetModel);
171
172     PropertyValue[] prop = new PropertyValue[2];
173     prop[0]       = new PropertyValue();
174     prop[0].Name  = "Overwrite";
175     prop[0].Value = new Boolean(true);
176     prop[1]       = new PropertyValue();
177     prop[1].Name  = "FilterName";
178     prop[1].Value = "StarOffice XML (Calc)";
179     store.storeAsURL(DOCUMENT_FILENAME,prop);
180     System.out.println("document saved");
181   }
182
189   private void closeDocument() throws java.lang.Exception {
190     iSpreadsheetComponent.dispose();
191     System.out.println("document closed");
192   }
193
200   public static void main(String[] args) {
201     try {
202       JavaSpreadsheet js = new JavaSpreadsheet();
203       js.createSpreadsheet();
204       js.useSpreadsheet();
205       js.storeSpreadsheet();
206       js.closeDocument();
207     } catch (java.lang.Exception e){
208       e.printStackTrace();
209     }
210     System.exit(0);
211   }
213 }

Infos

[1] Open-Office-Homepage: [http://www.openoffice.org]

[2] Build-Skript und Java-Programm zum Coffee-Shop: [https://www.linux-magazin.de/Service/Listings/2003/10/Coffeeshop]

[3] Das Filter-Projekt von Open Office: [http://xml.openoffice.org/filters.html]

Der Autor

Bernhard Bablok arbeitet bei der Allianz Versicherungs AG im Bereich Data-Warehouse-Systeme. Wenn er nicht Musik hört, mit dem Radl oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Objektorientierung. Er ist unter [mailto:coffee-shop@bablokb.de] zu erreichen.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben