web-dev-qa-db-ja.com

Unicode Regex;無効なXML文字

有効なXML文字のリストは、仕様によって定義されているとおり、よく知られています。

#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

私の質問は、Unicodeの一般的なカテゴリを使用して、コードポイントを実際にハードコーディングすることなく、これ(またはその逆)のPCRE正規表現を作成できるかどうかです。逆は、[\ p {Cc}\p {Cs}\p {Cn}]のようになる可能性がありますが、改行とタブが適切にカバーされず、その他の無効な文字が欠落している場合があります。

43
Edward Z. Yang

これはあなたの質問に対する正確な回答ではないことはわかっていますが、ここに記載しておくと役に立ちます。

一致する正規表現validXML文字:

[\u0009\u000a\u000d\u0020-\uD7FF\uE000-\uFFFD]

したがって、無効な文字をXMLから削除するには、次のようにします

// filters control characters but allows only properly-formed surrogate sequences
private static Regex _invalidXMLChars = new Regex(
    @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
    RegexOptions.Compiled);

/// <summary>
/// removes any unusual unicode characters that can't be encoded into XML
/// </summary>
public static string RemoveInvalidXMLChars(string text)
{
    if (string.IsNullOrEmpty(text)) return "";
    return _invalidXMLChars.Replace(text, "");
}

私は常駐の正規表現/ XMLの天才 4,400以上の賛成投票の投稿者 がいて、これをチェックして、彼はそれにサインオフしました。

88
Jeff Atwood

内部でコードポイントを TF-16 に格納するシステムの場合、0xFFFFを超えるコードポイントにはサロゲートペア(xD800-xDFFF)を使用するのが一般的です。これらのシステムでは、たとえば\を実際に使用できるかどうかを確認する必要があります。 u12345またはそれをサロゲートペアとして指定する必要があります。 (C#では \ u1234(16ビット)および\ U00001234(32ビット) を使用できることがわかりました)

Microsoft によると、W3Cの推奨事項では、要素名または属性名内の代理文字は許可されていません。 W3sのWebサイトを検索しているときに、興味のある C079 および C078 が見つかりました。

6
some

私はこれをJava=で試してみましたが、うまくいきます:

private String filterContent(String content) {
    return content.replaceAll("[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
}

ジェフありがとう。

5
Yuval Rimar

XmlConvert.IsXmlChar Method を使用してC#で正しくないXML文字を削除する別の方法(.NET Framework 4.0以降で利用可能)

public static string RemoveInvalidXmlChars(string content)
{
   return new string(content.Where(ch => System.Xml.XmlConvert.IsXmlChar(ch)).ToArray());
}

または、すべての文字がXML有効であることを確認できます。

public static bool CheckValidXmlChars(string content)
{
   return content.All(ch => System.Xml.XmlConvert.IsXmlChar(ch));
}

.Net Fiddle- https://dotnetfiddle.net/v1TNus

たとえば、垂直タブ記号(\ v)はXMLでは無効で、UTF-8は有効ですが、XML 1.0は無効であり、多くのライブラリ(libxml2を含む)でもこれを逃して、無効なXMLを警告なしで出力します。

1
Alex Vazhev

16進コードがXMLに存在する場合、上記の解決策は私にとってはうまくいきませんでした。例えば.

<element>&#x8;</element>

次のコードは壊れます:

string xmlFormat = "<element>{0}</element>";
string invalid = " &#x8;";
string xml = string.Format(xmlFormat, invalid);
xml = Regex.Replace(xml, @"[\x01-\x08\x0B\x0C\x0E\x0F\u0000-\u0008\u000B\u000C\u000E-\u001F]", "");
XDocument.Parse(xml);

それは返します:

XmlException: ''、16進値0x08は無効な文字です。行1、位置14。

以下は、改善された正規表現であり、上記の問題を修正しました。

&#x([0-8BCEFbcef] | 1 [0-9A-Fa-f]); | [\ x01-\x08\x0B\x0C\x0E\x0F\u0000-\u0008\u000B\u000C\u000E-\u001F]

以下は、最初の300ユニコード文字の単体テストであり、無効な文字のみが削除されていることを確認します。

[Fact]
        public void validate_that_RemoveInvalidData_only_remove_all_invalid_data()
        {
            string xmlFormat = "<element>{0}</element>";
            string[] allAscii = (Enumerable.Range('\x1', 300).Select(x => ((char)x).ToString()).ToArray());
            string[] allAsciiInHexCode = (Enumerable.Range('\x1', 300).Select(x => "&#x" + (x).ToString("X") + ";").ToArray());
            string[] allAsciiInHexCodeLoweCase = (Enumerable.Range('\x1', 300).Select(x => "&#x" + (x).ToString("x") + ";").ToArray());

            bool hasParserError = false;
            IXmlSanitizer sanitizer = new XmlSanitizer();

            foreach (var test in allAscii.Concat(allAsciiInHexCode).Concat(allAsciiInHexCodeLoweCase))
            {
                bool shouldBeRemoved = false;
                string xml = string.Format(xmlFormat, test);
                try
                {
                    XDocument.Parse(xml);
                    shouldBeRemoved = false;
                }
                catch (Exception e)
                {
                    if (test != "<" && test != "&") //these char are taken care of automatically by my convertor so don't need to test. You might need to add these.
                    {
                        shouldBeRemoved = true;
                    }
                }
                int xmlCurrentLength = xml.Length;
                int xmlLengthAfterSanitize = Regex.Replace(xml, @"&#x([0-8BCEF]|1[0-9A-F]);|[\u0000-\u0008\u000B\u000C\u000E-\u001F]", "").Length;
                if ((shouldBeRemoved && xmlCurrentLength == xmlLengthAfterSanitize) //it wasn't properly Removed
                    ||(!shouldBeRemoved && xmlCurrentLength != xmlLengthAfterSanitize)) //it was removed but shouldn't have been
                {
                    hasParserError = true;
                    Console.WriteLine(test + xml);
                }
            }
            Assert.Equal(false, hasParserError);
        }
1
Rafi