JAXB: How to ignore namespace during unmarshalling XML document?
Asked Answered
G

6

82

My schema specifies a namespace, but the documents don't. What's the simplest way to ignore namespace during JAXB unmarshalling (XML -> object)?

In other words, I have

<foo><bar></bar></foo>

instead of,

<foo xmlns="http://tempuri.org/"><bar></bar></foo>
Gormley answered 10/11, 2008 at 10:9 Comment(3)
My problem was actually the opposite - I have some documents with xmlns attributes (on one or more elements), and some without. @lunicon's solution allows me to read both styles.Engine
Hi, I am also having the exact same issue. How did you fix it? I am using the XMLStreamReader and used the approach mentioned in answer but that does not work for me for some reason.Delete
I tried a similar approach but did not work for me so I have added the question here can you please once check and respond? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Delete
S
19

I believe you must add the namespace to your xml document, with, for example, the use of a SAX filter.

That means:

  • Define a ContentHandler interface with a new class which will intercept SAX events before JAXB can get them.
  • Define a XMLReader which will set the content handler

then link the two together:

public static Object unmarshallWithFilter(Unmarshaller unmarshaller,
java.io.File source) throws FileNotFoundException, JAXBException 
{
    FileReader fr = null;
    try {
        fr = new FileReader(source);
        XMLReader reader = new NamespaceFilterXMLReader();
        InputSource is = new InputSource(fr);
        SAXSource ss = new SAXSource(reader, is);
        return unmarshaller.unmarshal(ss);
    } catch (SAXException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } catch (ParserConfigurationException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } finally {
        FileUtil.close(fr); //replace with this some safe close method you have
    }
}
Surname answered 10/11, 2008 at 10:14 Comment(5)
Why are there spam advertisement links in this post?Spooner
@Spooner I am sorry, I have restored the proper link (with web.archive.org). Please consider that this link was not spam advertisement when I wrote the answer... 7 years ago ;)Surname
@Inequality No update from my side. If you find any update, don't hesitate to update this answer.Surname
ok, maybe not exactly outdated, but i was missing the NamespaceFilterXMLReader. Actually Kristofer's highly scored post provides oneInequality
I tried a similar approach but did not work for me so I have added the question here can you please once check and respond? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Delete
H
109

Here is an extension/edit of VonCs solution just in case someone doesn´t want to go through the hassle of implementing their own filter to do this. It also shows how to output a JAXB element without the namespace present. This is all accomplished using a SAX Filter.

Filter implementation:

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import org.xml.sax.helpers.XMLFilterImpl;

public class NamespaceFilter extends XMLFilterImpl {

    private String usedNamespaceUri;
    private boolean addNamespace;

    //State variable
    private boolean addedNamespace = false;

    public NamespaceFilter(String namespaceUri,
            boolean addNamespace) {
        super();

        if (addNamespace)
            this.usedNamespaceUri = namespaceUri;
        else 
            this.usedNamespaceUri = "";
        this.addNamespace = addNamespace;
    }



    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        if (addNamespace) {
            startControlledPrefixMapping();
        }
    }



    @Override
    public void startElement(String arg0, String arg1, String arg2,
            Attributes arg3) throws SAXException {

        super.startElement(this.usedNamespaceUri, arg1, arg2, arg3);
    }

    @Override
    public void endElement(String arg0, String arg1, String arg2)
            throws SAXException {

        super.endElement(this.usedNamespaceUri, arg1, arg2);
    }

    @Override
    public void startPrefixMapping(String prefix, String url)
            throws SAXException {


        if (addNamespace) {
            this.startControlledPrefixMapping();
        } else {
            //Remove the namespace, i.e. don´t call startPrefixMapping for parent!
        }

    }

    private void startControlledPrefixMapping() throws SAXException {

        if (this.addNamespace && !this.addedNamespace) {
            //We should add namespace since it is set and has not yet been done.
            super.startPrefixMapping("", this.usedNamespaceUri);

            //Make sure we dont do it twice
            this.addedNamespace = true;
        }
    }

}

This filter is designed to both be able to add the namespace if it is not present:

new NamespaceFilter("http://www.example.com/namespaceurl", true);

and to remove any present namespace:

new NamespaceFilter(null, false);

The filter can be used during parsing as follows:

//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Unmarshaller u = jc.createUnmarshaller();

//Create an XMLReader to use with our filter
XMLReader reader = XMLReaderFactory.createXMLReader();

//Create the filter (to add namespace) and set the xmlReader as its parent.
NamespaceFilter inFilter = new NamespaceFilter("http://www.example.com/namespaceurl", true);
inFilter.setParent(reader);

//Prepare the input, in this case a java.io.File (output)
InputSource is = new InputSource(new FileInputStream(output));

//Create a SAXSource specifying the filter
SAXSource source = new SAXSource(inFilter, is);

//Do unmarshalling
Object myJaxbObject = u.unmarshal(source);

To use this filter to output XML from a JAXB object, have a look at the code below.

//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Marshaller m = jc.createMarshaller();

//Define an output file
File output = new File("test.xml");

//Create a filter that will remove the xmlns attribute      
NamespaceFilter outFilter = new NamespaceFilter(null, false);

//Do some formatting, this is obviously optional and may effect performance
OutputFormat format = new OutputFormat();
format.setIndent(true);
format.setNewlines(true);

//Create a new org.dom4j.io.XMLWriter that will serve as the 
//ContentHandler for our filter.
XMLWriter writer = new XMLWriter(new FileOutputStream(output), format);

//Attach the writer to the filter       
outFilter.setContentHandler(writer);

//Tell JAXB to marshall to the filter which in turn will call the writer
m.marshal(myJaxbObject, outFilter);

This will hopefully help someone since I spent a day doing this and almost gave up twice ;)

Hinkle answered 27/1, 2010 at 16:26 Comment(5)
Does this solution work with multiple nested XML objects which use multiple namespaces throughout the document? I have attempted to use this example in such a scenario and found that while it is capable of removing namespaces for the first two levels in an XML document (root element and children of root), it does not appear to filter out the namespaces beyond that. In order to unmarshal such an XML document I had to use namespace declarations for the grandchildren of the root element and below.Samaveda
I'm sure people would like to see that too if you're willing to share your improved filter...Hinkle
Why jaxb doesn't give you a better error message and requires these gymnastics at all, is beyond me. This is an incredibly prevalent problem that almost everyone will face!Numerable
Thanks a lot! Works like a charm ... kind of absurd that all these are needed just to ignore defunct namespaces in vendor files :-)Lynching
This works nicely, but if you only want to remove the namespace, try option 3) from Jaxb ignore the namespace on unmarshalling which uses a SAXParserFactory with setNamespaceAware(false)Dott
C
44

I have encoding problems with XMLFilter solution, so I made XMLStreamReader to ignore namespaces:

class XMLReaderWithoutNamespace extends StreamReaderDelegate {
    public XMLReaderWithoutNamespace(XMLStreamReader reader) {
      super(reader);
    }
    @Override
    public String getAttributeNamespace(int arg0) {
      return "";
    }
    @Override
    public String getNamespaceURI() {
      return "";
    }
}

InputStream is = new FileInputStream(name);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithoutNamespace xr = new XMLReaderWithoutNamespace(xsr);
Unmarshaller um = jc.createUnmarshaller();
Object res = um.unmarshal(xr);
Copacetic answered 24/6, 2014 at 12:49 Comment(6)
I was about to try and implement Kristofer's solution when I noticed yours, which was vastly simpler and did the trick for me, thanks! It's still too complicated, though, why do we have to do this? JAXB should offer a built-in solution like a property setting for this common situation.Engine
Also don't forget to close the FileInputStream :)Engine
this did not ignore the namespace contained in package.info. so instead have the methods getNamespaceURI return the contents of package.info. In which case the XMLReaderWithoutNamespace should instead be XMLReaderWithNamespaceInMyPackageDotInfoChristianson
I tried this approach but for some reason its not working for me and fails durin the unmarshallingDelete
@BATMAN_2008, use um.unmarshal(xr, YourRootClass.class);Copacetic
@Copacetic I tried that but it did not work for me so I have added the question here can you please once check and respond? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Delete
S
19

I believe you must add the namespace to your xml document, with, for example, the use of a SAX filter.

That means:

  • Define a ContentHandler interface with a new class which will intercept SAX events before JAXB can get them.
  • Define a XMLReader which will set the content handler

then link the two together:

public static Object unmarshallWithFilter(Unmarshaller unmarshaller,
java.io.File source) throws FileNotFoundException, JAXBException 
{
    FileReader fr = null;
    try {
        fr = new FileReader(source);
        XMLReader reader = new NamespaceFilterXMLReader();
        InputSource is = new InputSource(fr);
        SAXSource ss = new SAXSource(reader, is);
        return unmarshaller.unmarshal(ss);
    } catch (SAXException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } catch (ParserConfigurationException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } finally {
        FileUtil.close(fr); //replace with this some safe close method you have
    }
}
Surname answered 10/11, 2008 at 10:14 Comment(5)
Why are there spam advertisement links in this post?Spooner
@Spooner I am sorry, I have restored the proper link (with web.archive.org). Please consider that this link was not spam advertisement when I wrote the answer... 7 years ago ;)Surname
@Inequality No update from my side. If you find any update, don't hesitate to update this answer.Surname
ok, maybe not exactly outdated, but i was missing the NamespaceFilterXMLReader. Actually Kristofer's highly scored post provides oneInequality
I tried a similar approach but did not work for me so I have added the question here can you please once check and respond? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Delete
G
5

In my situation, I have many namespaces and after some debug I find another solution just changing the NamespaceFitler class. For my situation (just unmarshall) this work fine.

 import javax.xml.namespace.QName;
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.XMLFilterImpl;
 import com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector;

 public class NamespaceFilter extends XMLFilterImpl {
    private SAXConnector saxConnector;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        if(saxConnector != null) {
            Collection<QName> expected = saxConnector.getContext().getCurrentExpectedElements();
            for(QName expectedQname : expected) {
                if(localName.equals(expectedQname.getLocalPart())) {
                    super.startElement(expectedQname.getNamespaceURI(), localName, qName, atts);
                    return;
                }
            }
        }
        super.startElement(uri, localName, qName, atts);
    }

    @Override
    public void setContentHandler(ContentHandler handler) {
        super.setContentHandler(handler);
        if(handler instanceof SAXConnector) {
            saxConnector = (SAXConnector) handler;
        }
    }
}
Grayling answered 7/12, 2012 at 11:23 Comment(0)
S
1

Another way to add a default namespace to an XML Document before feeding it to JAXB is to use JDom:

  1. Parse XML to a Document
  2. Iterate through and set namespace on all Elements
  3. Unmarshall using a JDOMSource

Like this:

public class XMLObjectFactory {
    private static Namespace DEFAULT_NS = Namespace.getNamespace("http://tempuri.org/");

    public static Object createObject(InputStream in) {
        try {
            SAXBuilder sb = new SAXBuilder(false);
            Document doc = sb.build(in);
            setNamespace(doc.getRootElement(), DEFAULT_NS, true);
            Source src = new JDOMSource(doc);
            JAXBContext context = JAXBContext.newInstance("org.tempuri");
            Unmarshaller unmarshaller = context.createUnmarshaller();
            JAXBElement root = unmarshaller.unmarshal(src);
            return root.getValue();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create Object", e);
        }
    }

    private static void setNamespace(Element elem, Namespace ns, boolean recurse) {
        elem.setNamespace(ns);
        if (recurse) {
            for (Object o : elem.getChildren()) {
                setNamespace((Element) o, ns, recurse);
            }
        }
    }
Subdue answered 28/11, 2008 at 16:14 Comment(2)
The only problem with this though is that you have to read the entire XML file into memory, which isn't an option with massive XML files.Unmarked
I have added the question here can you please once check and provide response? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Delete
C
0

This is just a modification of lunicon's answer (https://mcmap.net/q/242094/-jaxb-how-to-ignore-namespace-during-unmarshalling-xml-document) if you want to replace one namespace for another during parsing. And if you want to see what exactly is going on, just uncomment the output lines and set a breakpoint.

public class XMLReaderWithNamespaceCorrection extends StreamReaderDelegate {

    private final String wrongNamespace;
    private final String correctNamespace;

    public XMLReaderWithNamespaceCorrection(XMLStreamReader reader, String wrongNamespace, String correctNamespace) {
        super(reader);

        this.wrongNamespace = wrongNamespace;
        this.correctNamespace = correctNamespace;
    }

    @Override
    public String getAttributeNamespace(int arg0) {
//        System.out.println("--------------------------\n");
//        System.out.println("arg0: " + arg0);
//        System.out.println("getAttributeName: " + getAttributeName(arg0));
//        System.out.println("super.getAttributeNamespace: " + super.getAttributeNamespace(arg0));
//        System.out.println("getAttributeLocalName: " + getAttributeLocalName(arg0));
//        System.out.println("getAttributeType: " + getAttributeType(arg0));
//        System.out.println("getAttributeValue: " + getAttributeValue(arg0));
//        System.out.println("getAttributeValue(correctNamespace, LN):"
//                + getAttributeValue(correctNamespace, getAttributeLocalName(arg0)));
//        System.out.println("getAttributeValue(wrongNamespace, LN):"
//                + getAttributeValue(wrongNamespace, getAttributeLocalName(arg0)));

        String origNamespace = super.getAttributeNamespace(arg0);

        boolean replace = (((wrongNamespace == null) && (origNamespace == null))
                || ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
        return replace ? correctNamespace : origNamespace;
    }

    @Override
    public String getNamespaceURI() {
//        System.out.println("getNamespaceCount(): " + getNamespaceCount());
//        for (int i = 0; i < getNamespaceCount(); i++) {
//            System.out.println(i + ": " + getNamespacePrefix(i));
//        }
//
//        System.out.println("super.getNamespaceURI: " + super.getNamespaceURI());

        String origNamespace = super.getNamespaceURI();

        boolean replace = (((wrongNamespace == null) && (origNamespace == null))
                || ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
        return replace ? correctNamespace : origNamespace;
    }
}

usage:

InputStream is = new FileInputStream(xmlFile);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithNamespaceCorrection xr =
    new XMLReaderWithNamespaceCorrection(xsr, "http://wrong.namespace.uri", "http://correct.namespace.uri");
rootJaxbElem = (JAXBElement<SqgRootType>) um.unmarshal(xr);
handleSchemaError(rootJaxbElem, pmRes);
Corrody answered 20/10, 2020 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.