替换无效XML字符的字符引用

本文关键字:字符 引用 XML 无效 替换 | 更新日期: 2023-09-27 18:04:36

我正在使用ADO.NET从SQL Server投射一些数据作为XML。我的一些数据包含在XML中无效的字符,例如CHAR(7)(称为BEL)。

SELECT 'This is BEL: ' + CHAR(7) AS A FOR XML RAW

SQL Server将无效字符编码为数字引用:

<row A="This is BEL: &#x7;" />

但是,即使编码的形式在XML 1.0下也是无效的,并且会在XML解析器中产生错误:

var doc = XDocument.Parse("<row A='"This is BEL: &#x7;'" />");
// XmlException: ' ', hexadecimal value 0x07, is an invalid character. Line 1, position 25.

我想用Unicode替换字符'�'替换所有这些无效的数字引用。我知道如何处理未编码的XML:

string str = "<row A='"This is BEL: 'u0007'" />";
if (str.Any(c => !XmlConvert.IsXmlChar(c)))
    str = new string(str.Select(c => XmlConvert.IsXmlChar(c) ? c : '�').ToArray());
          // <row A="This is BEL: �" />

是否有一个直接的方法,使它的工作编码XML太?我宁愿避免必须先HtmlDecode然后HtmlEncode整个字符串,为了不冒引入更改的风险,而不是无效的字符替换。

编辑:转换需要在我的c#代码中完成,而不是SQL,以便集中实现

替换无效XML字符的字符引用

我使用正则表达式进行了另一次尝试。这应该处理十进制和十六进制字符代码。此外,这将不会影响任何东西,但数字编码字符。

public string ReplaceXMLEncodedCharacters(string input)
{
    const string pattern = @"&#(x?)([A-Fa-f0-9]+);";
    MatchCollection matches = Regex.Matches(input, pattern);
    int offset = 0;
    foreach (Match match in matches)
    {
        int charCode = 0;
        if (string.IsNullOrEmpty(match.Groups[1].Value))
            charCode = int.Parse(match.Groups[2].Value);
        else
            charCode = int.Parse(match.Groups[2].Value, System.Globalization.NumberStyles.HexNumber);
        char character = (char)charCode;
        input = input.Remove(match.Index - offset, match.Length).Insert(match.Index - offset, character.ToString());
        offset += match.Length - 1;
    }
    return input;
}

可以将特殊字符包装在CDATA标记中。这通知解析器忽略标记中的文本。使用你的例子:

SELECT 'This is BEL: <![CDATA[' + CHAR(7) + ']]>' AS A FOR XML RAW

这将允许至少解析XML,尽管需要对文档结构进行轻微更改。

供参考,这是我的解决方案。我基于Tonkleton的答案,但修改了它以更接近HtmlDecode的内部实现。下面的代码忽略代理对。

// numeric character references
static readonly Regex ncrRegex = new Regex("&#x?[A-Fa-f0-9]+;");
static string ReplaceInvalidXmlCharacterReferences(string input)
{
    if (input.IndexOf("&#") == -1)   // optimization
        return input;
    return ncrRegex.Replace(input, match =>
    {
        string ncr = match.Value;            
        uint num;
        var frmt = NumberFormatInfo.InvariantInfo;
        bool isParsed =
            ncr[2] == 'x' ?   // the x must be lowercase in XML documents
            uint.TryParse(ncr.Substring(3, ncr.Length - 4), NumberStyles.AllowHexSpecifier, frmt, out num) :
            uint.TryParse(ncr.Substring(2, ncr.Length - 3), NumberStyles.Integer, frmt, out num);
        return isParsed && !XmlConvert.IsXmlChar((char)num) ? "�" : ncr;
    });
}