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, PDFs 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 Jakarta XML Binding einsetzen können
Grundverständnis von Jakarta JSON Bibliotheken
ZIP-Archive auslesen können
Verwendete Datentypen in diesem Kapitel:
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹
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 & 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 im RecipeML-Format ausgibt.
1.1.2. Prüfen, ob alle Bilder ein alt-Attribut haben ⭐
Bilder in HTML-Dokumenten sollten immer ein alt
-Attribut haben.
Aufgabe:
Implementiere einen XHTML-Prüfer, der meldet, ob jedes
img
-Tag ein Attributalt
gesetzt hat.Nimm als XHTML-Datei z. B. http://tutego.de/download/index.xhtml.
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.
JAXB wurde in der Java 6 in die Standard Edition aufgenommen und in Java 11 wieder entfernt. Wir benötigen daher eine Dependency:
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.2</version>
<scope>runtime</scope>
</dependency>
Aufgabe:
Schreibe JAXB-Beans, damit wir folgendes XML erzeugen können:
<?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, das 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 wir später eine individuelle Ausgabe entwickeln können.
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 http://tutego.de/download/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>3.1.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/v3.1.0/index.html erklärt.Starte von der Kommandozeile
mvn generate-sources
. Es entstehen zwei Klassen im Paketcom.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
Java SE bietet von sich aus keine Unterstützung für JSON, aber es gibt zwei Standards aus dem Jakarta-EE-Projekt, die diese Unterstützung bieten: Jakarta JSON Processing (JSON-P) (https://jakarta.ee/specifications/jsonp/) und Jakarta JSON Binding (JSON-B) (https://jakarta.ee/specifications/jsonb/). Mit JSON-B können Java-Objekte in JSON abgebildet werden und umgekehrt, während JSON-P APIs für die Verarbeitung von JSON-Daten bereitstellt. Eine weitere beliebte Implementierung ist Jackson (https://github.com/FasterXML/jackson).
Um JSON-B zu nutzen, müssen wir sowohl die API als auch eine Implementierung in das POM unseres Projekts einbinden. Die Referenzimplementierung, Yasson, ist eine gute Wahl.
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>3.0.0</version>
<scope>runtime</scope>
</dependency>
1.2.1. Hacker News JSON auswerten ⭐
Die Seite Hacker News (https://news.ycombinator.com/) wurde in Kapitel »Netzwerkprogrammierung«, 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 JSON-B lässt sich dieses JSON in eine Map
konvertieren:
Map map = JsonbBuilder.create().fromJson( quelle, Map.class );
Die quelle
kann ein String
, Reader
oder InputStream
sein.
Aufgabe:
Schreibe eine neue Methode
Map<Object, Object> news(long id)
, die mithilfe von JSON-B das JSON-Dokument unter"https://hacker-news.firebaseio.com/v0/item/" + id + ".json"
bezieht und in eineMap
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 die folgenden 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
istString
,folding
istboolean
,fontFamily
ist ein Array oderList
.
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 einerMap<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 mit in das POM auf:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.4</version>
</dependency>
Aufgabe:
Studiere die Beispiele unter https://jsoup.org/cookbook/extracting-data/dom-navigation und https://jsoup.org/cookbook/extracting-data/selector-syntax.
Erfrage von der Wikipedia-Hauptseite alle Bilder und speichere sie im eigenen Dateisystem.
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:
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>
Studiere den Quellcode von SimpleImages.java.
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(); }
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äuschen 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.Übertrage alle Dateinamen aus der ZIP-Datei 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 einemBufferedInputStream
und öffne einenAudioSystem.getAudioInputStream(…)
. Spiele die WAV-Datei ab und greife auf folgenden Code zurück, wobeiais
derAudioInputStream
ist.Clip clip = AudioSystem.getClip(); clip.open( ais ); clip.start(); TimeUnit.MICROSECONDS.sleep( clip.getMicrosecondLength() + 50 ); clip.close();
In Kapitel »Exceptions« hatten wir mit der
javax.sound
-API schon einmal gearbeitet.
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹