How to generate “svn info --xml” from Java code

In Java, there is the great SVNKit which I have used for many a customer project successfully. You can do all Subversion operations in a few lines of Java, with a great object-oriented interface, exceptions, and so on. It's easy to use, and it just works.

Today I had to produce certain XML files programmatically based on "svn info –xml" information. I was glad to see there was a SVNXMLInfoHandler which allows you to write the XML into any SAX ContentHandler. Again, great software design, it's exactly what you want.

Alas, it didn't quite work. At least in my setup of using SAXON and Xerces for XML processing, which we are already using for the project in hand. (In a different part of the software, XSLT 2.0 processing is done, and SAXON's about the only library I know for any language that can do it.)

The problems were:

  1. That SAX events were written to the stream but the document was never started/ended,
  2. The source provided no namespace information, but SAXON requires namespace information (even if just to explicitly say that the "empty namespace" is used.

Unhelpfully, regarding point (2), I got the error

org.xml.sax.SAXException:
Parser configuration problem: namespace reporting is not enabled

I didn't really know what this meant, especially in the context of SVNKit producing "svn info --xml" information. Looking at the source of SAXON, it turns out that the wording of the error makes sense if you're using an XML parser to feed the XML tags into SAX, as opposed to e.g. SVNKit. In that case, the XML parser would have a document with namespace information, and could "report" it to SAX, or not. So, in other words, SVNKit wasn't producing tags with namespace information, and SAX didn't like that.

Looking at the source of SVNKit we can see lines like:

getHandler().startElement("", "", tagName, mySharedAttributes);

So startElement is being called e.g. like

startElement("", "", "info").

What SAXON needs is something like

startElement("my-namespace-url", "info", "ns:info").

So alas I had to develop the following class, which can be used as follows:

class SvnKitDomCreator extends IdentityForwardingSaxHandler {

  public SvnKitDomCreator(ContentHandler destination)
  throws SAXException {
    super(destination);
    startDocument();
    startPrefixMapping("svn", ns);
    startElement("", "", commandType.name(), new AttributesImpl());
  }

  @Override public void startElement(
    String uri, String localName, String qName, Attributes noNsAttr
  ) throws SAXException {
    AttributesImpl nsAttr = new AttributesImpl();
    for (int i = 0; i < noNsAttr.getLength(); i++)
      nsAttr.addAttribute("", noNsAttr.getQName(i),
        "svn:" + noNsAttr.getQName(i), noNsAttr.getType(i),
        noNsAttr.getValue(i));
    super.startElement(ns, qName, "svn:" + qName, nsAttr);
  }

  @Override public void endElement(
    String uri, String localName, String qName
  ) throws SAXException {
    super.endElement(ns, qName, "svn:" + qName);
  }

  public void close() throws SAXException {
    endElement("", "", commandType.name());
    endPrefixMapping("svn");
    endDocument();
  }
}

SvnKitDomCreator domWriterWithLocalName = 
  new SvnKitDomCreator(domWriter);
clientManager.getWCClient().doInfo(repository,
  SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.EMPTY,
  new SVNXMLInfoHandler(domWriterWithLocalName));

The source for the referenced IdentityForwardingSaxHandler is LGPL: GitHub.

P.S. Hooray for open source! I'm sure I'd never have managed to get to the bottom of this if I hadn't had the source for SVNKit and SAXON. I would have just been stuck with the conclusion that SVNKit produced XML, SAXON consumed XML, and "something" to do namespaces was going wrong.

SVNKit 1.7

P.S. I recently created a nerdy privacy-respecting tool called When Will I Run Out Of Money? It's available for free if you want to check it out.

This article is © Adrian Smith.
It was originally published on 12 Dec 2012
More on: FAIL | Java