I have a problem with unmarshaling xml. When I unmarshal an XML that hasn't got any attributes it works just fine but when I try to unmarshal an XML that has an attribute in any element I get only null values.
Here is my code:
<Document xmlns="sd">
<CstmrCdtTrfInitn>
<GrpHdr>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>100</CtrlSum>
<blabla>bla</blabla>
</GrpHdr>
<PmtInf>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>100</CtrlSum>
<blabla>bla</blabla>
</PmtInf>
</CstmrCdtTrfInitn>
</Document>
The xml schema:
package XMLElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "GrpHdr")
#XmlAccessorType(XmlAccessType.FIELD)
public class GrpHdr {
#XmlElement(name = "MsgId")
private String msgId;
#XmlElement(name = "CreDtTm")
private String creDtTm;
#XmlElement(name = "NbOfTxs")
private String nbOfTxs;
#XmlElement(name = "CtrlSum")
private String ctrlSum;
#XmlElement(name = "InitgPty")
private String initgPty;
// MsgId
public String getMsgId() {
return msgId;
}
public void setMsgId(String string_msgId) {
msgId = string_msgId;
}
// CreDtTm
public String getCreDtTm() {
return creDtTm;
}
public void setCreDtTm(String string_creDtTm) {
creDtTm = string_creDtTm;
}
// NbOfTxs
public String getNbOfTxs() {
return nbOfTxs;
}
public void setNbOfTxs(String string_nbOfTxs) {
nbOfTxs = string_nbOfTxs;
}
// CtrlSum
public String getCtrlSum() {
return ctrlSum;
}
public void setCtrlSum(String string_ctrlSum) {
ctrlSum = string_ctrlSum;
}
// InitgPty
public String getInitgPty() {
return initgPty;
}
public void setInitgPty(String string_initgPty) {
initgPty = string_initgPty;
}
}
And the Main.java
package application;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;
import XMLElement.CstmrCdtTrfInitn;
import XMLElement.Document;
import XMLElement.GrpHdr;
import XMLElement.PmtInf;
public class Main {
public static void main(String[] args) throws Exception {
XMLInputFactory xif2 = XMLInputFactory.newFactory();
StreamSource xml2 = new StreamSource("D:\\test2.xml");
XMLStreamReader xsr2 = xif2.createXMLStreamReader(xml2);
xsr2.nextTag();
while(xsr2.hasNext()) {
if(xsr2.isStartElement() &&
xsr2.getLocalName().equals("GrpHdr")) {
break;
}
xsr2.next();
}
JAXBContext jc2 = JAXBContext.newInstance(GrpHdr.class);
Unmarshaller unmarshaller2 = jc2.createUnmarshaller();
JAXBElement<GrpHdr> jb2 = unmarshaller2.unmarshal(xsr2,
GrpHdr.class);
xsr2.close();
GrpHdr grpHdr = jb2.getValue();
System.out.println(grpHdr.getNbOfTxs());
System.out.println(grpHdr.getCtrlSum());
}
}
When I remove the xmlns="sd" i get the result without any problem, but otherwise I get only null. Any suggestions on what I am doing wrong?
Note that xmlns stands for XML Namespace. So, if you have namespace in your XML document it will going to bind all tags of XML document with that namespace, so, it's obvious that XML unmarshalling won't work because your tag would be say sd:XYZ and JAXB would be checking for XYZ only.
You have to declare namespace in your XML mapping class as well to make this work with namespace (or remove namespace if you don't need it). You can add XmlSchema annotation in your document class to make it work with xmlns attribute.
#XmlSchema(
namespace = "http://www.example.com/namespaceURI",
elementFormDefault = XmlNsForm.QUALIFIED,
xmlns = {
#XmlNs(prefix="sd", namespaceURI="http://www.example.com/namespaceURI")
}
)
Related
My application's config file that I load, myconfig.app:
<?xml version="1.0" encoding="UTF-8"?>
<application
xmlns="http://www.example.com/com/code/app"
authentication="form">
<dummies>
<dummy id="dummy1"/>
</dummies>
</application>
Application.java
package com.code.custom;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = Application.XML_ELEMENT)
public class Application implements Serializable {
public static final String XML_ELEMENT = "application";
#XmlAttribute(name = "authentication")
protected Boolean authentication;
public Application() {}
public Boolean getAuthentication() {
return authentication;
}
}
Loading of file content:
InputStream inputStream = ..
JAXBContext context = JAXBContext.newInstance(Application.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Application obj = (Application) unmarshaller.unmarshal(inputStream);
Error:
unexpected element (uri:"http://www.example.com/com/code/app", local:"application"). Expected elements are <{}application>
I would like to deserialize XML like the following using JAXB in Java:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
inner text that I need
<foo attrib="meh">
<bar>value</bar>
</foo>
</container>
</root>
The thing that is tripping me up is capturing the inner text of <container>: I can't use both an #XmlValue to get the inner text and #XmlElement to grab foo elements that come after the inner text. See below for an outline of what I am looking to do
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
public class App {
private static final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><container>text<foo attrib=\"meh\"><bar>waffles</bar></foo></container></root>";
#XmlRootElement(name = "foo") static class Foo {
#XmlAttribute public String attrib;
#XmlElement public String bar;
}
#XmlRootElement(name = "container") static class Container {
//#XmlValue public String innerText;
#XmlElement public Foo foo;
}
public static void main(String[] args) {
try {
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final XMLEventReader xer = xmlInputFactory.createXMLEventReader(new ByteArrayInputStream(xml.getBytes("UTF-8")));
XMLEvent evt = xer.nextEvent(); // start document
evt = xer.nextEvent(); // <root>
evt = xer.peek(); // advance to <container>
JAXBContext ctx = JAXBContext.newInstance(Container.class);
Unmarshaller um = ctx.createUnmarshaller();
Object o = um.unmarshal(xer);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (XMLStreamException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (JAXBException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
This is called "mixed-mode content", and it's generally a pain in the ass to process.
The key in JAXB is to use the #XmlMixed annotation - see javadoc.
Try something like this:
#XmlRootElement(name = "container")
static class Container {
#XmlMixed
#XmlElementRefs({
#XmlElementRef(name="foo", type=Foo.class)
})
List<?> content;
// ... plus the usual getters/setters
}
The content list should contain a sequence of Foo objects and Strings.
I'm working in a legacy product that heavily relies on version 1 of the org.jdom project (http://mvnrepository.com/artifact/org.jdom/jdom/1.1.3) and manually constructed XMLs but I would like to use Jaxb as much as possible instead. We're using Moxy as the Jaxb implementation.
So say that I have the following xml:
<foo bar="bar">
<baz>
<test value="something" />
</baz>
</foo>
And because of legacy code using the baz element as an org.jdom.Element I would like to have Jaxb unmarshall the inner element "baz" to an org.jdom.Element but v in unmarshal is always an empty string ("") so baz becomes null.
I've created an example below where I try to use an XmlAdapter but I can't get it to work.
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
public class Test {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement
private static final class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "Foo [bar=" + bar + ", baz=" + baz + "]";
}
}
private static final class ElementAdapter extends XmlAdapter<String, Element> {
#Override
public Element unmarshal(String v) throws Exception {
Document document = new SAXBuilder().build(new StringReader(v));
return document.getRootElement();
}
#Override
public String marshal(Element v) throws Exception {
return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(v);
}
}
}
Maybe I'm attacking this from the wrong angle. Any suggestions on how to achieve this?
A good way to debug issues when unmarshalling is to use the javax.xml.bind.helpers.DefaultValidationEventHandler.
In your case, you can simply add it to your unmarshaller in the main method, e.g.
// ...
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
// ...
When you run your test program as-is with the validation handler attached, you'll see something like the following, which is basically telling you you have no Java class corresponding to the test element in your XML.
[Exception [EclipseLink-25004] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred unmarshalling the document
Internal Exception: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 47; unexpected element (uri:"", local:"test"). Expected elements are (none)]
I think you are assuming that your ElementAdapter implementation will read & consume the baz node and all children as a string. In actuality, I think (paging Blaise Doughan?) what's happening is that JAXB is doing some pre-walking of the XML tree, during which it sees that you have no model for test and subsequently discards test and its attribute.
To get a feel for what is going on here, first simplify your model and don't worry about the JDOM Element conversion. Note that I've changed the name of your top level class so it doesn't conflict with the Test model class:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement()
public static class Foo {
#XmlAttribute
public String bar;
#XmlElement
public Baz baz;
#Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
#XmlRootElement
public static class Baz {
#XmlElement
public Test test;
#Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
#XmlRootElement
public static class Test {
#XmlAttribute
public String value;
#Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
}
This should unmarshal correctly and print <foo bar="bar"><baz><test value="something"/></baz></foo>. Also note that normally you would pass the JAXBContextFactory an ObjectFactory containing the entire object graph, which would be generated from your XML Schema. This may be a source of confusion.
Now you can add your XmlAdapter back, but instead of attempting to convert from String to Element, convert from Baz to Element:
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement()
public static class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
#XmlRootElement
public static class Baz {
#XmlElement
public Test test;
#Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
#XmlRootElement
public static class Test {
#XmlAttribute
public String value;
#Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
public static class ElementAdapter extends XmlAdapter<Baz, Element> {
#Override
public Element unmarshal(Baz baz) throws Exception {
StringWriter sw = new StringWriter();
// Note: it is a terrible idea to re-instantiate a context here
// Use a cached value or a singleton from before
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Baz.class}, null);
javax.xml.bind.Marshaller m = jaxbContext.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(baz, sw);
Document document = new SAXBuilder().build(new StringReader(sw.toString()));
return document.getRootElement();
}
#Override
public Baz marshal(Element v) throws Exception {
// TODO implement this
return null;
}
}
}
This should more or less do what you want, though you may have to clean up the Baz element some in the adapter (get rid of the XML preamble, etc.) before converting to JDOM. Do note that instantiating another JAXBContext in the ElementAdapter is probably a terrible idea, and there may be a more efficient way to convert from a JAXB Element to a JDOM Element. This code is just easy to step through in a debugger so you can see what's going on.
Thought I would share a solution that worked for me.
Inspired by this post Jaxb: how to unmarshall xs:any XML-string part? that mentions that Jaxb leaves anything it doesn't know how to map as DOM element when marked as #XmlAnyElement(lax = true) I ended up with an XmlAdapter using org.w3c.dom.Element as an intermediate class that Jaxb knows how to map to and from.
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import org.jdom.Element;
import org.jdom.output.DOMOutputter;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Document;
public class Test {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement
private static final class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "Foo [bar=" + bar + ", baz=" + jdomElementToString(baz) + "]";
}
private String jdomElementToString(Element element) {
return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(element);
}
}
private static final class ElementAdapter extends XmlAdapter<org.w3c.dom.Element, Element> {
#Override
public Element unmarshal(org.w3c.dom.Element valueToUnmarshal) throws Exception {
org.jdom.input.DOMBuilder domBuilder = new org.jdom.input.DOMBuilder();
org.jdom.Element jdomElement = domBuilder.build(valueToUnmarshal);
return jdomElement;
}
#Override
public org.w3c.dom.Element marshal(Element elementToMarshal) throws Exception {
org.jdom.Document jdomDocument = new org.jdom.Document((Element) elementToMarshal.detach());
DOMOutputter domOutputter = new DOMOutputter();
Document domDocument = domOutputter.output(jdomDocument);
return domDocument.getDocumentElement();
}
}
}
The print out from running this is:
Foo [bar=bar, baz=<baz>
<test value="something" />
</baz>]
And as you can see what is in the baz tag is assigned "as-is" to the variable in the Foo class.
We're using JAXB to unmarshall a fragment of XML which looks a bit like this:
<someRandomElement xmlns:foo="http://example.com/foo" xpath="//foo:bar" />
Our object model is:
#XmlRootElement(name="someRandomElement")
class SomeRandomClass {
#XmlAttribute(name="xpath")
private XPathFragment _expression;
}
class XPathFragment {
String _expr;
// we need this to look up namespace prefixes used in _expr
Node _parentNode;
}
So my question is, how can I unmarshall the XPathFragment from the XML using JAXB?
I have tried using a custom XmlAdapter for XPathFragment, but this doesn't seem to have an opportunity to access the DOM Nodes corresponding to the someRandomElement and its attributes.
You could leverage the ability to pass an initialized XmlAdapter to an unmarshaller.
XPathFragmentAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.w3c.dom.Document;
public class XPathFragmentAdapter extends XmlAdapter<String, XPathFragment>{
private Document document;
public XPathFragmentAdapter() {
}
public XPathFragmentAdapter(Document document) {
this.document = document;
}
#Override
public XPathFragment unmarshal(String v) throws Exception {
XPathFragment xPathFragment = new XPathFragment();
xPathFragment.set_expr(v);
xPathFragment.set_parentNode(document.getDocumentElement());
return xPathFragment;
}
#Override
public String marshal(XPathFragment v) throws Exception {
return v.get_expr();
}
}
Demo
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class Demo {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
File file = new File("input.xml");
Document document = db.parse(file);
JAXBContext jc = JAXBContext.newInstance(SomeRandomClass.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(new XPathFragmentAdapter(document));
SomeRandomClass src = (SomeRandomClass) unmarshaller.unmarshal(document);
System.out.println(src.get_expression().get_parentNode() != null);
}
}
SomeRandomClass
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="someRandomElement")
class SomeRandomClass {
private XPathFragment _expression;
#XmlAttribute(name="xpath")
#XmlJavaTypeAdapter(XPathFragmentAdapter.class)
public XPathFragment get_expression() {
return _expression;
}
public void set_expression(XPathFragment _expression) {
this._expression = _expression;
}
}
XPathFragment
import javax.xml.bind.annotation.XmlTransient;
import org.w3c.dom.Node;
class XPathFragment {
String _expr;
// we need this to look up namespace prefixes used in _expr
Node _parentNode;
public String get_expr() {
return _expr;
}
public void set_expr(String _expr) {
this._expr = _expr;
}
#XmlTransient
public Node get_parentNode() {
return _parentNode;
}
public void set_parentNode(Node _parentNode) {
this._parentNode = _parentNode;
}
}
For JAXB RI:
Write a custom XmlAdapter for your XPathFragment. You can access the current namespace context during unmarshalling via UnmarshallingContext.getInstance(). See how QName handling is implemented (check implementations of com.sun.xml.bind.v2.model.runtime.RuntimeBuiltinLeafInfo). Parsing of QName also needs namespace resolution, just like you XPathFragment case.
I would like to deserialize XML like the following using JAXB in Java:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
inner text that I need
<foo attrib="meh">
<bar>value</bar>
</foo>
</container>
</root>
The thing that is tripping me up is capturing the inner text of <container>: I can't use both an #XmlValue to get the inner text and #XmlElement to grab foo elements that come after the inner text. See below for an outline of what I am looking to do
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
public class App {
private static final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><container>text<foo attrib=\"meh\"><bar>waffles</bar></foo></container></root>";
#XmlRootElement(name = "foo") static class Foo {
#XmlAttribute public String attrib;
#XmlElement public String bar;
}
#XmlRootElement(name = "container") static class Container {
//#XmlValue public String innerText;
#XmlElement public Foo foo;
}
public static void main(String[] args) {
try {
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final XMLEventReader xer = xmlInputFactory.createXMLEventReader(new ByteArrayInputStream(xml.getBytes("UTF-8")));
XMLEvent evt = xer.nextEvent(); // start document
evt = xer.nextEvent(); // <root>
evt = xer.peek(); // advance to <container>
JAXBContext ctx = JAXBContext.newInstance(Container.class);
Unmarshaller um = ctx.createUnmarshaller();
Object o = um.unmarshal(xer);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (XMLStreamException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (JAXBException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
This is called "mixed-mode content", and it's generally a pain in the ass to process.
The key in JAXB is to use the #XmlMixed annotation - see javadoc.
Try something like this:
#XmlRootElement(name = "container")
static class Container {
#XmlMixed
#XmlElementRefs({
#XmlElementRef(name="foo", type=Foo.class)
})
List<?> content;
// ... plus the usual getters/setters
}
The content list should contain a sequence of Foo objects and Strings.