Contents

XML-bestanden lezen en schrijven met Java

XML-bestanden kunnen verschillende doelen dienen, waaronder gegevensopslag. Voordat JSON populair werd, was XML de voorkeursindeling voor het weergeven, opslaan en transporteren van gestructureerde gegevens.

Ondanks het feit dat XML tegenwoordig steeds minder gebruikt wordt, kom je het nog steeds af en toe tegen. Het is daarom van cruciaal belang dat je bedreven raakt in het werken met deze indeling. Ontdek de fijne kneepjes van het gebruik van de Document Object Model (DOM) Application Programming Interface (API) voor het lezen en schrijven van XML-documenten met Java als primair gereedschap.

Vereisten voor het verwerken van XML in Java

De Java Standard Edition, of SE, bevat de Java API voor XML-verwerking, ook wel JAXP genoemd, een uitgebreid framework dat verschillende facetten van XML-verwerking omvat. Dit uitgebreide repertoire bestaat uit verschillende integrale componenten, waaronder:

Het Document Object Model (DOM) omvat een verzameling klassen die het manipuleren van XML-componenten, waaronder elementen, knooppunten en attributen, vergemakkelijken. Gezien het ontwerp om een volledig XML-bestand in het geheugen te laden voor verwerking, is het echter niet ideaal uitgerust om documenten van substantiële omvang efficiënt te verwerken.

De Simple API for XML (SAX) is een lichtgewicht parseermechanisme dat speciaal is ontworpen voor het verwerken van XML-documenten. In tegenstelling tot het Document Object Model (DOM), dat een hele boomstructuur opbouwt tijdens het verwerken, werkt SAX door events af te vuren op basis van de XML-inhoud die wordt aangetroffen tijdens het parsen van een bestand. Deze aanpak vermindert het geheugengebruik en biedt meer flexibiliteit bij het verwerken van grote hoeveelheden gegevens. Het gebruik van SAX kan echter lastig zijn in vergelijking met het gebruik van het DOM, omdat het afhankelijk is van event-based programmeerparadigma’s.

StAX, of de Streaming API voor XML, is een recentere toevoeging aan het domein van XML-verwerking. Met indrukwekkende prestatiemogelijkheden op het gebied van het filteren, verwerken en wijzigen van streams slaagt dit krachtige hulpmiddel erin om zijn doelstellingen te bereiken zonder dat XML-documenten op grote schaal in het geheugen hoeven te worden geladen. In tegenstelling tot de event-driven architectuur van de SAX API, gebruikt StAX een pull-type benadering waardoor het eenvoudiger en gebruiksvriendelijker is om te coderen.

Om XML-gegevens binnen een Java-toepassing te kunnen verwerken, is het nodig om bepaalde pakketten op te nemen die deze functionaliteit vergemakkelijken. Deze pakketten bieden verschillende methoden en klassen voor het parsen, manipuleren en genereren van XML-documenten.

 import javax.xml.parsers.*;
import javax.xml.transform.*;
import org.w3c.dom.*;

Een voorbeeld van een XML-bestand voorbereiden

/nl/images/sample-xml-file-from-microsoft.jpeg

Om de voorbeeldcode en de concepten erachter te begrijpen, kunt u dit voorbeeld XML-bestand van Microsoft gebruiken. Hier is een uittreksel:

 <?xml version="1.0"?>
<catalog>
  <book id="bk101">
    <author>Gambardella, Matthew</author>
    <title>XML Developer's Guide</title>
    <genre>Computer</genre>
    <price>44.95</price>
    <publish_date>2000-10-01</publish_date>
    <description>An in-depth look at creating applications
      with XML.</description>
  </book>
  <book id="bk102">
    <author>Ralls, Kim</author>
...snipped... 

Het XML-bestand lezen met DOM API

Om een XML-bestand effectief te verwerken met behulp van de Document Object Model (DOM) Application Programming Interface, moeten we eerst een instantie van de klasse DocumentBuilder maken, die het parsen van het XML-document zal vergemakkelijken.

 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder(); 

Men kan er nu voor kiezen om het hele document in het geheugen op te slaan, te beginnen met het XML root element, dat in dit geval overeenkomt met het “catalog” element.

 // XML file to read
File file = "<path_to_file>";
Document document = builder.parse(file);
Element catalog = document.getDocumentElement();

Door deze benadering te gebruiken, krijgt men volledige toegang tot het hele XML-document, te beginnen met het hoofdknooppunt, namelijk het “catalogus”-element.

Informatie ophalen met behulp van de DOM API

Als je eenmaal het hoofdelement hebt gevonden met behulp van de XML parser, kun je de Document Object Model (DOM) API gebruiken om waardevolle informatie op te halen. Een praktische benadering is bijvoorbeeld het ophalen van alle directe afstammelingen van het root element en daar iteratief doorheen gaan. Merk echter op dat de methode getChildNodes() alle soorten kinderen retourneert, inclusief tekstknooppunten en commentaarknooppunten, die niet relevant zijn voor onze huidige taak. Daarom moeten we ons specifiek richten op alleen de child-elementen bij het verwerken van deze resultaten.

 NodeList books = catalog.getChildNodes();

for (int i = 0, ii = 0, n = books.getLength() ; i < n ; i\\+\\+) {
  Node child = books.item(i);

  if ( child.getNodeType() != Node.ELEMENT_NODE )
    continue;

  Element book = (Element)child;
  // work with the book Element here
}

Om een bepaald child element onder zijn parent te vinden in een XML-document met behulp van C#, kan een statische methode worden gemaakt die de verzameling child nodes itereert om het gewenste element te identificeren op basis van zijn naam. Als het element tijdens dit proces wordt ontdekt, wordt het geretourneerd; anders is het resultaat null.

 static private Node findFirstNamedElement(Node parent,String tagName)
{
  NodeList children = parent.getChildNodes();

  for (int i = 0, in = children.getLength() ; i < in ; i\\+\\+) {
    Node child = children.item(i);

    if (child.getNodeType() != Node.ELEMENT_NODE)
      continue;

    if (child.getNodeName().equals(tagName))
      return child;
  }

  return null;
}

Houd er rekening mee dat de DOM API de tekstuele inhoud binnen een element classificeert als een individueel knooppunt van de categorie TEXT\_NODE. De tekstuele inhoud kan uit meerdere aaneengesloten tekstknooppunten bestaan, waardoor specifieke handelingen nodig zijn om de tekst van een bepaald element op te halen:

 static private String getCharacterData(Node parent)
{
  StringBuilder text = new StringBuilder();

  if ( parent == null )
    return text.toString();

  NodeList children = parent.getChildNodes();

  for (int k = 0, kn = children.getLength() ; k < kn ; k\\+\\+) {
    Node child = children.item(k);

    if (child.getNodeType() != Node.TEXT_NODE)
      break;

    text.append(child.getNodeValue());
  }

  return text.toString();
}

Laten we met behulp van de meegeleverde hulpprogramma’s een voorbeeld bekijken dat relevante gegevens uit een XML-document haalt dat een catalogus van boeken voorstelt.De resulterende code toont uitgebreide details over elke afzonderlijke publicatie in deze catalogus:

 NodeList books = catalog.getChildNodes();

for (int i = 0, ii = 0, n = books.getLength() ; i < n ; i\\+\\+) {
  Node child = books.item(i);

  if (child.getNodeType() != Node.ELEMENT_NODE)
    continue;

  Element book = (Element)child;
  ii\\+\\+;

  String id = book.getAttribute("id");
  String author = getCharacterData(findFirstNamedElement(child, "author"));
  String title = getCharacterData(findFirstNamedElement(child, "title"));
  String genre = getCharacterData(findFirstNamedElement(child, "genre"));
  String price = getCharacterData(findFirstNamedElement(child, "price"));
  String pubdate = getCharacterData(findFirstNamedElement(child, "pubdate"));
  String descr = getCharacterData(findFirstNamedElement(child, "description"));

  System.out.printf("%3d. book id = %s\n" \\+
    " author: %s\n" \\+
    " title: %s\n" \\+
    " genre: %s\n" \\+
    " price: %s\n" \\+
    " pubdate: %s\n" \\+
    " descr: %s\n",
    ii, id, author, title, genre, price, pubdate, descr);
}

Hier volgt een stapsgewijze uitleg van de code:

De code bekijkt de jonkies van het broed, of hoofdonderdeel, dat dient als basis voor de hele structuur.

Voor elk individueel kindknooppunt, dat overeenkomt met een bepaald boek, controleert het programma of de onderliggende datastructuur de eigenschap bezit een ELEMENT_NODE te zijn. Als niet aan deze voorwaarde wordt voldaan, gaat het proces verder met de volgende iteratie.

Als een child node van het type ELEMENT_NODE wordt aangetroffen in het DOM tree traversal proces, wordt de child eigenschap omgezet in een instantie van de Element interface.

De daaropvolgende uitvoering van het programma omvat het extraheren van meerdere attributen en karaktergegevens die geassocieerd zijn met een gespecificeerd boekelement, zoals de unieke identificatiecode (“id”), auteursnaam, titel, genre, prijs, publicatiedatum en beschrijvende informatie. Deze verzamelde informatie wordt vervolgens afgedrukt naar de console met behulp van de System.out.printf() methode voor presentatiedoeleinden.

Zo ziet de uitvoer eruit:

/nl/images/parsing-xml-in-java-source-code-and-output.jpeg

XML-uitvoer schrijven met Transform API

Java biedt de XML Transformation API als middel om XML-gegevens te manipuleren. Deze API wordt gebruikt in combinatie met de identiteitstransformatie om uitvoer te produceren. Ter illustratie kun je de vorige voorbeeldcatalogus uitbreiden met een nieuw boekelement.

De informatie met betrekking tot een literair werk, inclusief de auteur en titel, kan worden verkregen uit een externe bron zoals een database of een eigenschappenbestand. Het meegeleverde eigendomsbestand dient hiervoor als model.

 id=bk113
author=Jane Austen
title=Pride and Prejudice
genre=Romance
price=6.99
publish_date=2010-04-01
description="It is a truth universally acknowledged, that a single man in possession of a good fortune must be in want of a wife." So begins Pride and Prejudice, Jane Austen's witty comedy of manners-one of the most popular novels of all time-that features splendidly civilized sparring between the proud Mr. Darcy and the prejudiced Elizabeth Bennet as they play out their spirited courtship in a series of eighteenth-century drawing-room intrigues.

Om een XML-document te verwerken, is het nodig om de eerder beschreven parsingtechniek te gebruiken. Hierbij wordt de op tekst gebaseerde structuur van het bestand afgebroken en wordt relevante informatie geëxtraheerd voor verdere analyse of manipulatie.

 File file = ...; // XML file to read
Document document = builder.parse(file);
Element catalog = document.getDocumentElement();

Door gebruik te maken van de meegeleverde klasse Eigenschappen in de programmeertaal Java, kan op efficiënte wijze informatie worden opgehaald en verwerkt die is opgeslagen in een afzonderlijk extern configuratiebestand dat een “eigenschappenbestand” wordt genoemd. Dit proces vereist minimale codeercomplexiteit en stroomlijnt de integratie van door de gebruiker gedefinieerde voorkeuren of instellingen met de algehele applicatielogica.

 String propsFile = "<path_to_file>";
Properties props = new Properties();

try (FileReader in = new FileReader(propsFile)) {
  props.load(in);
}

Na het laden van het eigenschappenbestand kan men de gewenste waarden voor toevoeging uit dat bestand halen.

 String id = props.getProperty("id");
String author = props.getProperty("author");
String title = props.getProperty("title");
String genre = props.getProperty("genre");
String price = props.getProperty("price");
String publish_date = props.getProperty("publish_date");
String descr = props.getProperty("description");

Maak nu een leeg boekelement.

 Element book = document.createElement("book");
book.setAttribute("id", id);

Het opnemen van de individuele componenten van het boek in het tekstcorpus is een ongecompliceerde onderneming. Om dit proces te vergemakkelijken, kan men een catalogus samenstellen van noodzakelijke benamingen door ze te organiseren in een verzameling die bekend staat als een “Lijst”. Door een terugkerende handeling binnen deze lijst uit te voeren, kunnen de respectieve vermeldingen efficiënt worden toegevoegd aan het bredere narratieve kader.

 List<String> elnames =Arrays.asList("author", "title", "genre", "price",
  "publish_date", "description");

for (String elname : elnames) {
  Element el = document.createElement(elname);
  Text text = document.createTextNode(props.getProperty(elname));
  el.appendChild(text);
  book.appendChild(el);
}

catalog.appendChild(book);

De eerder genoemde cataloguscomponent bezit momenteel een extra, recent geïntroduceerde boekentiteit. De enige resterende taak is het formuleren van het herziene XML-document (Extensible Markup Language) dat deze updates bevat.

Om een XML-document te genereren met behulp van een transformator, moet je eerst een instantie van die transformator verkrijgen. Dit kan door de nodige code te implementeren in een programmeertaal of ontwikkelomgeving. In Python zou men bijvoorbeeld de transformers bibliotheek en de bijbehorende functies kunnen gebruiken om een instantie van een transformator te construeren voor gebruik met natuurlijke taalverwerkingstaken.

 TransformerFactory tfact = TransformerFactory.newInstance();
Transformer tform = tfact.newTransformer();
tform.setOutputProperty(OutputKeys.INDENT, "yes");
tform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); 

Je kunt de methode setOutputProperty() gebruiken om het gewenste inspringingsniveau in de gegenereerde uitvoer op te geven.

In de laatste fase wordt het conversieproces uitgevoerd. Het resultaat wordt weergegeven via de uitvoerstroom, die kan worden waargenomen door de console of terminal te controleren waar het programma draait.

 tform.transform(new DOMSource(document), new StreamResult(System.out));

Om de uitvoer van het programma op te slaan in een bestand in plaats van het af te drukken op de console, kan de volgende aanpak worden gebruikt:

 tform.transform(new DOMSource(document), new StreamResult(new File("output.xml")));

Om zowel het lezen als het schrijven van Extensible Markup Language (XML) bestanden uit te voeren met behulp van de programmeertaal Java, moet een reeks procedurele acties achtereenvolgens worden uitgevoerd. Deze omvatten het definiëren van een XML-documentobject, het maken van knooppunten binnen dat document, het koppelen van kindelementen aan ouderknooppunten, het specificeren van elementattributen, het toevoegen of invoegen van nieuwe knooppunten op verschillende posities in het document en tot slot het sluiten van open tags voordat het proces wordt beëindigd.

Nu weet je hoe je XML-bestanden moet lezen en schrijven met Java

Java gebruiken om Extensible Markup Language (XML) te parsen en te manipuleren is een onmisbare vaardigheid die je vaak tegenkomt in praktische toepassingen. Het Document Object Model (DOM) en de Transformation API’s zijn bijzonder nuttig voor dit doel.

Een goed begrip van het Document Object Model (DOM) is onmisbaar voor ontwikkelaars die client-side scripts willen maken voor webgebaseerde applicaties of websites. Gelukkig is de architectuur van het DOM gestandaardiseerd in verschillende programmeertalen, waardoor consistente manipulatie mogelijk is via code geschreven in talen als Java en JavaScript.