在JAXB中控制名称空间前缀

时间:2011-02-02 10:42:40

标签: java xml jaxb xml-namespaces

jaxb如何确定编组对象的命名空间前缀声明列表?我使用xjc为ebics编译java类(ebics schema)。当我为ebicsRequest创建一个实例时,它看起来像这样:


<?xml version="1.0" encoding="UTF-16"?>
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ns4="http://www.ebics.org/S001" xmlns:ns5="http://www.ebics.org/H000">
    <ns2:header authenticate="true">
        <ns2:static>
            <ns2:HostID>SIZBN001</ns2:HostID>
            <ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce>
            <ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp>
            <ns2:PartnerID>EBICS</ns2:PartnerID>
            <ns2:UserID>EBIX</ns2:UserID>
            <ns2:Product Language="de">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product>
            <ns2:OrderDetails>
                <ns2:OrderType>FTB</ns2:OrderType>
                <ns2:OrderID>A037</ns2:OrderID>
                <ns2:OrderAttribute>OZHNN</ns2:OrderAttribute>
                <ns2:StandardOrderParams/>
            </ns2:OrderDetails>
            <ns2:BankPubKeyDigests>
                <ns2:Authentication Algorithm="RSA" Version="X002">...</ns2:Authentication>
                <ns2:Encryption Algorithm="RSA" Version="E002">...</ns2:Encryption>
            </ns2:BankPubKeyDigests>
            <ns2:SecurityMedium>0000</ns2:SecurityMedium>
            <ns2:NumSegments>1</ns2:NumSegments>
        </ns2:static>
        <ns2:mutable>
            <ns2:TransactionPhase>Initialisation</ns2:TransactionPhase>
        </ns2:mutable>
    </ns2:header>
    <ns2:AuthSignature>
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#xpointer(//*[@authenticate='true'])">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>...</ds:SignatureValue>
    </ns2:AuthSignature>
    <ns2:body>
        <ns2:DataTransfer>
            <ns2:DataEncryptionInfo authenticate="true">
                <ns2:EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest>
                <ns2:TransactionKey>...</ns2:TransactionKey>
            </ns2:DataEncryptionInfo>
            <ns2:SignatureData authenticate="true">...</ns2:SignatureData>
        </ns2:DataTransfer>
    </ns2:body>
</ns2:ebicsRequest>

我使用自定义NamespacePrefixMapper来声明ds和xsi的默认命名空间和前缀。对于命名空间ds,它可以正常工作。但对于默认命名空间,它没有。它被声明两次一次作为ns2,一次声明为“”后者来自我的自定义NamespacePrefixMapper.getPreDeclaredNamespaceUris。我和这堂课玩过很多次。此外,我尝试使用package-info.java,但我无法使jaxb使用"http://www.ebics.org/H003"作为默认命名空间。我还不明白的是ns4和ns5的外观,它们不是xml文档的全部内容。

我的NamespacePrefixMapper类看起来像


public class NamespacePrefixMapperImpl extends NamespacePrefixMapper implements NamespaceContext {
    private static final String[] EMPTY_STRING = new String[0];

    private Map prefixToUri = null;
    private Map uriToPrefix = null;

    private void init(){
    prefixToUri = new HashMap();

    prefixToUri.put("", "http://www.ebics.org/H003" );
    prefixToUri.put("ds", "http://www.w3.org/2000/09/xmldsig#" );
    prefixToUri.put("xsi", "http://www.w3.org/2001/XMLSchema-instance" );
    prefixToUri.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI  );
    prefixToUri.put(XMLConstants.XMLNS_ATTRIBUTE , XMLConstants.XMLNS_ATTRIBUTE_NS_URI );

    uriToPrefix = new HashMap();
    for(String prefix : prefixToUri.keySet()){
        uriToPrefix.put(prefixToUri.get(prefix), prefix);
    }
    }

    @Override
    public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
        if (uriToPrefix == null)
        init();

        if (uriToPrefix.containsKey(namespaceUri)){
            return uriToPrefix.get(namespaceUri);
        }

        return suggestion;
    }

    @Override
    public String[] getContextualNamespaceDecls() {
    // TODO Auto-generated method stub
    return EMPTY_STRING;
    }

    @Override
    public String[] getPreDeclaredNamespaceUris() {
    // TODO Auto-generated method stub
    return EMPTY_STRING;

    }

    @Override
    public String[] getPreDeclaredNamespaceUris2() {
    return new String [] {"", prefixToUri.get("")};

    }

    public String getNamespaceURI(String prefix) {
    if (prefixToUri == null)
            init();

    if (prefixToUri.containsKey(prefix)) {
        return prefixToUri.get(prefix);
    } else {
        return XMLConstants.NULL_NS_URI;
    }
    }

    public String getPrefix(String namespaceURI) {
    if (uriToPrefix == null)
            init();

        if (uriToPrefix.containsKey(namespaceURI)){
        return uriToPrefix.get(namespaceURI);
    } else {
        return null;
    }
    }

    public Iterator getPrefixes(String namespaceURI) {
    if (uriToPrefix == null)
            init();

    List prefixes = new LinkedList();

    if (uriToPrefix.containsKey(namespaceURI)){
        prefixes.add(uriToPrefix.get(namespaceURI));
    }
    return prefixes.iterator();
    }


}

我正在使用


Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 1.5.0-b64 (Sun Microsystems Inc.)
Specification-Title: Java Architecture for XML Binding
Specification-Version: 2.0
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: JAXB Reference Implementation 
Implementation-Version: 2.0.2
Implementation-Vendor: Sun Microsystems, Inc.
Implementation-Vendor-Id: com.sun
Extension-Name: com.sun.xml.bind
Build-Id: b01
Class-Path: jaxb-api.jar activation.jar jsr173_1.0_api.jar jaxb1-impl.
 jar

Name: com.sun.xml.bind.v2.runtime
Implementation-Version: 2.0.2-b01-fcs

5 个答案:

答案 0 :(得分:8)

出于性能原因,JAXB始终将JAXBContext已知的所有命名空间添加到XML文档的根元素。有关更多信息,请参阅Kohsuke在JAXB-103上发表的评论。

我发现处理此问题的唯一方法是在使用JAXB创建文档后自行遍历文档,并使用以下帮助程序类删除所有未使用的名称空间:

public class RemoveUnusedNamespaces {

    private static final String XML_NAMESPACE_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";

    private static final String XML_NAMESPACE_NAMESPACE = "http://www.w3.org/2000/xmlns/";

    private interface ElementVisitor {

        void visit(Element element);

    }

    public void process(Document document) {
        final Set<String> namespaces = new HashSet<String>();

        Element element = document.getDocumentElement();
        traverse(element, new ElementVisitor() {

            public void visit(Element element) {
                String namespace = element.getNamespaceURI();
                if (namespace == null)
                    namespace = "";
                namespaces.add(namespace);
                NamedNodeMap attributes = element.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node node = attributes.item(i);
                    if (XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
                        continue;
                    String prefix;
                    if (XML_NAMESPACE_SCHEMA_INSTANCE.equals(node.getNamespaceURI())) {
                        if ("type".equals(node.getLocalName())) {
                            String value = node.getNodeValue();
                            if (value.contains(":"))
                                prefix = value.substring(0, value.indexOf(":"));
                            else
                                prefix = null;
                        } else {
                            continue;
                        }
                    } else {
                        prefix = node.getPrefix();
                    }
                    namespace = element.lookupNamespaceURI(prefix);
                    if (namespace == null)
                        namespace = "";
                    namespaces.add(namespace);
                }
            }

        });
        traverse(element, new ElementVisitor() {

            public void visit(Element element) {
                Set<String> removeLocalNames = new HashSet<String>();
                NamedNodeMap attributes = element.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node node = attributes.item(i);
                    if (!XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
                        continue;
                    if (namespaces.contains(node.getNodeValue()))
                        continue;
                    removeLocalNames.add(node.getLocalName());
                }
                for (String localName : removeLocalNames)
                    element.removeAttributeNS(XML_NAMESPACE_NAMESPACE, localName);
            }

        });
    }

    private final void traverse(Element element, ElementVisitor visitor) {
        visitor.visit(element);
        NodeList children = element.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            traverse((Element) node, visitor);
        }
    }

}

答案 1 :(得分:1)

EclipseLink JAXB (MOXy)使用@XmlSchema注释中指定的前缀(我是MOXy引导)。请查看我对类似问题的回答,例如:

答案 2 :(得分:1)

我使用 xjc 生成了我的JAXB类,但是我使用的SOAP WebService强制我遵循一些规则,比如不使用命名空间前缀。

这是无效的:

<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
     <idLote>123</idLote>
     <evento>
         <ns2:Signature/>
     </evento>
</envEvento>

这是有效的:

<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
    <idLote>123</idLote>
    <evento>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"/>
    </evento> 
</envEvento>

正如所指出的,JAXB将名称空间声明放在根元素上。

为了克服这个问题,我使用的第一种方法是避免在上下文中使用不必要的元素。

例如,设置编组器的上下文如下:

JAXBContext.newInstance("path.to.package");

可以引导JAXB对命名空间进行一些不必要的声明。

有时,我可以通过烦人的 xmlns =“http://www.w3.org/2000/09/xmldsig#”来设置上下文所需的根元素:< / p>

JAXBContext.newInstance(MyRootElement.class);

我在第一种方法不足时使用的第二种方法是使整个上下文使用相同的命名空间。只需将每个名称空间声明(如@XmlElement或@XSchema)中不需要的“http://www.w3.org/2000/09/xmldsig#”更改为允许的唯一名称空间(http://www.portalfiscal.inf.br/nfe

然后,我只是在所需的孩子身上创建一个属性:

@XmlAttribute(name="xmlns")
String xmlns = "http://www.w3.org/2000/09/xmldsig#";

现在我在正确的元素中使用了名称空间声明,而没有使用任何前缀。

答案 3 :(得分:0)

我发现让JAXB删除ns2前缀的方法是在xs:schema元素中包含以下属性:elementFormDefault =“qualified”。所以它看起来像这样:

<xs:schema targetNamespace="urn:blah:blah" xmlns="urn:blah:blah" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

答案 4 :(得分:0)

在抓取许多帖子后,使用 NamespacePrefixMapper 的解决方案依赖于JDK版本(将来可能会破坏代码)或XML DOM树操作看起来很复杂。

我的蛮力方法是操纵生成的XML本身。

/**
 * Utility method to hide unused xmlns definition in XML root.
 * @param sXML Original XML string.
 * @return
 */
public static String hideUnUsedNamespace(String sXML) {
    int iLoc0 = sXML.indexOf("?><");
    int iLoc1 = sXML.indexOf("><",iLoc0+3)+1;
    String sBegin = sXML.substring(0,iLoc0+2);
    String sHeader = sXML.substring(iLoc0+2, iLoc1-1);
    String sRest = sXML.substring(iLoc1);
    //System.out.println("sBegin=" + sBegin);
    //System.out.println("sHeader=" + sHeader);
    //System.out.println("sRest=" + sRest);

    String[] saNS = sHeader.split(" ");
    //System.out.println("saNS=" + java.util.Arrays.toString(saNS));

    StringBuffer sbHeader = new StringBuffer();
    for (String s: saNS) {
        //System.out.println(s);
        if (s.startsWith("xmlns:")) {
            String token = "<" + s.substring(6,s.indexOf("="));
            //System.out.println("token=" + token + ",indexOf(token)=" + sRest.indexOf(token));
            if (sRest.indexOf(token) >= 0) {
                sbHeader = sbHeader.append(s).append(" ");
                //System.out.println("...included");
            }
        } else {
            sbHeader = sbHeader.append(s).append(" ");
        }
    }
    return (sBegin + sbHeader.toString().trim() + ">" + sRest);
}

/**
 * Main method for testing
 */
public static void main(String[] args) {
    String sXML ="<?xml version=\"1.0\" encoding=\"UTF-16\"?><ns2:ebicsRequest xmlns:ns2=\"http://www.ebics.org/H003\" Revision=\"1\" Version=\"H003\" xmlns=\"http://www.ebics.org/H003\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:ns4=\"http://www.ebics.org/S001\" xmlns:ns5=\"http://www.ebics.org/H000\"><ns2:header authenticate=\"true\"><ns2:static><ns2:HostID>SIZBN001</ns2:HostID><ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce><ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp><ns2:PartnerID>EBICS</ns2:PartnerID><ns2:UserID>EBIX</ns2:UserID><ns2:Product Language=\"de\">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product><ns2:OrderDetails><ns2:OrderType>FTB</ns2:OrderType><ns2:OrderID>A037</ns2:OrderID><ns2:OrderAttribute>OZHNN</ns2:OrderAttribute><ns2:StandardOrderParams/></ns2:OrderDetails><ns2:BankPubKeyDigests><ns2:Authentication Algorithm=\"RSA\" Version=\"X002\">...</ns2:Authentication><ns2:Encryption Algorithm=\"RSA\" Version=\"E002\">...</ns2:Encryption></ns2:BankPubKeyDigests><ns2:SecurityMedium>0000</ns2:SecurityMedium><ns2:NumSegments>1</ns2:NumSegments></ns2:static><ns2:mutable><ns2:TransactionPhase>Initialisation</ns2:TransactionPhase></ns2:mutable></ns2:header><ns2:AuthSignature><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#xpointer(//*[@authenticate='true'])\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>...</ds:SignatureValue></ns2:AuthSignature><ns2:body><ns2:DataTransfer><ns2:DataEncryptionInfo authenticate=\"true\"><ns2:EncryptionPubKeyDigest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\" Version=\"E002\">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest><ns2:TransactionKey>...</ns2:TransactionKey></ns2:DataEncryptionInfo><ns2:SignatureData authenticate=\"true\">...</ns2:SignatureData></ns2:DataTransfer></ns2:body></ns2:ebicsRequest>";

    System.out.println("Before=" + sXML);
    System.out.println("After =" + hideUnUsedNamespace(sXML));
}

输出显示未使用的xmlns名称空间被过滤掉:

<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">