JAXB - 将动态生成的名称空间移动到文档根目录 [英] JAXB - Move dynamically generated namespaces to document root

查看:59
本文介绍了JAXB - 将动态生成的名称空间移动到文档根目录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  public class SimpleElement {

私有命名空间命名空间;
private String tagName;
私有字符串值;
private Collection< Attribute>属性;

/ * getters / setters / ... * /

完整性,属性

 公共类属性{

private String name;
私有字符串值;
私有命名空间命名空间;

/ * getters / setters / ... * /

命名空间

  public class Namespace {

private final String uri;
private final String prefix;

/ * getters / setters / ... * /

SimpleElementAdapter SimpleElement 序列化为 org.w3c.dom.Element 这个方法的唯一问题是命名空间总是在元素级别结束,而不是在文档根目录。



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

解决方案

我的推荐



我的建议是让JAXB实现按其认为合适的方式编写命名空间声明。只要元素正确地进行了命名空间限定,命名空间声明的位置就不重要了。



如果您忽略我的推荐,下面是您可以使用的方法。






ORIGINAL ANSWER



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



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



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

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

公共类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 Marshaller



com.sun.xml.bind.namespacePrefixMapper 属性用于指定 Marshaller NamespacePrefixMapper $ c>。

  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 static void main(String [] args)抛出异常{
JAXBContext jc = JAXBContext.newInstance(Foo.class);

Foo foo = new Foo();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
元素元素= 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.0encoding =UTF-8standalone =yes?> 
< foo xmlns:ns1 =http://www.example.com/FOOxmlns:ns2 =http://www.example.com/BAR>
< ns1:foo />
< / foo>






更新




明确回答,谢谢。但是,我需要从
SimpleElementAdapter访问NSMapper。你有什么建议?我现在唯一能看到
的方法就是让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。*;

公共类BarAdapter扩展XmlAdapter< Object,Bar> {

@Override
public Object marshal(Bar bar)抛出异常{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
元素元素= document.createElementNS(http://www.example.com/BAR,ns:bar);
element.setTextContent(bar.getValue());
返回元素;
}

@Override
public Bar unmarshal(Object arg0)throws Exception {
// TODO自动生成的方法stub
返回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;

公共类NsContentHandler扩展DefaultHandler {

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

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

公共地图< String,String> getNamespaces(){
返回名称空间;
}

}



指定要包含在根目录中的命名空间元素



MyNamespacePrefixMapper 的实现稍微改变一下,使用从我们的 ContentHandler中捕获的namrespace

  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 static void main(String [] args)抛出异常{
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第一次获取命名空间声明
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo,contentHandler);

//元帅的第二次元帅
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.0encoding =UTF-8standalone =yes?> 
< foo xmlns:ns =http://www.example.com/BAR>
< ns:bar> Hello World< / ns:bar>
< / foo>


I've got this POJO, encapsulating a dynamic, non-nested element of an Atom entry:

public class SimpleElement {

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

    /* getters/setters/... */

And for completeness, Attribute

public class Attribute {

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

    /* getters/setters/... */

And Namespace:

public class Namespace {

    private final String uri;
    private final String prefix;

    /* getters/setters/... */

SimpleElementAdapter serializes a SimpleElement into its org.w3c.dom.Element counterpart.

The only problem with this approach is that namespaces always end up at element level, never at document root.

Is there a way to dynamically declare namespaces at document root?

解决方案

MY RECOMMENDATION

My recommendation is to let the JAXB implementation write the namespace declarations as it sees fit. As long as the elements are properly namespace qualified it does not really matter where the namespace declarations occur.

If you ignore my recommendation, below is an approach you can use.


ORIGINAL ANSWER

Specify the Namespaces to Include on Root Element

You can use the NamespacePrefixMapper extension to add extra namespace declarations to the root element (see: https://jaxb.java.net/nonav/2.2.11/docs/ch05.html#prefixmapper). You will need to derive from your own object model what namespaces should be declared at the root.

Note: NamespacePrefixMapper is in the com.sun.xml.bind.marshaller package. This means you will need the JAXB refereince implementation jar on your classpath (see: 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"};
    }


}

Specify the NamespacePrefixMapper on the Marshaller

The com.sun.xml.bind.namespacePrefixMapper property is used to specify the NamespacePrefixMapper on the Marshaller.

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

Demo Code

Java Model (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;
    }

}

Demo

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

}

Output

Below is sample output that will be produced:

<?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>


UPDATE

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.

I forgot about your XmlAdapter.

Java Model

Below is a more complicated iteration of the model, where instead of Foo holding an instance of a DOM element, it holds and instance of Bar that gets adapted into an instance of a DOM element.

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

}

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

}

Grab Namespace Declarations

Since your object model does not hold the DOM elements directly you can't traverse it to get the namespace declarations. Instead we could do a marshal to a ContentHandler to collect them. Below are the reasons for marshalling to a ContentHandler:

  1. It gives us an easy event which we can use to collection the namespace declarations.
  2. It doesn't actually produce anything so it is the lightest marshal target we can use.

NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);

NsContentHandler

The implementation of ContentHandler will look something like:

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

}

Specify the Namespaces to Include on Root Element

The implementation of MyNamespacePrefixMapper changes a little to use the namrespaces captured from our 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;
    }

}

Demo Code

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

}

Output

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

这篇关于JAXB - 将动态生成的名称空间移动到文档根目录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆