JAXB unmarshalling игнорирование пространства имен превращает атрибуты элементов в null
Я пытаюсь использовать JAXB для unmarshal 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"/>
xsd-файл для создания классов JAXB
<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, но ни одна из перестановок, похоже, не работает.
Я также попытался unmarshal игнорируя пространство имен, используя следующий код:
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());
который отлично работает, но не может unmarshal атрибуты элементов и печатает
null
null
по не зависящим от нас причинам мы ограничены в использовании Java 1.5, и мы используем JAXB 2.0, что неудачно, потому что второй блок кода работает по желанию, используя Java 1.6.
любые предложения были бы весьма признательны, альтернативой является вырезание объявления пространства имен из файла перед его разбором, который кажется неэлегантным.
5 ответов
дело в том, что JAXB на самом деле реализует XML и XML-схему правильно. Это звучит как хорошая вещь, но как ты поняла, JAXB, который часто может быть немного ... слишком буквально.
Итак, мне кажется, что у вас есть XSD, который говорит: "ожидайте каталог здесь", а затем у вас есть XML, который говорит: "Вот {X-schema: TamsDataSchema.xml}каталог", и неудивительно, что JAXB становится чрезмерно анальным и говорит: "это не круто."Нет никакого способа обойти это, что я вижу; либо вам нужно предварительно проанализировать XML, чтобы удалить пространство имен, либо вам нужно настроить схему, чтобы разрешить это.
любое решение, как вы сказали, неэлегантно, но когда вы пытаетесь поместить квадратный колышек в круглое отверстие, иногда вам нужно быть немного неэлегантным (и вы в основном говорите: "установите этот квадратный/пространственный колышек в круглое/не-пространственное отверстие", поэтому ...)
Спасибо за этот пост и ваш код. Это определенно поставило меня на правильный путь, поскольку я также сходил с ума, пытаясь иметь дело с некоторым поставщиком XML, который имел xmlns="http://vendor.com/foo"
повсюду.
моим первым решением (до того, как я прочитал ваш пост) было взять XML в строке, затем xmlString.replaceAll(" xmlns=", " ylmns=");
(ужас, ужас). Помимо оскорбления моей чувствительности, in была болью при обработке XML из InputStream.
мое второе решение, после просмотра вашего кода фрагмент: (я использую 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 часов в канализацию.
как игнорировать пространства имен
можно использовать 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);
теперь фактический xml, который вводится в JAXB, не имеет никакой информации о пространстве имен.
важное примечание (xjc)
если вы создали классы java из xsd
схему с помощью xjc
и схема пространство имен определено, созданное аннотации будут иметь это пространство имен, поэтому удалите его вручную! В противном случае 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
мы создали все необходимое, что.
вот мое решение для этой проблемы, связанной с пространством имен. Мы можем обмануть JAXB, реализовав наш собственный XMLFilter и атрибут.
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;
}
существует обходной путь для этой проблемы описано в этом посте: JAXB: как игнорировать пространство имен во время unmarshalling XML-документа?. В нем объясняется, как динамически добавлять / удалять записи xmlns из XML с помощью фильтра SAX. Ручки сортировочных и обратный так.