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:
- That SAX events were written to the stream but the document was never started/ended,
- 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