使用 Retrofit 反序列化带有文本和子标签的 XML 标签

Deserializing an XML tag with text AND subtags using Retrofit

我正在使用 Retrofit 和 SimpleXML 来解析来自某些 public API 的 XML 响应。我对所有内容都做得很好,直到我偶然发现包含自由文本和子标签的 XML 标签 - 如以下示例所示:

<a>
   Some free-style text
   <b>Even more text!</b>
</a>

为了尝试使用 Simple-XML 注释进行反序列化,我采用了两种方法。请记住,基本上 'a' 是 条目标签列表

第一个:

@ElementList(entry = "a", inline = true, required = false) List<A> aList;

'A' 定义如下:

public static class A {
    @Text(required = false) protected String a;
}

这可以很好地读取自由文本部分,但是任何反序列化 'b' 标签内容的尝试(例如通过添加 @Element w 或 w/o @Path class 'A') 的注释成员失败。我查看了 SimpleXML 文档,显然存在使用 @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.

第二种方法,比较简单:

@ElementList(entry = "a", inline = true, required = false) List<String> aList;

再一次,'a' 标签的内容得到了正确的反序列化,但是无法获取 'b' 子标签的内容。

如何使用 JAVA 对象上的纯 Simple-XML 注释将 'a' 标签的内容与其关联的 'b' 子标签反序列化?

虽然这个问题似乎没有引起太多关注,但我正在分享我为这个问题找到的解决方案 - 也许其他人会受益。

显然,Simple XML 框架的开发者意识到一些 XML 不适合他们预定义的标准案例(很像我的案例)。因此,他们在 serialization/deserialization overriding 中添加了支持。可以创建自定义转换器 class 并使用 @Convert 注释将其应用于特定的 XML 构造。在自定义转换器中,XML 反序列化是 'reduced' 到非常类似于标准 Java org.w3c.dom 框架的 API。

为了解决我问题中引入的问题,可以使用如下代码:

// 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
    }
}

CustomConverter 本质上是将 'a' 下的文本内容和 'b' 下的文本内容连接到 A 的 content 数据成员上。

又向前迈进了一步

为了全面披露,我还想分享我最终寻求的真正解决方案,这是对我在 post.[=18 中提出的问题的概括=]

我必须反序列化的匿名 'a' 标签下的内容实际上是 HTML 标签文本。例如:

<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 标签与整个 XML 的解析无关:我真正需要的是将 'a' 下的内容反序列化为纯文本class 名为 'A'。所以这是我的(递归)转换器:

@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(">");
    }
}

注意:在此解决方案中,'a' 标记也将被解析为最终字符串。为避免这样做,可以为根节点发出一种特殊情况的 concatNodesTree() 方法。