我正在尝试使用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>
代码段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按需工作。
任何建议都会非常感激,另一种方法就是在解析它之前将名称空间声明从文件中删除,这似乎是不优雅的。
答案 0 :(得分:9)
感谢您收到此帖子和您的代码段。它肯定让我走上了正确的道路,因为我也在努力处理一些供应商提供的XML,其中xmlns="http://vendor.com/foo"
遍布整个地方。
我的第一个解决方案(在我阅读你的帖子之前)是用字符串中的XML,然后是xmlString.replaceAll(" xmlns=", " ylmns=");
(恐怖,恐怖)。除了冒犯我的敏感性之外,从InputStream处理XML时也很痛苦。
我的第二个解决方案,看了你的代码片段后:(我正在使用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个小时的排水管。
答案 1 :(得分:7)
关于JAXB的问题是,它实际上正确地实现了XML和XML模式。这听起来像是件好事,但正如你所发现的那样,JAXB通常有点......太字面了。
所以,在我看来你有一个XSD上写着“期待这里的目录”,然后你得到的XML上写着“这里是一个{x-schema:TamsDataSchema.xml}目录”,并且不出所料JAXB过度肛门并说“这不酷”。我无法解决这个问题;您必须预先解析XML以删除命名空间,或者您需要调整架构以允许它。
正如你所说的那样,任何一种解决方案都不优雅,但是当你试图将一个方形钉固定在一个圆孔中时,你需要有点不优雅(而且你基本上说“适合这个方块/命名空间”挂入圆形/非命名空洞“,所以......”
答案 2 :(得分:6)
您可以使用非名称空间感知的XMLStreamReader
,它基本上会删除您正在解析的xml文件中的所有名称空间:
JAXBContext jc = JAXBContext.newInstance(your.ObjectFactory.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false); // this is the magic line
StreamSource source = new StreamSource(f);
XMLStreamReader xsr = xif.createXMLStreamReader(source);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object unmarshal = unmarshaller.unmarshal(xsr);
现在,输入JAXB的实际xml没有任何命名空间信息。
如果您使用xsd
从xjc
模式生成了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")
package-info.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
就是这样。
答案 3 :(得分: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;
}
答案 4 :(得分:1)
此帖中解释了此问题的解决方法:JAXB: How to ignore namespace during unmarshalling XML document?。它解释了如何使用SAX Filter从XML动态添加/删除xmlns条目。处理编组和解组等。