Categories
Development Java

Docx4J

Ein Report soll als Word Dokument (.docx) verfügbar gemacht werden.

Dazu wird zuerst eine Word Datei als Template erstellt mit Platzhaltern an den entsprechenden Stellen. Die Ersetzung einzelner Wörter ist relativ trivial, das Befüllen einer Tabelle mit n Zeilen stellte sich dann etwas kniffliger heraus. Diese Notizen und das dazugehörige Beispielprojekt dienen als Erinnerungsstütze, falls der Zukunftsingo nach dem aktuellen Projekt noch einmal etwas mit docx4j machen darf.

Word Template erstellen

Word mit einem leeren Dokument öffnen, einen Platzhalter für eine Überschrift, einen Text und eine Tabelle hinzufügen.

Die Überschriften der Tabelle sind fix und werden nicht ersetzt.
Die Anzahl der Zeilen in dem Report ist dynamisch.
Die Tabelle erhält keine Zeilen, diese werden später hineingeneriert.

Würde der Report eine Tabelle mit einer fixen Anzahl von Zeilen vorsehen, könnte man die ganze Tabelle in das Dokument eintragen und und später die einzelnen Werte per Wortersetzung hineingenerieren.

Die Tabelle bekommt noch einen Titel, um sie so eindeutig identifizieren zu können. Beispielsweise wenn das Dokument mehrere Tabellen enthält.

Abschließend unter dem Namen template.docx als Word-Dokument speichern, nicht als Strict Open XML-Dokument!

Projekt einrichten

Ein neues Maven Projekt anlegen.

Die Datei template.docx nach src/main/resources kopieren.

Java Klasse anlegen, die das Template läd und eine neue Zieldatei im tmpdir mit teilgeneriertem Name anlegt. Dadurch erhalten wir bei jedem Durchlauf eine neue Datei, um sie ggf. noch einmal miteinander vergleichen zu können, und das Aufräumen übernimmt das Betriebssystem für uns.

Als Dummyaction wird das Template in die Zieldatei kopiert. An dieser Stelle kommt im nächsten Schritt Docx4J ins Spiel um das Template zu befüllen.

Im letzten Schritt lassen wir uns die Zieldatei mit Word öffnen um so direkt das Ergebnis überprüfen zu können.

public class Main {

    public static void main(String[] args) throws Exception {
        File template = getTemplateFile();
        File output   = getOutputFile();
        
        Files.copy(Paths.get(template.toURI()), Paths.get(output.toURI()), StandardCopyOption.REPLACE_EXISTING);

        Desktop.getDesktop().open(output);
    }
    
    private static File getTemplateFile() throws Exception {
        File template = new File( Main.class.getClassLoader().getResource("template.docx").toURI() );
        return template;
    }

    private static File getOutputFile() throws Exception {
        String tmpdir = System.getProperty("java.io.tmpdir");
        Path tmpfile = Files.createTempFile(Paths.get(tmpdir), "MyReport" + "-", ".docx");
        return tmpfile.toFile();
    }
}

Um das Template befüllen zu können legen wir einen Report mit Beispieldaten an:

public class Report {

    public String ueberschrift = "Sample Report";
    public String text = "Lorem Ipsum";
    public List<String[]> tabelle;
    
    public Report() {
        tabelle = new ArrayList<>();
        tabelle.add(new String[] {"Montag", "Regen", "4°C"});
        tabelle.add(new String[] {"Dienstag", "Wolkig", "6°C"});
        tabelle.add(new String[] {"Mittwoch", "Sonne", "10°C"});
    }
}

Docx4J

Projekt Homepage: Link

Projekt GitHub Repository: Link

Artikel zum Einstieg: Baeldung

Dem Projekt dank Maven einfach die notwendigen Abhängigkeiten in der pom.xml hinzufügen:

<dependencies>
  <!-- https://mvnrepository.com/artifact/org.docx4j/docx4j-JAXB-ReferenceImpl -->
  <dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
    <version>11.4.9</version>
  </dependency>
</dependencies>

Template befüllen

Template laden

WordprocessingMLPackage wordPackage = WordprocessingMLPackage.load(template);

Text ersetzen

Wir suchen alle Texte und wenn ein Platzhalter gefunden wird, wird der Text mit dem Wert aus dem Report gefüllt:

private static void textErsetzen(WordprocessingMLPackage wordPackage, Report report) throws Exception {
  List<Object> texts = wordPackage.getMainDocumentPart().getJAXBNodesViaXPath(XPATH_TO_SELECT_TEXT_NODES, true);
  for (Object obj : texts) {  
    Text text = (Text) ((JAXBElement<?>) obj).getValue();

    String textValue;
    if ("ÜBERSCHRIFT1".equals(text.getValue())) {
      textValue = report.ueberschrift;
    } else if ("TEXT1".equals(text.getValue())) {
      textValue = report.text;
    } else {
      textValue = text.getValue();
    }

    text.setValue(textValue);
  } 
}

Resultat:

Tabellenzeilen erzeugen

Wir laufen über alle Elemente und wenn eine Tabelle gefunden wird, wird deren Titel überprüft und dann die Tabelle befüllt.

private static void tabellenzeilenErzeugen(WordprocessingMLPackage wordPackage, Report report) {
  for (Object o : wordPackage.getMainDocumentPart().getContent()) {
    if (o instanceof JAXBElement) {
      @SuppressWarnings("unchecked")
      Tbl tbl = ((JAXBElement<Tbl>)o).getValue();
      String tblCaption = tbl.getTblPr().getTblCaption() != null ? tbl.getTblPr().getTblCaption().getVal() : null;
      if ("MyTabelle1".equals(tblCaption)) {
        fillTabelle1(tbl, report);
      } else {
        System.out.println("tblCaption: " + tblCaption);
      }
    }
  }
}

private static void fillTabelle1(Tbl tbl, Report report) {
  for (String[] zeile : report.tabelle) {
    addRow(tbl, zeile[0], zeile[1], zeile[2]);
  }
}

Bei den Einträgen in der Tabelle setzen wir noch den gewünschten Font.

private static void addRow(Tbl tbl, String... cells) {
  ObjectFactory factory = new ObjectFactory();

  // setup Font
  String fontName = "Arial";
  RFonts fonts = factory.createRFonts();
  fonts.setAscii(fontName);
  fonts.setCs(fontName);
  fonts.setHAnsi(fontName);
  RPr rpr = factory.createRPr();
  rpr.setRFonts(fonts);

  // new Row
  Tr tr = factory.createTr();
  for (String cell : cells) {
    Tc tc = factory.createTc();

    P p = factory.createP();
    R r = factory.createR();
    r.setRPr(rpr);

    Text text = factory.createText();

    text.setValue(cell);
    r.getContent().add(text);
    p.getContent().add(r);

    tc.getContent().add(p);

    tr.getContent().add(tc);
  }
  tbl.getContent().add(tr);
}

Resultat:

Report speichern

wordPackage.save(output);