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 ヘッダを付けるほうが理に適っているし。