【问题标题】:Using JAXB to extract content of several XML elements as text使用 JAXB 将多个 XML 元素的内容提取为文本
【发布时间】:2017-05-04 07:13:49
【问题描述】:

我有以下 XML 文件

<items>
   <title><a href="blabla">blabla</a></title>
   <text><a href="123">123</a></text>
</items>

我通过 JAXB 和 XmlAnyElement 注释将 XML 解组到下一个 java 对象,其中两个类实现 DOMHandler。我想将元素“标题”和“文本”的内部 XML 提取为字符串。

public class Item implements Serializable {
    private String title;
    private String text;

    public String getTitle() {
        return title;
    }
    @XmlAnyElement(value = TitleHandler.class)
    public void setTitle(String title) {
        this.title = title;
    }
    public String getText() {
        return text;
    }
    @XmlAnyElement(value = TextHandler.class)
    public void setText(String text) {
        this.text = text;
    }
}

但是当我在 TitleHandler 和 TextHandler 的方法“String getElement(StreamResult rt)”中设置断点时,这两个元素都使用 TextHandler.class 进行解组。元素“title”使用 TextHandler 而不是 TitleHandler。 任何帮助将不胜感激

更新 XmlAnyElement 注释的限制使用约束: 一个类及其超类中只能有一个 XmlAnyElement 注解的 JavaBean 属性。

【问题讨论】:

  • 您希望字符串title 包含&lt;a href="blabla"&gt;blabla&lt;/a&gt; 还是blabla?换句话说,您需要将元素的内容作为 XML 字符串还是只需要嵌套元素的文本?
  • 我希望字符串“title”包含“blabla”,字符串“text”包含“123 "

标签: java xml jaxb


【解决方案1】:

@XmlAnyElement 注释用作 XML 输入中未按名称映射到某些特定属性的元素的全部内容。这就是为什么每个类只能有一个这样的注释(包括继承的属性)。你想要的是这样的:

public class Item implements Serializable {
    private String title;
    private String text;

    public String getTitle() {
        return title;
    }
    @XmlElement(name = "title")
    @XmlJavaTypeAdapter(value = TitleHandler.class)
    public void setTitle(String title) {
        this.title = title;
    }
    public String getText() {
        return text;
    }
    @XmlElement(name = "text")
    @XmlJavaTypeAdapter(value = TextHandler.class)
    public void setText(String text) {
        this.text = text;
    }
}

@XmlElement 注释表示对应的属性映射到具有该名称的元素。因此,Java text 属性派生自 XML &lt;text&gt; 元素,title 属性派生自 &lt;title&gt; 元素。由于属性和元素的名称相同,这也是没有@XmlElement注解的默认行为,因此您可以省略它们。

为了处理从 XML 内容到字符串而不是实际结构的转换(例如 Title 类或 Text 类),您需要一个适配器。这就是 @XmlJavaTypeAdapter 注释的用途。它指定必须如何处理该属性的编组/解组。

查看这个有用的答案:https://stackoverflow.com/a/18341694/630136

如何实现TitleHandler 的示例。

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class TitleHandler extends XmlAdapter<Object, String> {

    /**
     * Factory for building DOM documents.
     */
    private final DocumentBuilderFactory docBuilderFactory;
    /**
     * Factory for building transformers.
     */
    private final TransformerFactory transformerFactory;

    public TitleHandler() {
        docBuilderFactory = DocumentBuilderFactory.newInstance();
        transformerFactory = TransformerFactory.newInstance();
    }

    @Override
    public String unmarshal(Object v) throws Exception {
        // The provided Object is a DOM Element
        Element titleElement = (Element) v;
        // Getting the "a" child elements
        NodeList anchorElements = titleElement.getElementsByTagName("a");
        // If there's none or multiple, return empty string
        if (anchorElements.getLength() != 1) {
            return "";
        }
        Element anchor = (Element) anchorElements.item(0);
        // Creating a DOMSource as input for the transformer
        DOMSource source = new DOMSource(anchor);
        // Default transformer: identity tranformer (doesn't alter input)
        Transformer transformer = transformerFactory.newTransformer();
        // This is necessary to avoid the <?xml ...?> prolog
        transformer.setOutputProperty("omit-xml-declaration", "yes");
        // Transform to a StringWriter
        StringWriter stringWriter = new StringWriter();
        StreamResult result = new StreamResult(stringWriter);
        transformer.transform(source, result);
        // Returning result as string
        return stringWriter.toString();
    }

    @Override
    public Object marshal(String v) throws Exception {
        // DOM document builder
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        // Creating a new empty document
        Document doc = docBuilder.newDocument();
        // Creating the <title> element
        Element titleElement = doc.createElement("title");
        // Setting as the document root
        doc.appendChild(titleElement);
        // Creating a DOMResult as output for the transformer
        DOMResult result = new DOMResult(titleElement);
        // Default transformer: identity tranformer (doesn't alter input)
        Transformer transformer = transformerFactory.newTransformer();
        // String reader from the input and source
        StringReader stringReader = new StringReader(v);
        StreamSource source = new StreamSource(stringReader);
        // Transforming input string to the DOM
        transformer.transform(source, result);
        // Return DOM root element (<title>) for JAXB marshalling to XML
        return doc.getDocumentElement();
    }

}

如果解组输入/编组输出的类型保留为Object,则 JAXB 将提供 DOM 节点。上面使用 XSLT 转换(虽然没有实际的样式表,只是一个“身份”转换)将 DOM 输入转换为字符串,反之亦然。我已经在一个最小的输入文档上对其进行了测试,它既适用于 XML 到 Item 对象,也适用于其他方式。

编辑:

以下版本将处理 &lt;title&gt; 中的任何 XML 内容,而不是期望单个 &lt;a&gt; 元素。您可能希望将其转换为抽象类,然后让 TitleHanderTextHandler 对其进行扩展,以便实现提供当前硬编码的 &lt;title&gt; 标签。

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class TitleHandler extends XmlAdapter<Object, String> {

    /**
     * Factory for building DOM documents.
     */
    private final DocumentBuilderFactory docBuilderFactory;
    /**
     * Factory for building transformers.
     */
    private final TransformerFactory transformerFactory;

    /**
     * XSLT that will strip the root element. Used to only take the content of an element given
     */
    private final static String UNMARSHAL_XSLT = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
"<xsl:transform xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\">\n" +
"\n" +
"    <xsl:output method=\"xml\" omit-xml-declaration=\"yes\" />\n" +
"\n" +
"    <xsl:template match=\"/*\">\n" +
"      <xsl:apply-templates select=\"@*|node()\"/>\n" +
"    </xsl:template>\n" +
"\n" +
"    <xsl:template match=\"@*|node()\">\n" +
"        <xsl:copy>\n" +
"            <xsl:apply-templates select=\"@*|node()\"/>\n" +
"        </xsl:copy>\n" +
"    </xsl:template>\n" +
"    \n" +
"</xsl:transform>";

    public TitleHandler() {
        docBuilderFactory = DocumentBuilderFactory.newInstance();
        transformerFactory = TransformerFactory.newInstance();
    }

    @Override
    public String unmarshal(Object v) throws Exception {
        // The provided Object is a DOM Element
        Element rootElement = (Element) v;
        // Creating a DOMSource as input for the transformer
        DOMSource source = new DOMSource(rootElement);
        // Creating a transformer that will strip away the root element
        StreamSource xsltSource = new StreamSource(new StringReader(UNMARSHAL_XSLT));
        Transformer transformer = transformerFactory.newTransformer(xsltSource);
        // Transform to a StringWriter
        StringWriter stringWriter = new StringWriter();
        StreamResult result = new StreamResult(stringWriter);
        transformer.transform(source, result);
        // Returning result as string
        return stringWriter.toString();
    }

    @Override
    public Object marshal(String v) throws Exception {
        // DOM document builder
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        // Creating a new empty document
        Document doc = docBuilder.newDocument();
        // Creating a DOMResult as output for the transformer
        DOMResult result = new DOMResult(doc);
        // Default transformer: identity tranformer (doesn't alter input)
        Transformer transformer = transformerFactory.newTransformer();
        // String reader from the input and source
        StringReader stringReader = new StringReader("<title>" + v + "</title>");
        StreamSource source = new StreamSource(stringReader);
        // Transforming input string to the DOM
        transformer.transform(source, result);
        // Return DOM root element for JAXB marshalling to XML
        return doc.getDocumentElement();
    }

}

【讨论】:

  • 感谢您的回答。如果字段“title”不仅包含 ,而且像这样的想法:“blabla

    12345

    Hello World
  • 好的,我找到了解决方案
  • @DmitryIgumnov 我进行了编辑以显示处理一般情况的处理程序版本。
【解决方案2】:

正如您已经发现的那样,@XmlAnyElement 不符合您的需要。 我不会使用DomHandler,而是选择直截了当的方式,让 JAXB 为您完成所有工作。

让类Item有两个元素titletext

public class Item {
    private Title title;
    private Text text;

    @XmlElement(name = "title")
    public Title getTitle() {
        return title;
    }
    public void setTitle(Title title) {
        this.title = title;
    }

    @XmlElement(name = "text")
    public Text getText() {
        return text;
    }
    public void setText(Text text) {
        this.text = text;
    }
}

创建类 TitleText 仅包含元素 a

public class Title {
    private A a;

    @XmlElement(name = "a")
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
}

public class Text {
    private A a;

    @XmlElement(name = "a")
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
}

创建具有href 属性和内部文本值的类A@XmlValue):

public class A {
    private String href;
    private String value;

    @XmlAttribute(name = "href")
    public String getHref() {
        return href;
    }
    public void setHref(String href) {
        this.href = href;
    }

    @XmlValue
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
       this.value = value;
    }
}

【讨论】:

  • 根据对问题的评论,想法是将实际的 &lt;a&gt; 元素作为 XML 字符串,而不是作为数据结构。
  • @G_H 好吧,我好像错过了这个要求
  • 我对问题进行了编辑,以便更清楚地说明元素的 XML 内容必须以字符串形式获取。
猜你喜欢
  • 2011-07-29
  • 2015-03-30
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
  • 2011-10-13
  • 2011-07-27
  • 2020-06-29
相关资源
最近更新 更多