XmlSerializer で CDATA セクションを含めるように指定する
というわけで無理やりネタ。ぐぐればすぐにわかるようなことだけど!
public class Foo { [XmlElement] public string Bar = "test text"; }
<Foo> <Bar>test text</Bar> </Foo>
となるけれども
<Foo> <Bar><![CDATA![test text]]></Bar> </Foo>
と、CDATA セクションになっていたってちゃんとデシリアライズできる。では逆に CDATA セクションへシリアライズしたいときはどうしたらいいだろうか? という問題。
方法の1つしては、XmlAnyElement を利用して自分で記述する。Any 系属性の問題点は、XmlSerializer の Unknown 系イベントが発生しなくなるため、デシリアライズを実行する人が考慮しなければならない点が増えたり、そのことにそもそも気がつかないことで不具合に遭遇してしまう可能性がゼロではないこと。
それを避けるためには自己完結した状態にする必要があって、その方法として、
public class Foo { private string bar; [XmlElement] public XmlCDataSection Bar { get { return new XmlDocument().CreateCDataSection(this.bar); set { this.bar = value.Value; } } }
みたいな形で XmlCDataSection をそのまま使用してしまう方法や、
public struct CDATA : IXmlSerializable { private string text; public CDATA(string text) { this.text = value; } XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { this.text = reader.ReadElementString(); } void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { writer.WriteCData(this.text); } // string 型との暗黙の変換なんて実装してあると便利かもネ }
みたいな IXmlSerializable な型を作成して、
public class Foo { [XmlElement] public CDATA Bar; }
なんてしちゃってりしてもよい。それでは、
public class Baz { [XmlAttribute] public string Attr = "attribute value"; [XmlText] public string Text = "text value"; }
こんなクラスは、
<Baz Attr="attribute value"> text value </Baz>
こんな感じのシリアライズ結果がえられるわけだが、これを
<Baz Attr="attribute value"> <![CDATA![text value]]> </Baz>
のようにするにはどうすればいいだろうか? 最初にあげた XmlAnyElement を利用する方法で実装しようと思うと、この Baz Element の外側に実装を加えないといけないので不可能である。*1
XmlCDataSection を利用する方法も XmlTextAttribute との併用ができないため利用できない。
となると、3番目の CDATA 構造体のような手法で、Baz クラス自身に IXmlSerializable を実装し、シリアライズの制御を行うことになるのだが、前述の Baz クラスに限定されたコードの記述は難しくはないが、汎用的なコードを書こうと考え出すとどうにも難易度が高くなってしまう……というか、XmlSerializer のやってることを丸々やることになるので、既存の XmlSerializer の処理をうまいこと流用できるような方法を考えるのがよさそうになってしまう。
*1:もちろん、外側の実装に手をいれることができれば実現は簡単だ。前述のような問題点が発生するのは同様。