Development

[VB/C#] System.Xml.XmlException 無効な文字の対策

VB/C# で XDocument.LoadParse メソッドを呼んだときに「型 ‘System.Xml.XmlException’ のハンドルされていない例外が System.Xml.dll で発生しました」・「追加情報:’ ‘ (16 進数値 0xXX) は無効な文字です。 行 XX、位置 XX。」のような例外を避けるための話です。

Xml.XmlException

GitHub など有名な開発者向けのサービスでも、このような例外を発生させる XML を出力していることは、よくあり、悩まされます。

原因

XML 内に改行やタブ以外の制御文字などが含まれているとこのような例外が発生します。ちなみに、IE で XML を表示しようとしてもエラーになります(Firefox などでは普通に表示できます)。

XML で有効な文字は次の通りのようです。

  • #x9
  • #xA
  • #xD
  • #x20-#xD7FF
  • #xE000-#xFFFD
  • #x10000-#x10FFFF

関連ページ: PRB: Parsing XML Containing Invalid Character May Raise ArgumentException

対策

読めることを優先し、これ以外の文字は削除してから XDocument.Parse メソッドを呼ぶようにします。もし、制御文字が重要な場合は、HTML エンコードするように書き換えてください。

Sanitize 関数が今回用意した、文字列から XML で無効な文字列を削除して返す関数です。

Visual Basic (VB.NET)

Module.vb
Sub Main()
    Dim url = "http://example.jp/xml"
    Dim client = New WebClient With {.Encoding = Text.Encoding.UTF8}
    Dim xml = client.DownloadString(url)
    Dim d = XDocument.Parse(Sanitize(xml))
End Sub

Private Function Sanitize(xml As String) As String
    Dim sb = New Text.StringBuilder

    For Each c In xml
        Dim code = AscW(c)

        If code = &H9 OrElse
            code = &HA OrElse
            code = &HD OrElse
            (&H20 <= code AndAlso code <= &HD7FF) OrElse
            (&HE000 <= code AndAlso code <= &HFFFD) OrElse
            (&H10000 <= code AndAlso code <= &H10FFFF) Then

            sb.Append(c)
        End If
    Next

    Return sb.ToString
End Function

C#

Program.cs
static void Main(string[] args)
{
    var url = "http://example.jp/xml";
    var client = new WebClient() { Encoding = Encoding.UTF8 };
    var xml = client.DownloadString(url);
    var d = XDocument.Parse(Sanitize(xml));
}

private static string Sanitize(string xml)
{
    var sb = new System.Text.StringBuilder();

    foreach (var c in xml)
    {
        var code = (int)c;

        if (code == 0x9 ||
            code == 0xa ||
            code == 0xd ||
            (0x20 <= code && code <= 0xd7ff) ||
            (0xe000 <= code && code <= 0xfffd) ||
            (0x10000 <= code && code <= 0x10ffff))
        {
            sb.Append(c);
        }
    }

    return sb.ToString();
}