Подтвердить что ты не робот

JAXB unmarshalling игнорирует пространство имен, превращает атрибуты элемента в null

Я пытаюсь использовать 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"/>

Файл 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, но ни одна из перестановок не работает.

Я также попытался отключить игнорирование пространства имен, используя следующий код:

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.

любые предложения были бы весьма полезны, альтернатива - вырезать декларацию пространства имен из файла перед ее разборкой, которая кажется неэлегантной.

4b9b3361

Ответ 1

Дело в JAXB заключается в том, что оно действительно реализует XML и XML-схему. Это звучит неплохо, но, поскольку вы обнаруживаете, JAXB часто может быть немного... слишком буквальным.

Итак, мне кажется, что у вас есть XSD, который говорит "ожидайте каталог здесь", а затем у вас есть XML, в котором говорится "здесь каталог {x-schema: TamsDataSchema.xml}", и неудивительно, что JAXB становится слишком анальным и говорит: "Это не круто". Невозможно обойти это, что я вижу; либо вы должны предварительно проанализировать XML для удаления пространства имен, либо вам нужно настроить схему, чтобы разрешить ее.

Любое решение, как вы сказали, неэлегантное, но когда вы пытаетесь поместить квадратную привязку в круглое отверстие, иногда вам нужно быть немного неэлегантным (и вы в основном говорите: "Вставьте этот квадрат/пространство имен привязать к круглому/не-именному отверстию", поэтому...)

Ответ 2

Благодарим вас за этот пост и фрагмент кода. Это определенно поставило меня на правильный путь, так как я тоже сходил с ума, пытаясь разобраться с каким-то предоставленным поставщиком XML, который имел xmlns="http://vendor.com/foo" повсюду.

Мое первое решение (прежде чем я прочитал ваше сообщение) состояло в том, чтобы взять XML в String, а затем xmlString.replaceAll(" xmlns=", " ylmns="); (ужас, ужас). Помимо оскорбления моей чувствительности, было больно при обработке 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), который по умолчанию является пространством имен. И в этом случае маршал() также имеет собственное пространство имен.

Yeh. Примерно через 4 часа после слива.

Ответ 3

Как игнорировать пространства имен

Вы можете использовать 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")
    
  • 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, которые мы создали для поставки именно этого.

Ответ 4

Вот мое решение для этой проблемы, связанной с пространством имен. Мы можем обмануть 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;
}