XMLBeam is an interesting library using an approach of projecting parts of an XML DOM tree into Java using some simple interfaces, annotations and XPath expressions.

In the following article, I’d like to share three experiments of mine with this library for reading, writing XML and parsing a live RSS feed.

RSS Feed Projection Interface

rss projection interface

Dependencies

Using Maven, we need to add only one dependency to our pom.xml:

<dependency>
	<groupId>org.xmlbeam</groupId>
	<artifactId>xmlprojector</artifactId>
	<version>1.2.1</version>
</dependency>

Examples

Now that we’ve got everything we need, we’re ready to create out first example.

Please note, that I’ll be covering only some basic features of the library. For more detailed information I highly recommend the excellent tutorial section on the XMLBeam project website or the four well-written and detailed articles on the Jaxenter website that I’ve linked in the “Resources” chapter.

Basic XML Parsing

In our first example, we’re going to implement a projection for a custom XML structure – in this case a list of books and their authors …

This is our books.xml file to be parsed:

<?xml version="1.0" encoding="UTF-8"?>
<books>
	<book>
		<title>One book</title>
		<published>2014</published>
		<authors>
			<author firstname="Barney" lastname="Stinson" />
			<author firstname="Selma" lastname="Bouvier" />
		</authors>
	</book>
	<book>
		<title>Another book</title>
		<published>2004</published>
		<authors>
			<author firstname="Sideshow" lastname="Bob" />
		</authors>
	</book>
	<book>
		<title>A third book</title>
		<published>2013</published>
		<authors>
			<author firstname="Tim" lastname="Allan" />
			<author firstname="Rob" lastname="Halford" />
			<author firstname="Rob" lastname="Roy" />
		</authors>
	</book>
</books>

The following projection allows us to parse this document. First of all, we’re using @XBDocURL to specify the location of the XML file and XBRead allows us to use XPath expressions to read data from specific structures in our document.

I really like the way the library allows us to construct custom structures and collections here using simple interfaces.

package com.hascode.tutorial.xbeam.projection;

import java.util.List;

import org.xmlbeam.annotation.XBDocURL;
import org.xmlbeam.annotation.XBRead;

@XBDocURL("resource:///books.xml")
public interface Books {

	interface Book {
		interface Author {
			@XBRead("./@firstname")
			String getFirstname();

			@XBRead("./@lastname")
			String getLastname();
		}

		@XBRead("./title")
		String getTitle();

		@XBRead(".//author")
		List<Author> getAuthors();
	}

	@XBRead("//book")
	List<Book> getBooks();
}

The following code is used to print some content…

package com.hascode.tutorial.xbeam;

import java.io.IOException;
import java.util.stream.Collectors;

import org.xmlbeam.XBProjector;

import com.hascode.tutorial.xbeam.projection.Books;

public class SimpleXmlFileParsingExample {
	public static void main(final String[] args) throws IOException {
		Books bookProjection = new XBProjector().io().fromURLAnnotation(Books.class);
		bookProjection.getBooks().forEach(book -> {
			System.out.println("book -> title: " + book.getTitle() + " and authors: ");
			System.out.println(book.getAuthors().stream().map(a -> {
				return a.getFirstname() + " " + a.getLastname();
			}).collect(Collectors.joining(", ")));
		});
	}
}

Running the code above should produce the following output:

book -> title: One book and authors:
Barney Stinson, Selma Bouvier
book -> title: Another book and authors:
Sideshow Bob
book -> title: A third book and authors:
Tim Allan, Rob Halford, Rob Roy

Reading and Writing a Maven Pom

In the next example we’re going not only to read but also write XML by writing a simplified projection for a Maven pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hascode.tutorial</groupId>
	<artifactId>my-artifactd</artifactId>
	<version>1.0.0</version>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-all</artifactId>
			<version>1.3</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

This is our simplified projection, please note that a version is readable and writable. We’re using a special namespace named xbdefaultns that simply maps to the default namespace in the parsed XML document.

We have made one element writable using @XBWrite so we’re able to set the version elements in the following projection:

package com.hascode.tutorial.xbeam.projection;

import java.util.List;

import org.xmlbeam.annotation.XBDocURL;
import org.xmlbeam.annotation.XBRead;
import org.xmlbeam.annotation.XBWrite;

@XBDocURL("resource:///pom.xml")
public interface MavenPom {
	public interface Artifact {
		@XBRead("./xbdefaultns:artifactId")
		String artifactId();

		@XBRead("./xbdefaultns:groupId")
		String groupId();

		@XBRead("./xbdefaultns:version")
		String version();

		@XBWrite("./version")
		void version(String version);
	}

	@XBRead("/xbdefaultns:project")
	Artifact project();

	@XBRead("//xbdefaultns:dependency")
	List<Artifact> dependencies();
}

The following snippet uses the projection to print out some information about the pom and to display the XML structure after having set a new version.

package com.hascode.tutorial.xbeam;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

import org.xmlbeam.XBProjector;

import com.hascode.tutorial.xbeam.projection.MavenPom;

public class MavenPomParsing {
	public static void main(final String[] args) throws IOException {
		MavenPom pom = new XBProjector().io().fromURLAnnotation(MavenPom.class);
		System.out.println("Project: " + pom.project().artifactId() + ":" + pom.project().groupId() + ":" + pom.project().version());
		pom.dependencies().forEach(dep -> {
			System.out.println("-dependency -> " + dep.artifactId() + ":" + dep.groupId() + ":" + dep.version());
		});
		System.out.println("updating project version to 1.2.3..");
		pom.project().version("1.2.3");

		Path path = FileSystems.getDefault().getPath("/tmp", "changed.xml");
		System.out.println("writing pom to file: " + path);
		new XBProjector().io().file(path.toFile()).write(pom);
		Files.readAllLines(path).forEach(System.out::println);
	}
}

The code above should produce the following output:

Project: my-artifactd:com.hascode.tutorial:1.0.0
-dependency -> junit:junit:4.11
-dependency -> hamcrest-all:org.hamcrest:1.3
updating project version to 1.2.3..
writing pom to file: /tmp/changed.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hascode.tutorial</groupId>
	<artifactId>my-artifactd</artifactId>
	<version>1.2.3</version>

	[..]
</project>

Loading and Reading an RSS Feed

In our final example, we’re going to parse some XML directly from the internet – in this case the RSS feed of my blog, located at https://www.hascode.com/feed/.

This is our (simplified) projection for an RSS feed using the Atom 2.0 protocol.

package com.hascode.tutorial.xbeam.projection;

import java.util.List;

import org.xmlbeam.annotation.XBRead;

public interface Rss {
	public interface Item {
		@XBRead("title")
		String title();

		@XBRead("link")
		String link();
	}

	public interface Channel {
		@XBRead("title")
		String title();

		@XBRead("//item")
		List<Item> items();
	}

	@XBRead("/rss/channel")
	Channel channel();
}

The following code fetches the RSS feed’s xml, parses it using the projection API and prints title and web address of each RSS feed’s item:

package com.hascode.tutorial.xbeam;

import java.io.IOException;

import org.xmlbeam.XBProjector;

import com.hascode.tutorial.xbeam.projection.Rss;

public class RssFeedParsing {
	public static void main(final String[] args) throws IOException {
		System.out.println("loading rss feed..");
		Rss rss = new XBProjector().io().url("https://www.hascode.com/feed/").read(Rss.class);

		System.out.println("rss feed received - channel: " + rss.channel().title());
		rss.channel().items().forEach(i -> {
			System.out.println("title: " + i.title() + ", link: " + i.link());
		});
	}
}

The code above produces the following output:

loading rss feed..
rss feed received - channel: hasCode.com
title: Lucene by Example: Specifying Analyzers on a per-field-basis and writing a custom Analyzer/Tokenizer, link: https://www.hascode.com/2014/07/lucene-by-example-specifying-analyzers-on-a-per-field-basis-and-writing-a-custom-analyzertokenizer/
title: Using jOOQ and Build Helper Plugin to Generate Database Metamodels with Maven, link: https://www.hascode.com/2014/06/using-jooq-and-build-helper-plugin-to-generate-database-metamodels-with-maven/
title: Java EE: Logging User Interaction the Aspect-Oriented Way using Interceptors, link: https://www.hascode.com/2014/05/java-ee-logging-user-interaction-the-aspect-oriented-way-using-interceptors/
title: Using Java Config-Builder to assemble your Application Configuration, link: https://www.hascode.com/2014/05/using-java-config-builder-to-assemble-your-application-configuration/
title: Allocating available random Ports in a Maven Build, link: https://www.hascode.com/2014/05/allocating-available-random-ports-in-a-maven-build/
title: Running JavaScript Tests with Maven, Jasmine and PhantomJS, link: https://www.hascode.com/2014/05/running-javascript-tests-with-maven-jasmine-and-phantomjs/
title: Java Persistence API: Controlling the Second-Level-Cache, link: https://www.hascode.com/2014/04/java-persistence-api-controlling-the-second-level-cache/
title: Using Apache Avro with Java and Maven, link: https://www.hascode.com/2014/03/using-apache-avro-with-java-and-maven/
title: Creating elegant, typesafe Queries for JPA, mongoDB/Morphia and Lucene using Querydsl, link: https://www.hascode.com/2014/02/creating-elegant-typesafe-queries-for-jpa-mongodbmorphia-and-lucene-using-querydsl/
title: Creating Grammar Parsers in Java and Scala with Parboiled, link: https://www.hascode.com/2014/01/creating-grammar-parsers-in-java-and-scala-with-parboiled/

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/xmlbeam-tutorial.git

Article Updates

  • 2014-07-25: Term “framework” replaced by the term “library”, thanks @lkimmel.