ExDoc 絡みで、今更ながら XmlWriter クラスを使って xml 形式のデータを出力しています。
そこで、XmlWriter クラスのエンコードなのですが、デフォルトでは「UTF-8」なんだけど、何故か StringWriter を使って文字列に吐き出させると「<?xml version=”1.0″ encoding=”utf-16″?>
」として吐き出されてしまう、問題があります。
ファイル自身は「utf-8」なのに、xml ヘッダが「utf-16」では、困るわけですね。
ややこしいのでコードの抜粋を示すと、
/// <summary> /// ドキュメントから文字列に変換 /// </summary> /// <returns></returns> public string SaveXML() { StringWriter sw = new StringWriter(); XmlWriterSettings setting = new XmlWriterSettings(); setting.Indent = true; XmlWriter writer = XmlWriter.Create(sw, setting); writer.WriteStartDocument(); SaveXML(writer, this.DocumentElement); writer.WriteEndDocument(); writer.Close(); string xml = sw.ToString(); sw.Close(); return xml; }
こんな風に、XmlWriter を使って出力する先を、StringWriter にしています。MSDN のドキュメントを見ると、
XmlWriterSettings.Encoding プロパティ (System.Xml)
http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=JA-JP&k=k(SYSTEM.XML.XMLWRITERSETTINGS.ENCODING);k(SOLUTIONITEMSPROJECT);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-CSHARP)&rd=true
なところで、XmlWriter は渡されたクラスのエンコードを使うので、XmlWriter 自身のエンコードは無視される、という書き方がされています。実際、StringWriter は内部コードなので、Unicode を使っているので「UTF-16」となるのは動きとしては正しいのですが、この StringWriter オブジェクトを、ファイルに書き込もうとするとちょっと困ったことになるのです。
/// <summary> /// ドキュメントからファイルを作成する /// </summary> /// <param name="path"></param> public void Save(string path) { StreamWriter sr = new StreamWriter(new FileStream(path, FileMode.Create)); string xml = SaveXML(); sr.Write(xml); sr.Close(); }
こんな風にエンコードを指定しないと、ファイルは「UTF-8」で書かれてしまうのです。なので、
- ファイルのコードは「UTF-8」なのだが、
- XML のヘッダには「UTF-16」と書いてある。
という不整合のファイルができてしまいます。仕方がないので、どちらかのエンコードを替えます。ファイルのエンコードを「UTF-16」に揃えてもよいのですが、どちらかというと XML ファイル自体を「UTF-8」に揃えておきたい訳です。
そうなると、先の StringWriter の Encoding プロパティの値が邪魔で、なんともできないなぁ、と思っていたのですが。
下記のように、StringWriter クラスを継承して、Encoding プロパティでは、常に「UTF-8」を返すようにすれば良いのでした。なるほど、確かにこれで XML のヘッダ部分が「UTF-8」になります。
private class StringWriterUTF8 : StringWriter { public override System.Text.Encoding Encoding { get { return System.Text.Encoding.UTF8; } } } /// <summary> /// ドキュメントから文字列に変換 /// </summary> /// <returns></returns> public string SaveXML() { StringWriter sw = new StringWriterUTF8(); XmlWriterSettings setting = new XmlWriterSettings(); setting.Indent = true; XmlWriter writer = XmlWriter.Create(sw, setting); writer.WriteStartDocument(); SaveXML(writer, this.DocumentElement); writer.WriteEndDocument(); writer.Close(); string xml = sw.ToString(); sw.Close(); return xml; }
こんな風に、内部クラスとして StringWriter を継承したクラスを作ると、変更が局所化されて便利です。
気づいたのだけど、UTF-8 で保存する時に xml ヘッダを書き換えるほうが簡単ですね。
実際ファイル書き込み時に xml ヘッダを付けるほうが理に適っているし。