Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB and namespace-less XML

Tags:

java

xml

jaxb

xjc

There is a vendor-supplied XML like this:

<?xml version="1.0" encoding="utf-8"?>
<Foo>
  <Bar>...</Bar>
  <Bar>...</Bar>
</Foo>

Note there is no xmlns="..." declaration, nor does the vendor supply a schema. This can't be changed, and the vendor will continue to ship XML like this in the future.

To generate JAXB bindings, I've created a schema like this:

<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://acme.com/schema"
            xmlns:tns="http://acme.com/schema"
            elementFormDefault="qualified">
    <xsd:element name="Foo">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element ref="tns:Bar" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Bar">
        ...
    </xsd:element>
</xsd:schema>

Note that I've declared a more or less meaningful namespace ("http://acme.com/schema") so that it could be used for element references, etc. XJC generates the following package-info.java:

@javax.xml.bind.annotation.XmlSchema(namespace = "http://acme.com/schema", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.acme.schema;

Then I try to unmarshal XML document:

JAXBContext jaxb = JAXBContext.newInstance("com.acme.schema");
Unmarshaller unmarshaller = jaxb.createUnmarshaller();
InputStream is = this.getClass().getClassLoader().getResourceAsStream("test.xml");

InputSource source = new InputSource(is);
Foo foo = (Foo) unmarshaller.unmarshal(source);

Here's the exception I get:

javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"Foo"). Expected elements are <{http://acme.com/schema}Foo>,...>

Obviously, this happens because XML elements belong to an empty namespace, while JAXB classes have a non-empty one.

Is there a way to fake an XML namespace (probably during XML parsing), so that JAXB would recognize the elements and successfully bind them? SAX/StAX solutions would be preferred over DOM, because XML documents could be rather huge.

like image 648
Martin Schmidt Avatar asked Oct 30 '22 03:10

Martin Schmidt


1 Answers

First, I wouldn't recommend all of this. Integrating with a third-party API is complicated enough without adding additional complication. Why bother adding a namespace at all? I'm not sure what you get out of it. Think of the lucky person that gets to inherit your code base. They'll see the addition of the namespace but have no idea why you're doing that.

I would even go one step further and suggest avoiding the schema entirely and just using annotated POJOs. All of these steps just add complication, build steps, etc.

However, if you're determined, this seems like the quintessential case for XSL transformations. You can find an XSL transform that adds namespaces easily enough, such as this question Then it is a simple matter of wiring your transformation into JAXB...

private static Foo unmarshalDocument(InputStream xslStream, InputStream xmlStream) throws Exception {
    StreamSource stylesource = new StreamSource(xslStream); 
    StreamSource inputStream = new StreamSource(xmlStream);
    Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
    JAXBResult result = new JAXBResult(context);
    transformer.transform(inputStream, result);
    return (Foo) result.getResult();
}
like image 192
Pace Avatar answered Nov 15 '22 07:11

Pace