将 varchar() 中的重音字符转换为 XML 导致 "illegal XML character"

Converting accented characters in varchar() to XML causing "illegal XML character"

我有 table 被一个应用程序写入。该字段是 varchar(max)。数据看起来像 xml。

DECLARE @poit VARCHAR(100)
SET @poit = '<?xml version="1.0" encoding="utf-8"?><test>VÍA</test>'
SELECT CONVERT(XML,@poit)

但是(似乎是因为 UTF8;删除它有效),我得到这个错误:

XML parsing: line 1, character 46, illegal xml character

有没有办法干净地转换它?

我找到了这个线程,它讨论了不支持 "non-ASCII characters" 的 varchar,但显然 I 是非 unicode。是的,我可以做到:

SELECT CONVERT(XML, REPLACE(@poit, 'encoding="utf-8"', ''))

但这是最​​好的方法吗?

Why does casting a UTF-8 VARCHAR column to XML require converting to NVARCHAR and encoding change?

我会尝试将您的 @poit 变量的数据类型从 VARCHAR(100) 更改为 NVARCHAR(100)。然后将 utf-8 编码替换为 utf-16,这样您的代码将类似于:

    DECLARE @poit NVARCHAR(100)
    SET @poit = '<?xml version="1.0" encoding="utf-8"?><test>VÍA</test>'
    SELECT CONVERT(XML,REPLACE(@poit, 'utf-8', 'utf-16'))

只要您不在 SELECT returns 大量结果中使用其中的替换调用转换,性能应该就可以了,它会完成工作.

参考:http://xml.silmaril.ie/characters.html <- 向下滚动,您会看到一些关于 utf-8 和 utf-16 之间区别的信息。希望这对您有所帮助!

如果您只想要答案而不需要完整的解释,请向下滚动到 "Conclusion"。但是,你真的应该花点时间阅读解释 😸

这里发生了一些事情:

  1. <xml> 元素的 encoding= 属性用于表示如何解释 XML 文档的底层字节。如果字符串文字中的文档是正确的,则不需要具有 encoding 属性。如果有不正确的字符,那么 encoding 属性可以保留,因为它会通知 XML 转换这些字符原来是什么。

  2. UTF-8 是一种 Unicode 编码,但您将变量和文字作为 VARCHAR 数据,而不是 NVARCHAR(这还需要在字符串文字前加上大写字母-N)。通过使用 VARCHAR 而不使用 N 前缀,如果 XML 文档中的任何字符不适合您所在的任何数据库的默认排序规则表示的代码页执行此查询时,您可能已经丢失了那些字符(即使您可以在屏幕上看到它们,它们在 VARCHAR 变量中也不正确,或者如果您做了一个简单的 SELECT字面量).

  3. Windows(以及 .NET、SQL 服务器等)使用 UTF-16 Little Endian。 Í 字符 Latin Capital Letter I with Acute 存在于代码页 1252 UTF-16LE 中作为值 205(例如 SELECT ASCII('Í'), CHAR(205); ),这就是为什么它当您删除 encoding="utf-8" 以及为什么您没有 "lose" 那个字符时,可以通过将其放置在 VARCHAR 文字和变量中来工作。但是,如该链接页面所示,UTF-8 编码中的字节序列为 195、141(是的,两个字节)。意思是,如果该字符确实是 UTF-8 编码的,那么在放入 UTF-16LE 环境中时就不会显示为该字符。

    XML 转换查看该字符的字节值 205(单字节,因为它当前是 VARCHAR 数据)并尝试提供与 that[=121 等效的 UTF-16LE =] 序列是 UTF-8。除了 205 本身在 UTF-8 中不存在。因此,您需要添加下一个字符,该字符是大写字母-"A",其值为 65。虽然 UTF-8 中有两个字节序列,但其中 none 是 205、65。这就是您收到 illegal xml character 错误的原因。

  4. 由于屏幕上的文本必须是 UTF-16LE,如果源确实是 UTF-8,则必须将底层 UTF-8 字节序列转换为 UTF-16LE。 Í 的底层字节序列是 195, 141。因此我们可以通过执行以下操作从代码页 1252 的常规 ASCII 字符(因为这是当前 VARCHAR 数据)创建该序列:

    DECLARE @poit VARCHAR(100);
    SET @poit = '<?xml version="1.0" encoding="UTF-8"?><test>V'
                  + CHAR(195) + CHAR(141) + 'A</test>';
    SELECT CONVERT(XML, @poit);
    

    Returns:

    <test>VÍA</test>
    

    数据还在VARCHARencoding="utf-8"还在<xml>元素!

  5. 如果将数据保持为 VARCHAR,则仅 encoding= 值的以下更改有效:

    DECLARE @poit VARCHAR(100);
    SET @poit = '<?xml version="1.0" encoding="Windows-1252"?><test>VÍA</test>';
    SELECT CONVERT(XML, @poit);
    

    这假定源编码确实是 "Windows-1252",它是 Latin1_General 的 Microsoft 版本,它是 Latin1_General 归类的基础。

    但是,如果 "encoding" 与当前数据库默认排序规则的代码页相同,则无需指定任何 VARCHAR 数据。

  6. 最后,XML服务器中的SQL数据是UTF-16LE,与NCHARNVARCHAR(和NTEXT ,但没有人应该再使用它了)。

结论

  1. 在将 XML 用作字符串(而不是 VARCHAR)时使用 NVARCHAR(MAX) 数据类型。

  2. 对于没有任何改变的字符的字符串(即屏幕上的一切看起来都很完美),然后像您所做的那样简单地删除 encoding="utf-8"。没有必要用 UTF-16 替换它,因为值的本质是 NVARCHAR 变量或文字(即以大写 - N 为前缀的字符串) .


关于使用 VARCHAR(MAX) 而不是 XML 甚至 NVARCHAR(MAX) 来保存 space,请记住 XML 数据类型在内部进行了优化,使得元素和属性名称仅在字典中存储一次,因此几乎没有 XML 的完全写出的字符串版本那么多的开销。因此,虽然 XML 类型确实将字符串存储为 UTF-16LE,但 if XML 文档有很多重复元素 and/or 属性名称,然后使用 XML 类型实际上可能比使用 VARCHAR(MAX):

产生更小的占用空间
DECLARE @ElementBased XML;
SET @ElementBased = (
                     SELECT * FROM master.sys.all_columns FOR XML PATH('Row')
                    );

DECLARE @AttributeBased XML;
SET @AttributeBased = (
                       SELECT * FROM master.sys.all_columns FOR XML RAW('Row')
                      );

SELECT @ElementBased AS [ElementBasedXML],
       @AttributeBased AS [AttributeBasedXML],

       DATALENGTH(@ElementBased) AS [ElementBasedXmlBytes],
       DATALENGTH(CONVERT(VARCHAR(MAX), @ElementBased)) AS [ElementBasedVarCharBytes],
       ((DATALENGTH(@ElementBased) * 1.0) / DATALENGTH(CONVERT(VARCHAR(MAX), @ElementBased))
               ) * 100 AS [XmlElementSizeRelativeToVarcharElementSize],

       DATALENGTH(@AttributeBased) AS [AttributeBasedXmlBytes],
       DATALENGTH(CONVERT(VARCHAR(MAX), @AttributeBased)) AS [AttributeBasedVarCharBytes],
       ((DATALENGTH(@AttributeBased) * 1.0) /
         DATALENGTH(CONVERT(VARCHAR(MAX), @AttributeBased))) * 100
               AS [XmlAttributeSizeRelativeToVarCharAttributeSize];

Returns(至少在我的系统上):

ElementBasedXmlBytes                              1717896
ElementBasedVarCharBytes                          5889081
XmlElementSizeRelativeToVarcharElementSize        29.170867237180130482100

AttributeBasedXmlBytes                            1544661
AttributeBasedVarCharBytes                        3461864
XmlAttributeSizeRelativeToVarCharAttributeSize    44.619343798600984902900

如您所见,对于基于元素的 XML,XML 数据类型是 VARCHAR(MAX) 版本大小的 29%,对于基于属性的 XML,XML 数据类型是 VARCHAR(MAX) 版本大小的 44%。