问题
I'm using Retrofit and SimpleXML in order to parse an XML response from some public API. I've been doing pretty well with all content, till I stumbled upon XML tags that contain both free-text AND sub-tags - as illustrated by the following example:
<a>
Some free-style text
<b>Even more text!</b>
</a>
In an attempt to deserialize using Simple-XML annotations, I've gone two ways. Keep in mind that basically the 'a's are a list of entry tags:
The first:
@ElementList(entry = "a", inline = true, required = false) List<A> aList;
Having 'A' defined like so:
public static class A {
@Text(required = false) protected String a;
}
This reads the free-text portion well, but any attempt to deserialize the content of the 'b' tag (e.g. by adding @Element
w or w/o @Path
annotation members to class 'A') has failed. I looked into the SimpleXML documentation and apparently there exists a limitation for using @Text
:
The rules that govern the use of the Text annotation are that there can only be one per schema class. Also, this annotation cannot be used with the Element annotation. Only the Attribute annotation can be used with it as this annotation does not add any content within the owning element.
The second method, which is more simplistic:
@ElementList(entry = "a", inline = true, required = false) List<String> aList;
Yet again, the content of 'a' tags get properly deserialize, but there's no way to reach in for the content of the 'b' sub-tags.
How can both the content of 'a' tags be deserialized with their associated 'b' sub-tags using pure Simple-XML annotations over JAVA objects?
回答1:
Though this question doesn't seem to have attract much attention, I'm sharing a solution that I've found for this problem anyways - perhaps others could benefit.
Apparently the makers of the Simple XML framework were aware of the fact that some XML's don't fit into their predefined, standard cases (much like in my case). Therefore, they've added support in serialization/deserialization overriding. One can create a custom converter class and use the @Convert
annotation to apply it over specific XML constructs. Within the custom converter, the XML deserialization is 'reduced' to an API very similar to the standard Java org.w3c.dom
framework.
In order to solve the problem introduced in my question, the following code can be used:
// First, declare the 'a' tags in the root class hosting them (this is pretty standard):
@ElementList(entry = "a", inline = true) List<A> aList;
// Second, create and apply a custom converter as described:
@Root
@Convert(CustomConverter.class)
public class A {
String content = "";
public String getContent() {
return content;
}
}
public class CustomConverter implements Converter<A> {
@Override
public A read(InputNode node) throws Exception {
A a = new A();
String value = node.getValue();
if (value != null) {
a.content = value;
}
InputNode nodeB = node.getNext();
if (nodeB != null) {
value = nodeB.getValue();
if (value != null) {
a.content += value;
}
}
return a;
}
@Override
public void write(OutputNode node, A value) throws Exception {
// N/A
}
}
The CustomConverter
in essence concatenates the content of the text directly under the 'a' and the text under 'b' onto A's content
data member.
Taking another step forward
In the interest of full disclosure, I'd also like to share the real solution I ended up going for, which was for a generalization of the problem I asked about in this post.
The content under the anonymous 'a' tag that I had to deserialize was in fact HTML-tagged text. For example:
<a>
If you can't explain it
<i>simply</i>
, you don't
<i>
understand it
<b>well enough.</b>
</i>
-- Albert Einstein
</a>
HTML tags are irrelevant for the parsing of the XML as a whole: All I really needed was for the content under 'a' to be deserialized as plain-text under a class named 'A'. So here's my (recursive) converter:
@Override
public A read(InputNode node) throws Exception {
final StringBuilder sb = new StringBuilder(1024);
concatNodesTree(sb, node);
A a = new A();
a.content = sb.toString();
return a;
}
private void concatNodesTree(StringBuilder sb, InputNode root) throws Exception {
if (root.isElement()) {
sb.append("<").append(root.getName()).append(">");
}
String value = root.getValue();
if (value != null) {
sb.append(value);
}
InputNode node = root.getNext();
while (node != null) {
concatNodesTree(sb, node);
// Each time a sub-tree is 'over', getValue() will deserialize the potentially upcoming free-text
value = root.getValue();
if (value != null) {
sb.append(value);
}
node = root.getNext();
}
if (root.isElement()) {
sb.append("</").append(root.getName()).append(">");
}
}
Note: In this solution, the 'a' tag would also be parsed into the final string. To avoid doing that, one can issue a special case concatNodesTree() method for the root node.
来源:https://stackoverflow.com/questions/34256195/deserializing-an-xml-tag-with-text-and-subtags-using-retrofit