web-dev-qa-db-ja.com

名前空間を無視してJAXBの非整列化により、要素の属性が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"/>

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の使用に制限されており、2番目のコードブロックがJava 1.6を使用して期待どおりに機能するため、残念ながらJAXB 2.0を使用しています。

提案があれば大歓迎です。代替案は、ファイルを解析する前に名前空間宣言をファイルから削除することです。

17
user227614

この投稿とコードスニペットをありがとう。 _xmlns="http://vendor.com/foo"_がいたるところにあるベンダー提供のXMLにも対処しようとしていたので、それは間違いなく私を正しい道に導きました。

私の最初の解決策(投稿を読む前)は、XMLを文字列で受け取り、次にxmlString.replaceAll(" xmlns=", " ylmns=");(ホラー、ホラー)にすることでした。私の感性を害することに加えて、InputStreamからのXMLを処理するときに苦労しました。

コードスニペットを確認した後の2番目のソリューション:(私は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();
_

しかし今、私は私がはるかに好きで、うまくいけばそれが他の人に役立つかもしれない3番目のソリューションを見つけました:スキーマで期待される名前空間を適切に定義する方法:

_<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時間。

15
Pierre D

名前空間を無視する方法

名前空間非対応の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には名前空間情報がありません。


重要な注意(xjc)

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はそれだけを提供します。

9

JAXBについてのことは、それが実際にXMLおよびXMLスキーマを正しく実装することです。それは良いことのように聞こえますが、あなたが発見しているように、JAXBはしばしば少し...あまりにもリテラルです。

つまり、「ここにカタログが必要です」というXSDがあり、次に「ここが{x-schema:TamsDataSchema.xml}カタログです」というXMLがあり、当然のことながらJAXBがあるようです。過度にアナルになって「それはクールじゃない」と言います。私が見ることができるこれを回避する方法はありません。 XMLを事前に解析して名前空間を削除するか、スキーマを調整して許可する必要があります。

あなたが言ったようにどちらの解決策もエレガントではありませんが、正方形のペグを丸い穴に嵌めようとするとき、あなたは時々少しエレガントでない必要があります(そしてあなたは基本的に「この正方形/名前空間のペグをラウンド/非名前空間の穴」なので、...)

7
machineghost

これは、この名前空間関連の問題に対する私の解決策です。独自の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;
}
3
Y.Y

この投稿で説明されているこの問題の回避策があります: JAXB:アンマーシャリングXMLドキュメント中に名前空間を無視する方法? 。 SAXフィルターを使用してXMLからxmlnsエントリを動的に追加/削除する方法を説明します。マーシャリングとアンマーシャリングを同様に処理します。

1
Kristofer