A couple of weeks ago, having gotten annoyed about the amount of boilerplate you need in Java in order to do some very simple XML actions I decided to put something in place that would get me close to the fluency of the Groovy and JScript XML extensions with a very small interface. Indeed the original proposal can be contained in a tweet and the only methods I have added since are parent() and prefix() which doesn't quite fit in the standard 140 characters.
So there is basically only one class to worry about X, this allows you to read and write values, select children and perform the normal input and output operations. Everything throws unchecked exceptions, and where possible Attr and Elements are treated the same so for example set(val) and get() will work for both in a consistent manner.
You can find simpler examples in the unit tests in the github project, but the most interesting use cases for me are when used with JAX-RS to access and program REST resources that accept XML. XPath makes Duck Typing easy and if you are careful your code can ignore minor schema changes, this is in stark contrast to static JAX-B binding for example.
Take this example, it looks at a remote resource and sets it as being offline using the Jersey client. In the simplest case you need to explicitly tell Jersey about the X message body readers and writers; but other than that the code is quite straight forward.
ClientConfig clientConfig = new DefaultClientConfig( XMessageBodyWriter.class,XMessageBodyReader.class); Client client = Client.create(clientConfig); client.addFilter(new HTTPBasicAuthFilter(...)); WebResource resource = client.resource("http://hudson..."); resource.put( resource.get(X.class) .set("//n:offline", "true")); System.out.println(resource.get(X.class).get("//n:offline"));
Note in the last line we use the pre-defined namespace prefix n which is always populated by the first node in the tree. You can also perform the select, or selectList for a list, explicitly to get hold of the value:
resource.put( resource.get(X.class) .select("//n:offline").set("true")); System.out.println(resource.get(X.class) .select("//n:offline").get());
You can also use x! in JAX-RS resources, here is a very simple hello world example using the same message body readers and writers as before. Since they are marked as @Provider your container should be able to pick them up for you.
@Path("/hello") public class HelloMessage { @POST @Produces("text/xml") @Consumes("text/xml") public X hello(X input) { String name = input.select("//name").get(); X response = X.in("http://www.example.com", "message"); response .children().create("name") .set(name).parent() // Think of .parent() like a CR .children().create("message") .set("Hello " + name) .set("@lang", "en"); return response; } }
I am not entirely sure about the flow to create new objects, feedback is always appreciated of course. The use of @ to set attributes is quick as internally this doesn't result in a XPath query. Currently you can only work on direct children when creating new attributes, for more complex paths you need to select the direct parent to create new attributes. You can still set values on attributes that do exist with complex xpath expressions though.
// @attr exists x.select("....@attr").set("value"); or s.set("...@attr", "value");
Here is a simple request and response from this service just for comparison you can get the code to reflect the xml quite closely.
// Example input message <message> <name>Bob></name> </message> // Example response <message xmlns="http://www.example.com"> <name>Bob></name> <message lang="en">Hello Bob></message> </message>
This really was a thought experiment that got out of control; but I would welcome feedback, suggestions, and of course as it is on GitHub patches.
One interesting direction is replacing the current DOM implementation with a streaming version which might be possible for certain read and write options depending on the ordering of the data.