XmlWriter.WriteRawメソッドでサロゲートペアを含んだ文字列を書き込むと文字化けする
XmlWriter.WriteRawメソッドを使ってXMLを生成するコードがあるのだが、XMLに書き込む文字列にサロゲートペアを含んでいる場合生成されたXMLが文字化けする問題がおきた。
文字化けには法則があり4バイトで構成されたサロゲートペアの文字コード(UTF8)が必ずed a0 bd ef bf bdの6バイトに変わってしまう。
しかしながら書き込む文字列長やサロゲートペアの文字位置によって問題が起きたり起きなかったりで詳しい原因は追いきれなかった。
同じコードを.NETの環境で動かすと文字化けしないのでMonoFrameworkの不具合なんだろう。
回避方法
サロゲートペアな文字を16進数のエンティティ表記(文字列)に置換してから書き込むことで問題が回避できた。
最終的には以下の様なコードになった。
XmlWriter writer = ...; string srcValue = (サロゲートペアを含んだ文字列); (略) char[] chars = srcValue.ToCharArray(); for (int i = 0; i < chars.Length; i++) { char ch1 = chars[i]; if (Char.IsHighSurrogate(ch1)) { if (i == chars.Length - 1) throw new InvalidDataException("Invalid surrogate pare. LowSurrogate not found."); char ch2 = chars[++i]; int high = Convert.ToInt32(ch1); int low = Convert.ToInt32(ch2); if (!Char.IsLowSurrogate(ch2)) throw new InvalidDataException(string.Format("Invalid surrogate pare. LowSurrogate=[{0:x}]", low)); long surrogateEntity = (high - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000; string surrogateEntityString = string.Format("&#x{0:x};", surrogateEntity); writer.WriteRaw(surrogateEntityString); } else { writer.WriteString(ch1.ToString()); } }
別のバグ
MonoFrameworkにはエンティティ表記に置換してくれるXmlWriter.WriteSurrogateCharEntityというメソッドがあるのだが、Unicode6.0のコード領域に対応していないのか文字によっては上位サロゲートの引数が範囲外だという例外が発生してまともに使えなかった(もちろん.NET環境では問題ない)。
なのでMSDNにあった計算式を元にコーディングした。