在JAXB中控制名称空间前缀

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

    SIZBN001 A5488F43223063171CA0FA59ADC635F0 2009-08-04T08:41:56.967Z EBICS EBIX EBICS-Kernel V2.0.4, SIZ/PPI  FTB A037 OZHNN    ... ...  0000 1   Initialisation            CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=   ...     dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE= ...  ...    

我使用自定义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 

出于性能原因,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 namespaces = new HashSet(); 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 removeLocalNames = new HashSet(); 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); } } } 

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

  • 如何在Jersey(JAX-WS)上自定义名称空间前缀

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

这是无效的:

  123     

这是有效的:

  123     

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

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

例如,像这样设置编组的上下文:

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

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

有时候,我可以通过烦人的xmlns =“http://www.w3.org/2000/09/xmldsig#”来设置上下文,并使用必要的根元素:

 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#"; 

现在,我在正确的元素中从根目录中获取名称空间声明,而不使用任何前缀。

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

  

在抓取许多post之后,使用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 ="SIZBN001A5488F43223063171CA0FA59ADC635F02009-08-04T08:41:56.967ZEBICSEBIXEBICS-Kernel V2.0.4, SIZ/PPIFTBA037OZHNN......00001InitialisationCSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=...dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=......"; System.out.println("Before=" + sXML); System.out.println("After =" + hideUnUsedNamespace(sXML)); } 

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