java - JAXB - 将动态生成的 namespace 移动到文档根目录

标签 java xml jaxb xml-namespaces

我有这个 POJO,它封装了 Atom 条目的一个动态的、非嵌套的元素:

public class SimpleElement {

    private Namespace namespace;
    private String tagName;
    private String value;
    private Collection<Attribute> attributes;

    /* getters/setters/... */

为了完整性,属性

public class Attribute {

    private String name;
    private String value;
    private Namespace namespace;  

    /* getters/setters/... */

命名空间:

public class Namespace {

    private final String uri;
    private final String prefix;

    /* getters/setters/... */

SimpleElementAdapterSimpleElement 序列化为其对应的 org.w3c.dom.Element

这种方法的唯一问题是 namespace 总是在元素级别结束,而不是在文档根目录。

有没有办法在文档根目录下动态声明命名空间?

最佳答案

我的建议

我的建议是让 JAXB 实现编写它认为合适的命名空间声明。只要元素是适当的命名空间限定的,命名空间声明出现在哪里并不重要。

如果您忽略我的建议,可以使用以下方法。


原始答案

指定要包含在根元素上的命名空间

您可以使用 NamespacePrefixMapper 扩展来向根元素添加额外的命名空间声明(参见:https://jaxb.java.net/nonav/2.2.11/docs/ch05.html#prefixmapper)。您将需要从您自己的对象模型派生出哪些 namespace 应该在根目录中声明。

注意:NamespacePrefixMapper 位于com.sun.xml.bind.marshaller 包中。这意味着您将需要在您的类路径中使用 JAXB 引用实现 jar(请参阅:https://jaxb.java.net/)。

import com.sun.xml.bind.marshaller.*;

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {

    @Override
    public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
        return null;
    }

    @Override
    public String[] getPreDeclaredNamespaceUris2() {
        return new String[] {"ns1", "http://www.example.com/FOO", "ns2", "http://www.example.com/BAR"};
    }


}

Marshaller 上指定 NamespacePrefixMapper

com.sun.xml.bind.namespacePrefixMapper 属性用于在 Marshaller 上指定 NamespacePrefixMapper

marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());

演示代码

Java 模型(Foo)

import javax.xml.bind.annotation.*;

@XmlRootElement
public class Foo {

    private Object object;

    @XmlAnyElement
    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

}

演示

import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.Element;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Foo.class);

        Foo foo = new Foo();

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.newDocument();
        Element element = document.createElementNS("http://www.example.com/FOO", "ns1:foo");
        foo.setObject(element);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());
        marshaller.marshal(foo, System.out);
    }

}

输出

下面是将生成的示例输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns1="http://www.example.com/FOO" xmlns:ns2="http://www.example.com/BAR">
    <ns1:foo/>
</foo>

更新

Clear answer, thanks. However, I need access to the NSMapper from SimpleElementAdapter. What do you suggest? The only way I see right now is making the NSMapper a mutable singleton so that SimpleElementAdapter can add namespaces if needed.

我忘记了你的XmlAdapter

Java 模型

下面是模型的一个更复杂的迭代,其中不是 Foo 保存 DOM 元素的实例,而是保存 Bar 的实例,该实例被改编成一个DOM 元素的实例。

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Foo {

    private Bar bar;

    @XmlAnyElement
    @XmlJavaTypeAdapter(BarAdapter.class)
    public Bar getBar() {
        return bar;
    }

    public void setBar(Bar bar) {
        this.bar = bar;
    }

}

public class Bar {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

BarAdapter

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class BarAdapter extends XmlAdapter<Object, Bar>{

    @Override
    public Object marshal(Bar bar) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.newDocument();
        Element element = document.createElementNS("http://www.example.com/BAR", "ns:bar");
        element.setTextContent(bar.getValue());
        return element;
    }

    @Override
    public Bar unmarshal(Object arg0) throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

}

抓取命名空间声明

由于您的对象模型不直接保存 DOM 元素,因此您无法遍历它来获取命名空间声明。相反,我们可以对 ContentHandler 进行编码以收集它们。以下是编码到 ContentHandler 的原因:

  1. 它为我们提供了一个简单的事件,我们可以使用它来收集命名空间声明。
  2. 它实际上不产生任何东西,因此它是我们可以使用的最轻的编码目标。
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);

NsContentHandler

ContentHandler 的实现看起来像这样:

import java.util.*;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class NsContentHandler extends DefaultHandler {

    private Map<String, String> namespaces = new TreeMap<String, String>();

    @Override
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        if(!namespaces.containsKey(prefix)) {
            namespaces.put(prefix, uri);
        }
    }

    public Map<String, String> getNamespaces() {
        return namespaces;
    }

}

指定要包含在根元素上的命名空间

MyNamespacePrefixMapper 的实现稍作更改,以使用从我们的 ContentHandler 捕获的命名空间。

import java.util.Map;
import java.util.Map.Entry;
import com.sun.xml.bind.marshaller.*;

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {

    private String[] namespaces;

    public MyNamespacePrefixMapper(Map<String, String> namespaces) {
        this.namespaces = new String[namespaces.size() * 2];
        int index = 0;
        for(Entry<String, String> entry : namespaces.entrySet()) {
            this.namespaces[index++] = entry.getKey();
            this.namespaces[index++] = entry.getValue();
        }
    }

    @Override
    public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
        return null;
    }

    @Override
    public String[] getPreDeclaredNamespaceUris2() {
        return namespaces;
    }

}

演示代码

import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Foo.class);

        Bar bar = new Bar();
        bar.setValue("Hello World");
        Foo foo = new Foo();
        foo.setBar(bar);

        Marshaller marshaller = jc.createMarshaller();

        // Marshal First Time to Get Namespace Declarations
        NsContentHandler contentHandler = new NsContentHandler();
        marshaller.marshal(foo, contentHandler);

        // Marshal Second Time for Real
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper(contentHandler.getNamespaces()));
        marshaller.marshal(foo, System.out);
    }

}

输出

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns="http://www.example.com/BAR">
    <ns:bar>Hello World</ns:bar>
</foo>

关于java - JAXB - 将动态生成的 namespace 移动到文档根目录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28530499/

相关文章:

java - 编码多个 radio 组

javascript - 是否有用于 XML 绑定(bind)的 JavaScript API——类似于 Java 的 JAXB?

java - 解析的 SOAP 响应与收到的响应不同

Java:删除第三个斜杠之前的所有内容

C# 不会加载特定 XML,但可以在浏览器中运行

java - 为什么scala中参数化类型的+运算符总是导致字符串

android - 如何在 activity(dot)xml 中包装 android 代码

java - 如何使用jaxb在xml文件中创建表?

java - java中的空格

java - java 计算 vector 出现次数