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

public class SimpleElement {

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

    /* getters/setters/... */

为了完整起见,Attribute
public class Attribute {

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

    /* getters/setters/... */

Namespace:
public class Namespace {

    private final String uri;
    private final String prefix;

    /* getters/setters/... */
SimpleElementAdapterSimpleElement序列化为其对应的org.w3c.dom.Element

这种方法的唯一问题是,名称空间总是以元素级别结束,而不是文档根目录。

有没有一种方法可以在文档根目录下动态声明名称空间?

最佳答案

我的建议

我的建议是让JAXB实现根据需要编写名称空间声明。只要元素正确地通过名称空间限定,名称空间声明出现的位置就没有关系。

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

原始答案

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

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

注意: NamespacePrefixMappercom.sun.xml.bind.marshaller包中。这意味着您将在类路径上需要JAXB参考实现罐(请参阅: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"};
    }


}

NamespacePrefixMapper上指定Marshallercom.sun.xml.bind.namespacePrefixMapper属性用于在NamespacePrefixMapper上指定Marshaller
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>

更新

明确的答案,谢谢。但是,我需要从以下位置访问NSMapper
SimpleElementAdapter。你有什么建议?我看到正确的唯一方法
现在正在将NSMapper变成易变的单例
如果需要,SimpleElementAdapter可以添加名称空间。

我忘记了你的XmlAdapter

Java模型

下面是该模型的一个更复杂的迭代,其中不是Foo持有DOM元素的实例,而是持有Bar的实例,该实例被适配为DOM元素的实例。

Foo
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的原因:
  • 它为我们提供了一个简单的事件,可用于收集名称空间声明。
  • 它实际上不产生任何东西,因此它是我们可以使用的最轻的元帅目标。

  • 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捕获的namrespaces。
    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>
    

    10-07 17:11