Wie kann man den Inhalt einer JTable zu LibreOffice-Writer in das ODT-Format exportieren?

Mit dem ODF Toolkit der Apache-Foundation lassen sich Dateien des Open Document Text (*.odt)-Formats erstellen und manipulieren.

Vorbereitung

Um die Bibliothek nutzen zu können, muss sie zunächst unter https://incubator.apache.org/odftoolkit/downloads.html heruntergeladen und dem Classpath hinzugefügt werden. Das Paket enthält eine Reihe *.jar-Archive, von denen für das vorliegende Beispiel die folgenden Verwendung finden und eingebunden werden müssen:

Das GUI

Die Beispielklasse erbt von JFrame. Sie zeigt ein einfaches Fenster, in dem eine JTable und darunter ein JButton zu sehen sind. Das GUI wird in der im Konstruktor aufgerufenen Methode initGUI() erzeugt. Die Aktivität des Buttons ruft die Methode expToODT() auf, die für den Export der gezeigten Tabelle in eine *.odt-Datei verantwortlich zeichnet.

In initGUI() werden zunächst zwei String-Arrays für die Bezeichnungen der Spaltenköpfe und den Inhalt der Tabelle erzeugt. Sie werden dem Konstruktor eines DefaultTableModel übergeben, das wiederum anschließend bei der Instanzierung der JTable Verwendung findet.
Nach der Erzeugung wird die Tabelle etwas formatiert, indem eine Hintergrundfarbe angegeben und die Sichtbarkeit des Tabellenrasters gesetzt werden. Durch setAutoCreateRowSorter(true) wird zudem sichergestellt, dass durch Klick auf die Spaltenköpfe eine einfache Sortierung ermöglicht wird.
Nachdem die Tabelle dem Frame hinzugefügt wurde, wird der Button erzeugt, bei einem ActionListener angemeldet und ebenfalls unten in das GUI eingefügt. Wie oben erwähnt wird bei seiner Betätigung expToODT() aufgerufen. Der Methode wird der String des Namens der Datei übergeben, in die der Export erfolgen soll.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.util.GregorianCalendar;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

import org.odftoolkit.simple.TextDocument;
import org.odftoolkit.simple.style.DefaultStyleHandler;
import org.odftoolkit.simple.style.Font;
import org.odftoolkit.simple.style.StyleTypeDefinitions.FontStyle;
import org.odftoolkit.simple.style.StyleTypeDefinitions.HorizontalAlignmentType;
import org.odftoolkit.simple.table.Cell;
import org.odftoolkit.simple.table.Table;
import org.odftoolkit.simple.text.Paragraph;

public class JTableToODT extends JFrame {

    private JTable table;
    private Object[] head;

    public JTableToODT() {
        initGUI();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("Tabelle zu ODT");
        this.setSize(600, 400);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private void initGUI() {
        head = new Object[]{ "eins", "zwei", "drei", "vier", "f\u00FCnf" };
        Object[][] content = new Object[20][5];
        for (int i = 0; i < content.length; i++) {
            for (int j = 0; j < content[i].length; j++) {
                content[i][j] = (i + 1) + "." + (j + 1);
            }
        }
        DefaultTableModel model = new DefaultTableModel(content, head);
        table = new JTable(model);
        table.setShowGrid(true);
        table.setAutoCreateRowSorter(true);
        table.setBackground(Color.LIGHT_GRAY);
        this.add(new JScrollPane(table), BorderLayout.CENTER);
        JButton butt = new JButton("Export");
        butt.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                expToODT("Tabelle.odt");
            }
        });
        this.add(butt, BorderLayout.SOUTH);
    }

    public void expToODT(final String fileName) {
        final DefaultTableModel model = (DefaultTableModel) table.getModel();
        final int clmCnt = table.getColumnCount();
        final int rowCnt = table.getRowCount();
        Thread t = new Thread() {
            @Override
            public void run() {
                TextDocument outputOdt = null;
                try {
                    outputOdt = TextDocument.newTextDocument();

                    // Header
                    Paragraph para = Paragraph.newParagraph(outputOdt);
                    para.applyHeading();
                    DateFormat df = DateFormat.getDateTimeInstance(
                            DateFormat.SHORT, DateFormat.SHORT);
                    para.setTextContent("Testtabelle - "
                            + df.format(new GregorianCalendar().getTime()));
                    outputOdt.getParagraphByIndex(0, false).remove();

                    // Tabelle
                    Table table = outputOdt.addTable(rowCnt + 1, clmCnt);
                    Cell cell;
                    Object value;
                    DefaultStyleHandler styleHandler;
                    Font normal = new Font("Helvetica", FontStyle.REGULAR, 8);
                    Font header = new Font("Helvetica", FontStyle.BOLD, 9);
                    for (int i = 0; i <= rowCnt; i++) {
                        for (int j = 0; j < clmCnt; j++) {
                            cell = table.getCellByPosition(j, i);
                            styleHandler = cell.getStyleHandler();
                            if (i == 0) {
                                styleHandler.getTextPropertiesForWrite()
                                        .setFont(header);
                                cell.setStringValue(head[j].toString());
                            } else {
                                value = model.getValueAt(
                                        JTableToODT.this.table
                                                .convertRowIndexToModel(i - 1),
                                        j);
                                cell.setHorizontalAlignment(
                                        HorizontalAlignmentType.LEFT);
                                styleHandler.getTextPropertiesForWrite()
                                        .setFont(normal);
                                cell.setStringValue(
                                        value == null ? "" : value.toString());
                            }
                        }
                    }
                    outputOdt.save(fileName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
    }

    public static void main(String[] args) {
        new JTableToODT();
    }
}

Export im Open Document Format

Die Erzeugung eines Dokumentes im Open Document Format, das Einfügen des Inhaltes und das Abspeichern in einer *.odt-Datei findet in der Methode expToODT() statt.
Hier wird zunächst das TableModel geholt, sowie die Anzahl der Spalten und Reihen der Tabelle ermittelt. Der eigentliche Export findet dann in einem eigenen Thread statt, um ihn funktional vom EDT zu entkoppeln. Zudem ist der gesamte Vorgang in einen try-catch-Block eingeschlossen, um Fehler bei den Erzeugungs- und Ausgabevorgängen abzufangen.

Als erstes wird nun ein neues Objekt vom Typ org.odftoolkit.simple.TextDocument erzeugt und diesem ein neuer Absatz (Paragraph) angefügt. Er wird als Überschrift erster Ordnung formatiert und mit Inhalt versehen.
In der Folge wird dann der erste Absatz ermittelt und gelöscht. Hier muss angemerkt werden, dass es eigenartigerweise nicht möglich war, diesen ersten Absatz zu Beginn zu ermitteln und dann sofort als Überschrift zu formatieren. Die Formatierung wurde bei diesem Verfahren nicht angenommen.

Paragraph para = outputOdt.getParagraphByIndex(0, false);
para.applyHeading();  // hier keine Header-Formatierung

Löscht man hingegen den ersten Absatz nicht, so bleibt ein leerer Absatz vor der Überschrift erhalten.

Um die Tabelle im Dokument zu erzeugen, werden einige Objekte gebildet. Zentral sind hier die Klassen Table und Cell des Package org.odftoolkit.simple.table sowie org.odftoolkit.simple.style.DefaultStyleHandler zur Formatierung der Erscheinung des Zelleninhaltes.
Nach der Erzeugung zweier Font-Objekte zur Formatierung des Tabelleninhaltes wird in einer geschachtelten Schleife dann die Tablelle Zelle für Zelle durchlaufen, das jeweilige Cell-Objekt anhand seiner Position in der Tabelle über die Indices ermittelt und dann bearbeitet. Hierzu wird auch der DefaultStyleHandler der Zelle benötigt.
Die erste Zeile der Tabelle enthält den Tabellenkopf. In dessen Zellen werden die Überschriften der einzelnen Spalten gesetzt und diese mit einer fetten Schrift formatiert. Alle anderen Zellen erhalten den Inhalt des DefaultTableModel.

An dieser Stelle ist ein zentraler Aspekt des Umgangs mit Tabellen in Java zu erkennen: Da im Sinne des Model-View-Controller-Patterns der Inhalt und die Erscheinung einer Tabelle strikt getrennt sind, kann es z.B. nach einer Sortierung des TabellenGUI vorkommen, dass die Reihenfolge der Zeilen in der Tabellendarstellung nicht mehr jener im Modell enspricht. Die Methode convertRowIndexToModel() der Klasse JTable erhält als Parameter den Zeilenindex der Tabellendarstellung und gibt den Index der zugehörigen Modellzeile zurück. Dies ist notwendig, um den der Tabellendarstellung entsprechenden Inhalt der Zelle aus dem Tabellenmodell zu ermitteln und im Object value zwischenzuspeichern. Geschähe dies nicht, so bezöge sich die aktuell ausgelesene Position auf den ursprünglich an dieser Position gespeicherten Inhalt des Modells.

Die Zelle wird dann linksbündig und mit dem erzeugten Font-Objekt formatiert und schließlich der oben aus dem Modell ermittelte Zellenwert eingefügt.
Nach Durchlauf der Schleife sind alle Werte übertragen und das Dokument wird durch save() gespeichert. Die Methode erzeugt die benötigte Datei selbsttätig. Sie muss vorher nicht existieren.