Jの衝動書き日記

さらりーまんSEの日記でございます。

JAXBでCDATAを出力したい

 例えば、以下のようにやりたい場合

<arg><[!CDATA[hogehoge]]></arg>

 
 XML→オブジェクトの変換は、CDATAが付いていようが無かろうが問題なく変換される。
だが、オブジェクト→XMLの変換時は、自動では変換されない。

 変換方法は以下のようなものがある。

 CDATA形式と値形式で出力するメソッドをオブジェクトに追加する。
 上記の場合だと、getArgとgetArgContentsを作成し、getArg→<[!CDATA[hogehoge]]>、getArgContents→hogehogeのように返るようにする。getArgの内容はgetArgContentsの返り値に<[!CDATA]>を挟む形になる

  • 方法2 アダプターの作成

 CDATA形式に変換するアダプターを作成し、それをCDATA形式で返したい属性に指定する。アダプターの処理内容は、方法1とほぼ同じだが、利用側はそれを指定すればいいだけである。
 参考:http://theopentutorials.com/tutorials/java/jaxb/jaxb-marshalling-and-unmarshalling-cdata-block/

  • 方法3 タグの設定

 CDATA形式で返却したいタグを設定し、それが来たらCDATA形式に変換する。
 参考1(XMLStreamWriter):http://web.archiveorange.com/archive/v/ACbGtrzbyFAGeo5vbWuE
 参考2(XMLEventWriter):http://d.hatena.ne.jp/hatanaka_akihiro/20090226/1235735887
→ただこの例では、すべてのTEXT属性のものがCDATAに変換されてしまうため、参考1のようにタグを記録する必要がある

 上記の方法を試してみた。
 まず試したのが方法1だ。余計なメソッドが増えるが、時間もないしーーお仕事ではCDATA形式で送ると途中で連絡があった……ーーこれでいいと思っていたが問題が生じた。<の部分が&lt;とエスケープ変換されて出力されるのである。
 これはmarshalで使用しているWriterがXMLStreamWriterであるためらしい。どうにもならなかったので、WriterをStringWriterに変換したら上手くいった(代わりにstandaloneが出てしまうがもう無視)。
 ただ、いちいちメソッドを作成するのも面倒なので、方法2を採用した。
ちなみに方法3であるが、処理中のタグ名を属性として持たなければいけないため――例えば上記のargの中身であるhogehogeが来てもそのタグ名がわからないのでタグの処理に入るたびにそれを記録しないといけないーーのちのち問題が生じそうなのでやめた。

 で、以下が方法2の具体例である。

1.アダプターを作成する

public class CDATAAdapter extends XmlAdapter<String, String>{

    @Override
    public String marshal(String v) throws Exception {
        return "<![CDATA[" + v + "]]>";
    }

    @Override
    public String unmarshal(String v) throws Exception {
        return v;
    }
}

2. オブジェクトの属性にアダプターを指定する

    @XmlJavaTypeAdapter(value=CDATAAdapter.class)
    @XmlElement(name = "args", required = false )
    public String getArgs() { return  args; }

 
3. ハンドラの指定を行う

StringWriter writer = new StringWriter();
JAXBContext jc = JAXBContext.newInstance(Info.class);
Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty("com.sun.xml.internal.bind.characterEscapeHandler",
                    new CharacterEscapeHandler() {
                        @Override
                        public void escape(char ch, int start, int length,
                                boolean isAttVal, Writer writer)
                                throws IOException {
                            writer.write(ch, start, length);
                        }
                    });

 ちなみにこの方法でも、WriterをXMLStreamWriterにした場合は<>はエスケープ変換される。
 また、antでコンパイルする場合com.sun.xml.*を使用しているためエラーとなってしまう。その場合、javacタスクに<compilerarg value="-XDignore.symbol.file" />を追加すればよい。