使用Jaxb将部分XML解组为org.jdom.Element的实例

时间:2015-11-12 21:36:06

标签: java xml jaxb moxy

我在遗留产品中工作,该产品严重依赖于org.jdom项目的版本1(http://mvnrepository.com/artifact/org.jdom/jdom/1.1.3)和手动构建的XML,但我想尽可能多地使用Jaxb。我们正在使用Moxy作为Jaxb实现。

所以说我有以下xml:

<foo bar="bar">
    <baz>
        <test value="something" />
    </baz>
</foo>

由于使用baz元素作为org.jdom.Element的遗留代码,我希望让Jaxb解组内部元素&#34; baz&#34;到一个org.jdom.Element但unmarshal中的v总是一个空字符串(&#34;&#34;)所以baz变为null。 我在下面创建了一个示例,我尝试使用XmlAdapter,但我无法使用它。

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);
    }

  }

}

也许我从错误的角度攻击这个。有关如何实现这一目标的任何建议吗?

2 个答案:

答案 0 :(得分:0)

解组时调试问题的好方法是使用javax.xml.bind.helpers.DefaultValidationEventHandler

在您的情况下,您只需将其添加到主方法中的unmarshaller,例如

// ...
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
// ...

当您使用附加的验证处理程序按原样运行测试程序时,您将看到类似下面的内容,这基本上告诉您没有与您的test元素对应的Java类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)]

我认为您假设您的ElementAdapter实施将会阅读&amp;使用baz节点和所有子节点作为字符串。实际上,我认为(分页Blaise Doughan?)正在发生的事情是JAXB正在对XML树进行一些预处理,在此期间它看到你没有模型对于test,然后丢弃test及其属性。

要了解此处发生的事情,请先简化模型,不要担心JDOM Element转换。请注意,我已更改了您的顶级课程的名称,因此它与Test模型类没有冲突:

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 + "\"/>";
        }
    }

}

这应该正确解组并打印<foo bar="bar"><baz><test value="something"/></baz></foo>。另请注意,通常您会传递包含整个对象图的JAXBContextFactoryObjectFactory,该对象图将从您的XML架构生成。这可能是混乱的根源。

现在您可以添加XmlAdapter,但不是尝试从String转换为Element,而是从Baz转换为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;
        }

    }
}

这应该或多或少地做你想要的,尽管在转换为JDOM之前你可能必须清理适配器中的一些Baz元素(去掉XML前导码等)。请注意,在JAXBContext中实例化另一个ElementAdapter可能是一个糟糕的想法,并且可能有更有效的方法将JAXB元素转换为JDOM元素。这段代码很容易在调试器中单步执行,因此您可以看到正在进行的操作。

答案 1 :(得分:0)

以为我会分享一个对我有用的解决方案。

受到这篇帖子Jaxb: how to unmarshall xs:any XML-string part?的启发,提到Jaxb在标记为@XmlAnyElement(lax = true)时不知道如何映射为DOM元素,我最后使用org.w3c创建了一个XmlAdapter。 dom.Element作为Jaxb知道如何映射的中间类。

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

  }
}

运行它的打印出来是:

Foo [bar=bar, baz=<baz>
    <test value="something" />
</baz>]

正如您所看到的那样,baz标签中的内容被“按原样”分配给Foo类中的变量。