How to make JAXB unmarshaller to ignore prefixes?
Asked Answered
N

2

9

I have the following XML:

<ns2:Person name="John" age="20" />

And I want to unmarshal it to JAXB object Person which was generated from the XSD.

this is the code I'm running:

JAXBContext context = JAXBContext.newInstance(PersoEntity.class);
Unmarshaller um = context.createUnmarshaller();
StringReader sr = new StringReader(xml);
Person p = (Person)um.unmarshal(sr);

Surprisingly I get the following exception:

javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: The prefix "ns2" for element "ns2:Person" is not bound.]

How do I solve it? Thanks

Newcastle answered 15/8, 2012 at 11:21 Comment(2)
you only posted one xml tag... is this really your case, i.e you want to unmarshall just this segment (or there's a whole xml document, which should have xmlns:ns2 defined) ?Ritchey
you guessed right, only this segment is what I wantNewcastle
E
7

GETTING THE FRAGMENT

The way that you are currently getting the XML fragment is causing the namespace declarations to be lost. In your fragment ns2 is no longer a prefix, you just have a element name with a colon in it (ns2:Person). This is going to cause problems with namespace aware parsers. The article below may be a better approach for you to get the XML fragment:

HANDLING YOUR USE CASE

Using the XML fragment that you have, you could create an XMLFilter that removes the prefix from the XML element, and then leverage JAXB's UnmarshallerHandler to do the unmarshalling.

Demo

package forum11968399;

import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLFilterImpl;

public class Demo {

    private static final String xml = "<ns2:Person name='John' age='20' />";

    public static void main(String[] args) throws Exception {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = spf.newSAXParser();
        XMLReader xmlReader = sp.getXMLReader();
        XMLFilter xmlFilter = new MyXMLFilter(xmlReader);

        JAXBContext context = JAXBContext.newInstance(PersonEntity.class);
        Unmarshaller um = context.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = um.getUnmarshallerHandler();
        xmlFilter.setContentHandler(unmarshallerHandler);

        StringReader sr = new StringReader(xml);
        xmlFilter.parse(new InputSource(sr));
        PersonEntity p = (PersonEntity) unmarshallerHandler.getResult();
    }

    private static class MyXMLFilter extends XMLFilterImpl {

        public MyXMLFilter(XMLReader xmlReader) {
            super(xmlReader);
        }

        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes attributes) throws SAXException {
            int colonIndex = qName.indexOf(':');
            if(colonIndex >= 0) {
                qName = qName.substring(colonIndex + 1);
            }
uri = XML_NAMESPACE; //to prevent unknown XML element exception, we have to specify the namespace here
            super.startElement(uri, localName, qName, attributes);
        }

        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException {
            int colonIndex = qName.indexOf(':');
            if(colonIndex >= 0) {
                qName = qName.substring(colonIndex + 1);
            }
            super.endElement(uri, localName, qName);
        }

    }

}

PersonEntity

package forum11968399;

import javax.xml.bind.annotation.*;

@XmlRootElement(name="Person")
@XmlAccessorType(XmlAccessType.FIELD)
public class PersonEntity {

    @XmlAttribute
    private String name;

    @XmlAttribute
    private int age;

}
Engdahl answered 15/8, 2012 at 13:58 Comment(1)
your solution almost worked. I had to modify it a bit. Even though we got rid of the prefix, I still got an exception of an unknown XML element due to namespace issues. So I just put the namespace in the Uri variable and it worked :)Newcastle
F
1

Your best bet is probably to nest the desired element inside another element that binds the namespace. It doesn't really matter what you bind it to, just make it a valid XML document that will parse. Then you can unmarshal by declared type

JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller um = context.createUnmarshaller();
String xml = "<ns2:Person name=\"John\" age=\"20\" />";
String xmlWithPrefixMapped = "<ns2:FakeElement xmlns:ns2=\"someuri\">" + xml + "</ns2:FakeElement>";
StringReader sr = new StringReader(xmlWithPrefixMapped);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(sr));
Node n = (Node) doc.getDocumentElement().getFirstChild();
JAXBElement<Person> personElement = um.unmarshal(n, Person.class);
Person p = personElement.getValue();
Furtherance answered 15/8, 2012 at 11:36 Comment(2)
I get the following: The method unmarshal(Node, Class<T>) in the type Unmarshaller is not applicable for the arguments (Element, Class<Person>)Newcastle
This worked for me, although instead of nesting it inside a new element, I just added the namespace info into the top-level tag I was getting.Beaconsfield

© 2022 - 2024 — McMap. All rights reserved.