I am currently working on an English translation. If you like to help to proofread please contact me: ullenboom ät g m a i l dot c o m.

Java Videotraining Werbung

1. XML, JSON und weitere Datenformate mit Java verarbeiten

Zwei wichtige Datenformate für den Austausch von Dokumenten sind XML und JSON. XML ist historisch gesehen der ältere Datentyp, JSON finden wir heutzutage oft in der Kommunikation zwischen einem Server und einer JavaScript Anwendung. JSON-Dokumente werden auch gerne für Konfigurationsdateien verwendet.

Während die Java SE unterschiedliche Klassen zum Lesen und Schreiben von XML-Dokumenten bietet, ist die Unterstützung von JSON nur in der Java Enterprise Edition, oder durch ergänzende Open-Source Bibliotheken gegeben. Viele der Aufgaben in diesem Kapitel greifen daher zu externen Bibliotheken.

Eine wichtige Kategorie der Dokumentenformate bilden Beschreibungssprachen. Sie definieren die Struktur der Daten. Zu den wichtigsten Formaten zählen HTML, XML, JSON und PDF.

Java bringt bis auf die Unterstützung der Property-Dateien und der Möglichkeit ZIP-Archive zu verarbeiten keine Unterstützung für andere Datenformate mit. Das gilt insbesondere auch für CSV-Dateien, PDF oder Office-Dokumente. Glücklicherweise füllen dutzende Open-Source-Bibliotheken diese Lücke, sodass man diese Funktionalität nicht selbst programmieren muss.

Voraussetzungen

  • Maven-Dependencies hinzunehmen können

  • StaX kennen

  • XML-Dokumente schreiben können

  • JAXB-Beans aus XML-Schema-Dateien erzeugen können

  • Objekt-XML-Mapping mit JAXB einsetzen können

  • Einarbeitung in JSON-Bibliothek Jackson

  • ZIP-Archive auslesen können

Verwendete Datentypen in diesem Kapitel:

1.1. XML-Verarbeitung mit Java

Es gibt unterschiedliche Java-APIs zum Umgang mit XML-Dokumenten. Eine Möglichkeit ist das Halten von kompletten XML Objekten im Speicher, die andere Lösung erinnert an Datenströme. StAX ist eine Pull-API, mit der die Elemente aktiv aus dem Datenstrom gezogen werden, und auch geschrieben werden können. Das Verarbeitungsmodell eignet sich optimal für große Dokumente, die nicht komplett im Speicher stehen müssen.

JAXB bietet eine einfache Möglichkeit Java-Objekte in XML zu konvertieren und XML später wieder in Java-Objekte. Mithilfe von Annotationen oder externen Konfigurationsdateien lässt sich die Abbildung präzise steuern.

1.1.1. XML-Datei mit Rezept schreiben ⭐

Captain CiaoCiao hat so viele Rezepte, dass er eine Datenbank benötigt. Es liegen ihm verschiedene Angebote für Datenbankmanagementsysteme vor, und er möchte schauen, ob sie alle seine Rezepte importieren können.

Seine eigenen Rezepte liegen im RecipeML-Format vor, einem XML-Format, das lose spezifiziert ist: http://www.formatdata.com/recipeml/. Unter https://dsquirrel.tripod.com/recipeml/indexrecipes2.html gibt es eine große Datenbank. Ein Beispiel von ›Key Gourmet‹:

<?xml version="1.0" encoding="UTF-8"?>
<recipeml version="0.5">
  <recipe>
    <head>
      <title>11 Minute Strawberry Jam</title>
      <categories>
        <cat>Canning</cat>
        <cat>Preserves</cat>
        <cat>Jams &amp; jell</cat>
      </categories>
      <yield>8</yield>
    </head>
    <ingredients>
      <ing>
        <amt>
          <qty>3</qty>
          <unit>cups</unit>
        </amt>
        <item>Strawberries</item>
      </ing>
      <ing>
        <amt>
          <qty>3</qty>
          <unit>cups</unit>
        </amt>
        <item>Sugar</item>
      </ing>
    </ingredients>
    <directions>
      <step>Put the strawberries in a pan.</step>
      <step>Add 1 cup of sugar.</step>
      <step>Bring to a boil and boil for 4 minutes.</step>
      <step>Add the second cup of sugar and boil again for 4 minutes.</step>
      <step>Then add the third cup of sugar and boil for 3 minutes.</step>
      <step>Remove from stove, cool, stir occasionally.</step>
      <step>Pour in jars and seal.</step>
    </directions>
  </recipe>
</recipeml>

Aufgabe:

  • Schreibe ein Programm, das ein XML-Dokument nach dem RecipeML Aufbau ausgibt.

1.1.2. Prüfen ob alle Bilder ein Alt haben ⭐

Bilder in HTML-Dokumenten sollten immer ein alt-Attribut haben.

Aufgabe:

  • Implementiere einen XHTML-Prüfer, der meldet, ob jedes img-Tag ein Attribut alt gesetzt hat.

  • Als XHTML-Datei kann man z. B. index.xhtml nehmen.

1.1.3. Java-Objekte mit JAXB schreiben ⭐

JAXB vereinfacht den Zugriff auf XML-Dokumente, denn es erlaubt eine praktische Abbildung von einem Java-Objekt auf ein XML-Dokument und umgekehrt.

JAX wurde in der Java 6 in die Standard Edition aufgenommen und in Java 11 wieder entfernt. Um für aktuelle Java-Versionen vorbereitet zu sein, binde in die POM-Datei ein:

<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.3.1</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>2.3.3</version>
  <scope>runtime</scope>
</dependency>

Aufgabe:

  • Schreibe JAXB-Beans, damit man folgendes XML erzeugen kann:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <ingredients>
        <ing>
            <amt>
                <qty>3</qty>
                <unit>cups</unit>
            </amt>
            <item>Sugar</item>
        </ing>
        <ing>
            <amt>
                <qty>3</qty>
                <unit>cups</unit>
            </amt>
        </ing>
    </ingredients>
  • Lege die Klassen Ingredients, Ing, Amt an.

  • Gibt den Klassen entsprechende Objektvariablen; es ist in Ordnung, wenn diese public sind.

  • Überlege, welche Annotation eingesetzt werden muss.

1.1.4. Witze einlesen und herzlich lachen ⭐⭐

Bonny Brain lacht auch über einfache Witze, wovon sie nie genug haben kann. Sie findet im Internet die Seite https://sv443.net/jokeapi/v2/joke/Any?format=xml, die ihr immer neue Witze liefert.

Das Format ist XML, was gut für den Datentransport ist, aber wir sind Java-Entwickler und wünschen uns alles in Objekten! Mit JAXB sollen die XML-Dateien eingelesen und in Java-Objekt konvertiert werden, sodass man später eine individuelle Ausgabe entwickeln kann.

Im ersten Schritt sollen aus einer XML-Schema-Datei JAXB-Beans automatisch generiert werden. Das Schema für die Joke-Seite ist wie folgt – keine Angst, man muss das nicht verstehen.

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="data">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:string" name="category" />
        <xs:element type="xs:string" name="type" />
        <xs:element name="flags">
          <xs:complexType>
            <xs:sequence>
              <xs:element type="xs:boolean" name="nsfw" />
              <xs:element type="xs:boolean" name="religious" />
              <xs:element type="xs:boolean" name="political" />
              <xs:element type="xs:boolean" name="racist" />
              <xs:element type="xs:boolean" name="sexist" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element type="xs:string" name="setup" />
        <xs:element type="xs:string" name="delivery" />
        <xs:element type="xs:int" name="id" />
        <xs:element type="xs:string" name="error" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Der Anbieter bietet kein Schema, daher ist es mithilfe von https://www.freeformatter.com/xsd-generator.html aus dem XML generiert.

Aufgabe:

  • Lade die XML-Schema-Definition unter jokes.xsd und setze die Datei in das Maven-Verzeichnis /src/main/resources.

  • Ergänze die POM-Datei um folgendes Element:

    <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxb2-maven-plugin</artifactId>
        <version>2.5.0</version>
        <executions>
          <execution>
            <id>xjc</id>
            <goals>
              <goal>xjc</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <packageName>com.tutego.exercise.xml.joke</packageName>
          <sources>
            <source>src/main/resources/jokes.xsd</source>
          </sources>
          <generateEpisode>false</generateEpisode>
          <outputDirectory>${basedir}/src/main/java</outputDirectory>
          <clearOutputDir>false</clearOutputDir>
          <noGeneratedHeaderComments>true</noGeneratedHeaderComments>
          <locale>en</locale>
        </configuration>
      </plugin>
    </plugins>
    </build>

    Die Plugin-Sektion bindet org.codehaus.mojo:jaxb2-maven-plugin ein und konfiguriert es; alle Optionen sind unter https://www.mojohaus.org/jaxb2-maven-plugin/Documentation/v2.5.0/index.html erklärt.

  • Starte von der Kommandozeile mvn generate-sources. Es entstehen 2 Klassen im Paket com.tutego.exercise.xml.joke:

    • Data

    • ObjectFactory

  • Nutze JAXB um von der URL https://sv443.net/jokeapi/v2/joke/Any?format=xml einen Witz zu beziehen und in ein Objekt zu konvertieren.

1.2. JSON

Die Java SE bringt keine Unterstützung für JSON mit, die Jakarta EE schon. Eine beliebte Implementierung ist Jackson, das auch Java-Objekte in JSON abbilden und aus JSON-Objekten wieder Java-Objekt rekonstruieren kann.

Jackson ist gut modularisiert. Es gibt drei Kernmodule Streaming, Annotations und Databind und mehrere Drittmodule insbesondere für speziellere Datentypen wie von Guava, javax.money, und Ergänzungen etwa zu Performance-Optimierung, die zum Beispiel Bytecode generieren, statt auf Reflection zu setzen.

Das Modul Databind hat eine Abhängigkeit auf Streaming und Annotations, sodass Entwickler damit alle Kernfunktionalitäten bekommen. Nimm in die Maven-POM folgende Dependency mit auf:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.0</version>
</dependency>

1.2.1. Hacker News JSON auswerten ⭐

Die Seite Hacker News (https://news.ycombinator.com/) wurde im Kapitel über Netzwerkzugriffe kurz vorgestellt.

Die URL https://hacker-news.firebaseio.com/v0/item/24857356.json liefert ein JSON-Objekt der Nachricht mit der ID 24857356. Die Antwort sieht (formatiert und bei den kids etwas gekürzt) so aus:

{
   "by":"luu",
   "descendants":257,
   "id":24857356,
   "kids":[
      24858151,
      24857761,
      24858192,
      24858887
   ],
   "score":353,
   "time":1603370419,
   "title":"The physiological effects of slow breathing in the healthy human",
   "type":"story",
   "url":"https://breathe.ersjournals.com/content/13/4/298"
}

Mit Jackson lässt sich dieses JSON in eine Map konvertieren:

ObjectMapper mapper = new ObjectMapper();
Map map = mapper.readValue( src, Map.class );

Es sind src verschiedene Quellen für die Daten, etwa String, File, Reader, InputStream, URL, …​

Aufgabe:

  • Schreibe eine neue Methode Map<?, ?> news(long id), die mithilfe von Jackson das JSON-Dokument unter "https://hacker-news.firebaseio.com/v0/item/" + id + ".json" bezieht und in eine Map konvertiert und zurückliefert.

Beispiel:

  • news(24857356).get("title")"The physiological effects of slow breathing in the healthy human"

  • news(111111).get("title")null

1.2.2. Editor-Konfigurationen als JSON lesen und schreiben ⭐⭐

Die Entwickler arbeiten für Captain CiaoCiao an einem neuen Editor, und die Konfigurationen sollen in einer JSON-Datei gesichert werden.

Aufgabe:

  • Schreibe eine Klasse Settings, sodass sich folgende Konfigurationen abbilden lassen:

    {
      "editor" : {
        "cursorStyle" : "line",
        "folding" : true,
        "fontFamily" : [ "Consolas, 'Courier New', monospace" ],
        "fontSize" : 22,
        "fontWeight" : "normal"
      },
      "workbench" : {
        "colorTheme" : "Default Dark+"
      },
      "terminal" : {
        "integrated.unicodeVersion" : "11"
      }
    }
  • Die JSON-Datei lässt die Datentypen gut erkennen:

    • cursorStyle ist String, folding ist boolean, fontFamily ist ein Array oder List.

  • Wenn ein Attribut nicht gesetzt ist, also null ist, soll es nicht geschrieben werden.

  • Bei terminal sind die enthaltenen Schlüsselwerte unbekannt, sie sollen in einer Map<String, String> enthalten sein.

1.3. HTML

HTML ist eine wichtige Auszeichnungssprache. Die Java-Standardbibliothek bringt keine Unterstützung für HTML-Dokumente mit, sieht man einmal von dem ab, was die javax.swing.JEditorPane kann, nämlich HTML 3.2 und eine Teilmenge von CSS 1.0 darstellen.

Damit Java-Programme in der Lage sind, HTML-Dokumente korrekt und valide zu schreiben und einzulesen und Knoten auszulesen, müssen wir zu (Open-Source-)Bibliotheken greifen.

1.3.1. Mit jsoup Wikipedia-Bilder laden ⭐⭐

Die beliebte quelloffene Bibliothek jsoup (https://jsoup.org/) lädt den Inhalt von Webseiten und repräsentiert den Inhalt in einem Baum im Speicher.

Nimm folgende Dependency in der POM mit auf:

<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.13.1</version>
</dependency>

Aufgabe:

1.4. Office-Dokumente

Microsoft Office steht weiterhin ganz oben, wenn es um Textverarbeitung und Tabellenkalkulation geht. Seit vielen Jahren ist das binäre Dateiformat wohlbekannt, und es gibt Java-Bibliotheken zum Lesen und Schreiben. Mittlerweile ist die Verarbeitung von Microsoft Office Dokumenten deutlich einfacher geworden, seitdem die Dokumente im Kern XML-Dokumente sind, die in einem ZIP-Archiv zusammengefasst werden. Die Unterstützung in Java ist sehr gut.

1.4.1. Word-Dateien mit Screenshots generieren ⭐⭐

Lies den Wikpedia-Eintrag zu POI: https://de.wikipedia.org/wiki/Apache_POI.

Aufgabe:

  1. Ergänze für Maven in der POM folgendes, damit Apache POI und die nötigen Abhängigkeiten für DOCX eingebunden werden:

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>4.1.2</version>
    </dependency>
  2. Studiere den Quellcode von SimpleImages.java.

  3. Mit Java kann man Screenshots aufnehmen, und zwar so:

    private static byte[] getScreenCapture() throws AWTException, IOException {
      BufferedImage screenCapture = new Robot().createScreenCapture( SCREEN_SIZE );
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      ImageIO.write( screenCapture, "jpeg", os );
      return os.toByteArray();
    }
  4. Schreibe ein Java-Programm, das 20 Sekunden lang alle 5 Sekunden einen Screenshot macht und das Bild in das Word-Dokument hängt.

1.5. Archive

Dateien mit Metadaten fasst man in Archiven zusammen. Ein bekanntes und beliebtes Archivformat ist ZIP, was die Daten nicht nur zusammenfasst, sondern auch komprimiert. Viele Archivformate können die Dateien auch verschlüsselt speichern und Prüfsummen ablegen, sodass später Fehler in der Übertragung erkannt werden können.

Java bietet zwei Möglichkeiten zur Kompression: Ab Java 7 gibt es ein ZIP-File-System-Provider und schon seit Java 1.0 gibt es die Klassen ZipFile und ZipEntry.

1.5.1. Insektengeräusche aus dem ZIP-Archiv abspielen ⭐⭐

Bonny Brain horcht gerne den Geräusch von Insekten und greift dabei auf die WAV-Sammlung von https://catalog.data.gov/dataset/bug-bytes-sound-library-stored-product-insect-pest-sounds zurück, wo verschieden Audio-Dateien in einem ZIP zum Download angeboten werden.

Aufgabe:

  • Studiere die Dokumentation unter https://christian-schlichtherle.bitbucket.io/truezip/truezip-path/.

  • Nimm zwei Abhängigkeiten in die Maven-POM mit auf:

    <dependency>
      <groupId>de.schlichtherle.truezip</groupId>
      <artifactId>truezip-path</artifactId>
      <version>7.7.10</version>
    </dependency>
    
    <dependency>
      <groupId>de.schlichtherle.truezip</groupId>
      <artifactId>truezip-driver-zip</artifactId>
      <version>7.7.10</version>
    </dependency>
  • Lade das ZIP mit den Insektengeräuschen herunter, aber packe es nicht aus.

  • Baue für die ZIP-Datei ein TPath-Objekt auf.

  • Übertage alle Dateinamen aus der ZIP in eine Liste, hier hilft Files.newDirectoryStream(…​).

  • Schreibe eine Endlosschleife und

    • suche eine zufällige WAV-Datei aus,

    • öffne die zufällig ausgewählte Datei mit Files.newInputStream(…​), dekoriere sie mit einem BufferedInputStream und öffne einen AudioSystem.getAudioInputStream(…​). Spiele die WAV ab und greife auf folgenden Code zurück, wobei ais der AudioInputStream ist.

      Clip clip = AudioSystem.getClip();
      clip.open( ais );
      clip.start();
      TimeUnit.MICROSECONDS.sleep( clip.getMicrosecondLength() + 50 );
      clip.close();

      Im Exception-Kapitel hatten wir mit der javax.sound-API schon einmal gearbeitet.