Reputation: 18413
I'm using standard JAXB implementation.
This is my annotated class (is part of a set of classes)
@XmlType()
@XmlAccessorType(FIELD)
class MyClass {
@XmlValue
protected final String value = null;
@XmlAttribute
protected String attr;
...get/set for attr...
}
I'm writing root object using
JaxbContext ctx = JAXBContext.newInstance("path.to.package");
XMLStreamWriter writer = new IndentingXMLStreamWriter(file); //stax-utils writer
ctx.marshal(rootObject, writer);
the result is <my-class attr="attrValue"></my-class>
but I need empty tag as <my-class attr="attrValue"/>
.
I tried some different combination (and read dozen of SO questions) of writer and target stream (not only file,but also StringWriter and others but I need to persist object to file).
Any advice or solution?
Upvotes: 3
Views: 4465
Reputation: 18413
I resolved using a custom XMLStreamWriter that replace the <tag-name></tag-name>
with <tag-name/>
if tag-name has no content at all.
The idea behind is to defer write events between startElement-endElement.
When an endElement is found if the aren't content events between boundaries replace startElement with a emptyElement and suppress endElement at all.
Sorry for poor comments, I'll came back later to make it more understandable.
public class EmptyTagXMLStreamWriter extends StreamWriterDelegate {
class Event {
Method m;
Object[] args;
}
enum EventEnum {
writeStartElement,
writeAttribute,
writeNamespace,
writeEndElement,
setPrefix,
setDefaultNamespace,
}
private List<Event> queue = new ArrayList<Event>();
protected EmptyTagXMLStreamWriter(XMLStreamWriter out) {
super(out);
}
@Override
public void writeStartElement(String localName) throws XMLStreamException {
d(e(m("writeStartElement",String.class)), localName);
}
@Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException
{
d(e(m("writeStartElement",String.class,String.class)), namespaceURI, localName);
}
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException
{
d(e(m("writeStartElement",String.class,String.class,String.class)), prefix, localName, namespaceURI);
}
@Override
public void writeAttribute(String localName, String value)
throws XMLStreamException {
d(e(m("writeAttribute",String.class, String.class)), localName, value);
}
@Override
public void writeAttribute(String namespaceURI, String localName,
String value) throws XMLStreamException {
d(e(m("writeAttribute",String.class, String.class, String.class)), namespaceURI, localName, value);
}
@Override
public void writeAttribute(String prefix, String namespaceURI,
String localName, String value) throws XMLStreamException {
d(e(m("writeAttribute",String.class, String.class, String.class, String.class)), prefix, namespaceURI, localName, value);
}
@Override
public void writeCData(String data) throws XMLStreamException {
fq();
super.writeCData(data);
}
@Override
public void writeCharacters(char[] text, int start, int len)
throws XMLStreamException {
fq();
super.writeCharacters(text, start, len);
}
@Override
public void writeCharacters(String text) throws XMLStreamException {
fq();
super.writeCharacters(text);
}
@Override
public void writeComment(String data) throws XMLStreamException {
fq();
super.writeComment(data);
}
@Override
public void writeDTD(String dtd) throws XMLStreamException {
fq();
super.writeDTD(dtd);
}
@Override
public void writeProcessingInstruction(String target)
throws XMLStreamException {
fq();
super.writeProcessingInstruction(target);
}
@Override
public void writeProcessingInstruction(String target, String data)
throws XMLStreamException {
fq();
super.writeProcessingInstruction(target, data);
}
@Override
public void writeNamespace(String prefix, String namespaceURI)
throws XMLStreamException {
d(e(m("writeNamespace",String.class, String.class)), prefix, namespaceURI);
}
@Override
public void writeEndElement() throws XMLStreamException {
d(e(m("writeEndElement")));
}
@Override
public void writeEndDocument() throws XMLStreamException {
fq();
super.writeEndDocument();
}
@Override
public void writeDefaultNamespace(String namespaceURI)
throws XMLStreamException {
super.writeDefaultNamespace(namespaceURI);
}
@Override
public void flush() throws XMLStreamException {
if(queue.isEmpty())
super.flush();
}
@Override
public void close() throws XMLStreamException {
fq();
out.close();
}
@Override
public void setPrefix(String prefix, String uri) throws XMLStreamException {
d(e(m("setPrefix", String.class, String.class)), prefix, uri);
}
@Override
public void setDefaultNamespace(String uri) throws XMLStreamException {
d(e(m("setDefaultNamespace", String.class)), uri);
}
void d(Event e,Object...args) throws XMLStreamException {
e.args = args;
switch(EventEnum.valueOf(e.m.getName()))
{
case writeStartElement:
fq();
queue.add(e);
break;
case writeAttribute:
case writeNamespace:
case setPrefix:
case setDefaultNamespace:
if(!queue.isEmpty())
queue.add(e);
else
ex(e, args);
break;
case writeEndElement:
if(!queue.isEmpty())
{
final Event e1 = queue.get(0);
e1.m = m("writeEmptyElement", e1.m.getParameterTypes());
fq();
}
else
{
ex(e, args);
}
break;
}
}
Event e(Method m,Object...params)
{
final Event e = new Event();
e.m = m;
e.args = params;
return e;
}
Method m(String methodName,Class<?>...args) throws XMLStreamException {
try {
return XMLStreamWriter.class.getMethod(methodName, args);
} catch (Exception e) {
throw new XMLStreamException(e);
}
}
void fq() throws XMLStreamException
{
for(int i = 0;i < queue.size();i++)
{
Event e = queue.get(i);
ex(e, e.args);
}
queue.clear();
}
void ex(Event e,Object...args) throws XMLStreamException
{
try
{
e.m.invoke(super.out, e.args);
}
catch(Exception ex)
{
throw new XMLStreamException(ex);
}
}
}
Upvotes: 1
Reputation: 149047
This is an issue that can come up when marshalling to an XMLStreamWriter
. If you can marshal to a File
, FileOutputStream
, or FileWriter
to get the behaviour you are looking for.
Note:
To get the output formatted with these output targets you can set the following property on your Marshaller
:
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Upvotes: 2