Every which way

A new approach to XML – Part 2: Features of the XMLBeam library

SvenEwald
library.1

In the second part of this special series, Sven Ewald takes you on a dynamic, external and bidirectional XML journey.

This article is part of a series. Before you read
this, we recommend checking out ‘
Part
1: Data projection with XMLBeam
’ to bring you up to
speed. 

This time around, we take a closer look at the
features of the XMLBeam library. We’ll show you how to
parametrize projection targets, explain the use
of external documents, and take a first look at writing
projections.

In the first part of this series we learned
about the concept of data projection and the function of
sub-projections. By adding XPath expressions to method
declarations, XMLBeam is an amazingly versatile tool. Today we are
using this Java library with even more versatility by enhancing
projection methods with parameters. These Parameters can influence
the target of a projection method even at runtime. Let’s start with
an example from the real world:

Example 1: Eclipse Code Formatter Configuration (Extract)
<profiles>
<profile name="Some Profile" version="8">
<setting id="org.eclipse.jdt.core.formatter..." value="false" />
                ...
</profile>
<profile name="Another Profile" version="8">
<setting id="org.eclipse.jdt.core.formatter..." value="true" />
         ...
</profile>
</profiles>

This XML file contains multiple configuration
profiles. We define a projection interface to get a list of all
profile names and all settings for one selected profile.

Example 1: Projection interface
@XBDocURL("resource://eclipsecodeformatprofile.xml")
public interface EclipseFormatterConfigFile {
interface Setting {
@XBRead("@id")
String getName();
@XBRead("@value")
String getValue();
}
@XBRead("//profile/@name")
List<String> getProfileNames();
@XBRead("//profile[@name="{0}"]/setting")
List<Setting> getAllSettingsForProfile(String profileName);
}

For now, we leave the first line containing
the
@XBDocURL aside because we are going
to come back to that later.

The inner interface
Setting, functioning as a sub-projection,
returns the corresponding value for each configuration entry. The
first requirement, providing a list of profile names, is met with
the method getProfileNames().

The corresponding XPath expression selects all
attributes
name
of all elements
profile. As in the
examples of the first part of this series, we don’t care about the
XML structure. The simple XPath expression just does its’ job. The
inner interface
Setting is our
sub-projection to access all values of a profiles
settings.

The second method
“getAllSettingsForProfile(
String)”
defines a
String parameter which fills
the placeholder “{0}” in the XPath expression when
called.

This works because all parameters of projection
methods are applied via the class

java.text.MessageFormat to their XPath
expression. So it’s possible for the specification of the effective
target to have multiple parameters. To distinguish this concept
from static data projection, we call this a dynamic
projection.

The extensive syntax of the class
MessageFormat is described in [2]. Our first
example just takes a single

String:

Example 1: Source code
EclipseFormatterConfigFile configFile = new XBProjector().io().fromURLAnnotation(EclipseFormatterConfigFile.class);

System.out.println("Profile names:" + configFile.getProfileNames());

for (Setting setting:configFile.getAllSettingsForProfile("Some Profile")) {
    System.out.println(setting.getName()+" -> "+setting.getValue());
}

Having defined the projection interface, it just
takes four lines of code to print out the list of profiles and all
settings for one profile.

Now we come back to the first usage of the
annotation
@XBDocURL. It assigns the URL
of the target document to the projection, so the document can be
loaded from a declared origin. This happens when the method
projector.io().fromURLAnnotation(…) is called.

Apart from the standard protocols supported by
the class
java.net.URL, the projector
accepts a special identifier “resource” defining a location within
the class path. The class loader of the current projection
interface is used to load the document. So you just need to place
your document beside the projection interface to have it found by
the projector. After successfully loading and parsing the document,
the URL is set as the BaseURI in the DOM-Tree. Of course, there is
no need to declare the URL in the interface.
Calling
projector.io().url(“resource://eclipsecodeformatprofile.xml”).read(…)
will do as well.

There is a second use case for the
annotation
@XBDocURL. You can directly
use it on projection methods so that the target document of the
XPath-evaluation is no longer the document attached to the
projection interface, but an external document. In this way it’s
possible 
to access multiple documents within one
Java object. A simple example:

Example 2: Projection interface with external documents
public interface ExternalDocumentsDemo {
@XBDocURL("http://somewhere.examle/fooDocument.xml")
@XBRead("//foo")
String getFoo();

@XBDocURL("http://somewhere.examle/barDocument.xml")
@XBRead("//bar")
String getBar();
}

The interface
ExternalDocumentsDemo defines two projection
methods for two different external documents. When such a method is
called, the assigned XPath expression isn’t applied to the document
of the projection method anymore. It’s even possible to leave the
document for the projection empty.

Example 2: Using external documents
ExternalDocumentsDemo projection = new XBProjector().projectEmptyDocument(ExternalDocumentsDemo.class);
System.out.println("Content of foo:"+projection.getFoo());
System.out.println("Content of bar:"+projection.getBar());

Each method call results in loading the target
document, evaluating the XPath expression and returning the result
converted into the desired Java type. This can even be combined
with dynamic projections, because the parameters fill placeholders
also in the document URL.

Example 3: A generic XPath evaluator on external documents
public interface DynamicExternalDemo {
@XBDocURL("{0}")
@XBRead("{1}")
String evaluateXPathfromURL(String url, String xpath);
}

Example 3 demonstrates the expressiveness of
XMLBeam’s data projection. Creating a generic tool to extract data
from a document at a given URL is really simple using
XMLBeam.

Before we get too distracted by external or
dynamic projection gimmicks, we take a look at another subject:
Bidirectional projections.

In the first part of this series we already
learned that projections are views onto DOM-trees. Now we learn
that these views are not limited to reading elements or attributes.
Creating, reading, updating, and deleting of any DOM nodes is
supported. However, the XPath syntax has to be limited for all
writing operations. E.g. functions must not be used and the target
of the XPath selector must be distinct. When the expression
complies with these formalities, reading and writing are symmetric
operations. This means you read the same data you

wrote previously, and writing data you read before does not
change the document.

Example 4: A bidirectional projection
public interface Bidirectional {
@XBRead("/a/b/c/foo")
String getFoo();

@XBWrite("/a/b/c/foo")
void setFoo(String value)

@XBDelete("/a/b/c/foo")
void deleteFoo();
}

Declaring a writing projection method is similar
to declaring a reading one: Instead of
@XBRead,
the annotation @XBWrite has to be
used and the method needs a parameter for the value to be written.
The parameter type may be chosen freely as the method toString() is
used. Passing a
null value removes the
value from the projection target leaving behind an empty attribute
or element, respectively. To delete entire attributes or elements,
the annotation
@XBDelete is
used.

To this point we have seen dynamic projections
with parameters for the XPath expression and writing projections
with a parameter for the value. But how do we combine writing a
projection with a dynamic XPath target? This is shown in the next
example.

Example 5: A dynamic bidirectional projection
public interface DynamicBidirectional {
@XBWrite("/a/{1}/foo")
void setFoo(String value,String parentElementName)

@XBWrite("/a/b/foo[@key="{0}"]/@value")
void setValue(String key,@Value int value);
}

The method setFoo() gets another parameter which
is used in the XPath expression. The first one will be written as a
value to the element “foo”. For the second method “setValue(…)”
we decide to create our API the other way around. By just
annotating this parameter with @Value we change the second
parameter into the value parameter. We are going to come back to
writing projections when we explain the creation of complete
documents in the fourth part of this series

In the third part of this series, we are going
to illuminate the usage of XMLBeam with name spaces, show the
configuration abilities of the projector, and explain how to store
the XPath expressions apart from the Java code.

About the author

Sven Ewald (Twitter handle:@Cfx) is the author
of the library XMLBeam (@XMLBeam). He has been creating Java
solutions for 15 years with no end in sight. Currently he works in
the field of domain-specific languages for the automotive
industry

Links & Literature

[1] http://xmlbeam.org

[2]

http://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html

[3]

http://www.w3schools.com/dom/prop_attr_baseuri.asp

Author
Comments
comments powered by Disqus