【问题标题】:JAXB unmarshalling ignoring namespace turns element attributes into nullJAXB 解组忽略命名空间将元素属性变为 null
【发布时间】:2010-12-24 16:06:54
【问题描述】:

我正在尝试使用 JAXB 将 xml 文件解组为对象,但遇到了一些困难。实际项目在 xml 文件中有几千行,所以我在较小的范围内重现了错误,如下所示:

XML 文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<catalogue title="some catalogue title" 
           publisher="some publishing house" 
           xmlns="x-schema:TamsDataSchema.xml"/>

用于生成 JAXB 类的 XSD 文件

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="catalogue" type="catalogueType"/>

 <xsd:complexType name="catalogueType">
  <xsd:sequence>
   <xsd:element ref="journal"  minOccurs="0" maxOccurs="unbounded"/>
  </xsd:sequence>
  <xsd:attribute name="title" type="xsd:string"/>
  <xsd:attribute name="publisher" type="xsd:string"/>
 </xsd:complexType>
</xsd:schema>

代码 sn-p 1:

final JAXBContext context = JAXBContext.newInstance(CatalogueType.class);
um = context.createUnmarshaller();
CatalogueType ct = (CatalogueType)um.unmarshal(new File("file output address"));

哪个会引发错误:

javax.xml.bind.UnmarshalException: unexpected element (uri:"x-schema:TamsDataSchema.xml", local:"catalogue"). Expected elements are <{}catalogue>
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:642)
 at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:247)
 at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:242)
 at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:116)
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1049)
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:478)
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:459)
 at com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector.startElement(SAXConnector.java:148)
 at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(Unknown Source)
 at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(Unknown Source)
 at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
 at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDispatcher.scanRootElementHook(Unknown Source)
 at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
 at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
 at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
 at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
 at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
    ...etc

因此,XML 文档中的命名空间会导致问题,不幸的是,如果将其删除,它可以正常工作,但由于文件是由客户端提供的,因此我们无法使用它。我尝试了多种在 XSD 中指定它的方法,但似乎没有一种排列有效。

我还尝试使用以下代码解组忽略命名空间:

Unmarshaller um = context.createUnmarshaller();
final SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
final XMLReader reader = sax.newSAXParser().getXMLReader();
final Source er = new SAXSource(reader, new InputSource(new FileReader("file location")));
CatalogueType ct = (CatalogueType)um.unmarshal(er);
System.out.println(ct.getPublisher());
System.out.println(ct.getTitle());

工作正常,但无法解组元素属性和打印

null
null

由于我们无法控制的原因,我们仅限于使用 Java 1.5,而且我们正在使用 JAXB 2.0,这很不幸,因为第二个代码块使用 Java 1.6 可以正常工作。

任何建议都将不胜感激,另一种方法是在解析文件之前从文件中删除名称空间声明,这似乎不优雅。

【问题讨论】:

  • 为什么不让模式描述命名空间?
  • 虽然 XSD 中没有定义,但可以使用命名空间(注释选项)扩展 JAXB 注释类以使其工作。我也想知道一种在 XSD 中指定它的方法……所以这些注释选项实际上是自动生成的,不必手动设置。

标签: java namespaces jaxb attributes jdk1.5


【解决方案1】:

感谢您的这篇文章和您的代码 sn-p。这绝对让我走上了正确的道路,因为我也疯狂地试图处理一些供应商提供的 XML,这些 XML 到处都是 xmlns="http://vendor.com/foo"

我的第一个解决方案(在我阅读您的帖子之前)是将 XML 放入一个字符串中,然后是 xmlString.replaceAll(" xmlns=", " ylmns=");(恐怖,恐怖)。除了冒犯我的感受之外,在处理来自 InputStream 的 XML 时也很痛苦。

查看您的代码 sn-p 后,我的第二个解决方案:(我正在使用 Java7)

// given an InputStream inputStream:
String packageName = docClass.getPackage().getName();
JAXBContext jc = JAXBContext.newInstance(packageName);
Unmarshaller u = jc.createUnmarshaller();

InputSource is = new InputSource(inputStream);
final SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
final XMLReader reader;
try {
    reader = sax.newSAXParser().getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
    throw new RuntimeException(e);
}
SAXSource source = new SAXSource(reader, is);
@SuppressWarnings("unchecked")
JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal(source);
return doc.getValue();

但是现在,我找到了我更喜欢的第三种解决方案,希望它对其他人有用:如何在架构中正确定义预期的命名空间:

<xsd:schema jxb:version="2.0"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
  xmlns="http://vendor.com/foo"
  targetNamespace="http://vendor.com/foo"
  elementFormDefault="unqualified"
  attributeFormDefault="unqualified">

有了它,我们现在可以删除sax.setNamespaceAware(false); 行(更新:实际上,如果我们保持unmarshal(SAXSource) 调用,那么我们需要sax.setNamespaceAware(true)。但更简单的方法是不要打扰SAXSource 和围绕其创建的代码,而不是 unmarshal(InputStream),默认情况下是命名空间感知的。而且 marshal() 的输出也有适当的命名空间。

是的。只用了大约 4 小时。

【讨论】:

  • 我不明白您的第三个解决方案,您让命名空间知道这是导致此异常的原因。
【解决方案2】:

如何忽略命名空间

您可以使用非命名空间感知的XMLStreamReader,它基本上会从您正在解析的 xml 文件中删除所有命名空间:

// configure the stream reader factory
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false); // this is the magic line

// create xml stream reader using our configured factory
StreamSource source = new StreamSource(someFile);
XMLStreamReader xsr = xif.createXMLStreamReader(source);

// unmarshall, note that it's better to reuse JAXBContext, as newInstance()
// calls are pretty expensive
JAXBContext jc = JAXBContext.newInstance(your.ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object unmarshal = unmarshaller.unmarshal(xsr);

现在输入 JAXB 的实际 xml 没有任何命名空间信息。


重要提示 (xjc)

如果您使用xjcxsd 架构生成Java 类,并且该架构定义了命名空间,那么生成的注释将具有该命名空间,因此请手动删除它!否则 JAXB 将无法识别此类数据。

需要更改注解的地方:

  • ObjectFactory.java

     // change this line
     private final static QName _SomeType_QNAME = new QName("some-weird-namespace", "SomeType");
     // to something like
     private final static QName _SomeType_QNAME = new QName("", "SomeType", "");
    
     // and this annotation
     @XmlElementDecl(namespace = "some-weird-namespace", name = "SomeType")
     // to this
     @XmlElementDecl(namespace = "", name = "SomeType")
    
  • 包信息.java

     // change this annotation
     @javax.xml.bind.annotation.XmlSchema(namespace = "some-weird-namespace", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
     // to something like this
     @javax.xml.bind.annotation.XmlSchema(namespace = "", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    

现在,您的 JAXB 代码将期望看到没有任何命名空间的所有内容,而我们创建的 XMLStreamReader 正好提供了这一点。

【讨论】:

  • 这是迄今为止我在互联网上找到的最佳解决方案。多谢。干杯。
  • 如何使用此代码拦截进入我的 Spring 应用程序的请求,以便在请求正文到达 JAXB 之前忽略它的命名空间?
  • 多年前亲爱的@Dmitry-Avtonomov,我的一天被行为不端的遗留代码毁了,但你拯救了我的一天
  • 亲爱的 @Mzzl 从 2021 年开始,我很高兴这个答案对 8 年后的人有用。同样令人遗憾的是,2021 年仍然会遇到同样的问题。
【解决方案3】:

这是我对这个命名空间相关问题的解决方案。我们可以通过实现我们自己的 XMLFilter 和 Attribute 来欺骗 JAXB。

class MyAttr extends  AttributesImpl {

    MyAttr(Attributes atts) {
        super(atts);
    }

    @Override
    public String getLocalName(int index) {
        return super.getQName(index);
    }

}

class MyFilter extends XMLFilterImpl {

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        super.startElement(uri, localName, qName, new VersAttr(atts));
    }

}

public SomeObject testFromXML(InputStream input) {

    try {
        // Create the JAXBContext
        JAXBContext jc = JAXBContext.newInstance(SomeObject.class);

        // Create the XMLFilter
        XMLFilter filter = new VersFilter();

        // Set the parent XMLReader on the XMLFilter
        SAXParserFactory spf = SAXParserFactory.newInstance();
        //spf.setNamespaceAware(false);

        SAXParser sp = spf.newSAXParser();
        XMLReader xr = sp.getXMLReader();
        filter.setParent(xr);

        // Set UnmarshallerHandler as ContentHandler on XMLFilter
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = unmarshaller
                .getUnmarshallerHandler();
        filter.setContentHandler(unmarshallerHandler);

        // Parse the XML
        InputSource is = new InputSource(input);
        filter.parse(is);
        return (SomeObject) unmarshallerHandler.getResult();

    }catch (Exception e) {
        logger.debug(ExceptionUtils.getFullStackTrace(e));
    }

    return null;
}

【讨论】:

  • 感谢您的回答!一些提示:(1)不要以“我有同样的问题”开头。它不相关,是进入低质量审查队列的好方法。 (2)从您的答案中删除该部分(这几乎是整个第一段)并且您有一个纯代码答案。虽然它可能是正确的,但最好用英语解释为什么答案是正确的,这样我们都可以学习(而不仅仅是拥有神奇的工作代码)。如果您在进行编辑后通知我,我会考虑对此表示赞同。欢迎使用 Stack Overflow!
【解决方案4】:

这篇文章中解释了这个问题的解决方法:JAXB: How to ignore namespace during unmarshalling XML document?。它解释了如何使用 SAX 过滤器从 XML 中动态添加/删除 xmlns 条目。处理编组和解组的方式相同。

【讨论】:

    猜你喜欢
    • 2015-06-19
    • 1970-01-01
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 2010-12-17
    相关资源
    最近更新 更多