为什么将UTF-8 VARCHAR列转换为XML需要转换为NVARCHAR和编码更改?

时间:2014-08-05 11:20:13

标签: sql sql-server xml encoding utf-8

我正在尝试将varchar列中的数据转换为XML,但我遇到了某些字符的错误。跑这个......

-- This fails
DECLARE @Data VARCHAR(1000) = '<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>';
SELECT CAST(@Data AS XML) AS DataXml

...导致以下错误

  

Msg 9420,Level 16,State 1,Line 3
  XML解析:第1行,第55个字符,非法xml字符

看起来它是导致错误的破坏管道字符,但我认为它是UTF-8的有效字符。查看XML spec它似乎有效。

当我把它改成这个......

-- This works
DECLARE @Data VARCHAR(1000) = '<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>';
SELECT CAST(REPLACE(CAST(@Data AS NVARCHAR(MAX)), 'encoding="utf-8"', '') AS XML) AS DataXml

...它没有错误(将编码字符串替换为utf-16也有效)。我正在使用带有SQL_Latin1_General_CP1_CI_AS Coallation的SQL Server 2008 R2。

任何人都可以告诉我为什么我需要转换为NVARCHAR并剥离encoding="utf-8"才能生效吗?

谢谢,

修改

看来这也有效......

DECLARE @Data VARCHAR(1000) = '<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>';
SELECT CAST(REPLACE(@Data, 'encoding="utf-8"', '') AS XML) AS DataXml

从prolog中删除utf-8编码足以让SQL Server进行转换。

2 个答案:

答案 0 :(得分:3)

您的管道字符使用Unicode代码点U+00A6 BROKEN BAR而不是U+007C VERTICAL LINEU+00A6超出ASCII。 VARCHAR不支持非ASCII字符。这就是为什么你必须使用NVARCHAR代替,它是为处理Unicode数据而设计的。

答案 1 :(得分:0)

不幸的是,所接受的答案是103.3333333%错误。 VARCHAR绝对支持扩展ASCII。标准ASCII只是前128个值(0x00-0x7F)。对于SQL Server中的所有代码页(即8位VARCHAR数据)和UTF-16(即16位NVARCHAR数据),情况恰好相同。扩展ASCII覆盖256个总值(0x80-0xFF)中的剩余128个。每个代码页的128个值/代码点不同,尽管其中一些之间有很多重叠。

接受的答案指出VARCHAR不支持U+00A6 BROKEN BAR。只需在第一行之后添加SELECT @Data;即可轻松证明这一点:

DECLARE @Data VARCHAR(1000) =
                 '<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>';
SELECT @Data;

返回:

<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>

明确支持¦字符,因此问题可能出在其他地方。


  

似乎是导致错误的竖线字符,但我认为这是UTF-8的有效字符。

破折号是UTF-8中的有效字符。问题是:您没有传递UTF-8数据。是的,您声明xml声明中的编码为UTF-8,但这并不意味着数据 UTF-8,它只是将期望值设置为UTF-8

您正在将VARCHAR文字转换为XML。数据库的默认归类为SQL_Latin1_General_CP1_CI_AS,它使用Windows-1252代码页存储VARCHAR数据。这意味着折断的竖线字符的值为166或0xA6。好吧,0xA6不是有效的UTF-8编码的任何东西。如果您确实要传递UTF-8编码的数据,那么折断的竖线字符将是两个字节:0xC2和0xA6。如果将0xC2字节添加到原始输入值(0xA6相同,那么我们可以将其保留在原来的位置),我们得到:

DECLARE @Data VARCHAR(1000) = '<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test'
                              + CHAR(0xC2) + '¦</NewDataSet>';
SELECT @Data AS [@Data];
SELECT CAST(@Data AS XML) AS [DataXml];

并返回:

<?xml version="1.0" encoding="utf-8"?><NewDataSet>Test¦</NewDataSet>

其次:

<NewDataSet>Test¦</NewDataSet>

这就是为什么删除encoding="utf-8"可以解决问题的原因:

  1. 在其中,该字符串的字节实际上需要为UTF-8,但实际上不是,并且...
  2. 将其删除后,假定编码与字符串本身相同,即VARCHAR,这意味着编码是与字符串的排序规则关联的代码页,以及{{1 }}文字或变量使用数据库的默认排序规则。意味着,在这种情况下,如果没有VARCHAR或带有encoding="xxxxxx",则字节将需要编码为Windows-1252,而实际上它们是。 >

将所有内容放在一起,我们得到:

  1. 如果您有实际的UTF-8编码字符串,则可以将其 传递到XML数据类型中,但是您需要:

    1. no 大写的“ N”作为字符串文字的前缀,并且没有encoding="Windows-1252"变量或列用于包含字符串
    2. XML声明,说明编码为UTF-8
  2. 如果您在代码页中编码的字符串与数据库的默认排序规则相关联,则您需要:

    1. no 大写的“ N”作为字符串文字的前缀,并且没有NVARCHAR变量或列用于包含字符串
    2. 没有NVARCHAR声明的一部分进行“编码”,或者没有将编码设置为与数据库的默认排序规则关联的代码页(例如,代码页1252的<?xml ?>
  3. 如果您的字符串已经是Unicode,则需要:

    1. 以大写的“ N”作为前缀的字符串文字,或者对传入的XML使用Windows-1252变量或列
    2. NVARCHAR声明中没有“编码”,或者编码设置为“ utf-16”

有关此详细信息,请参见我对“ Converting accented characters in varchar() to XML causing “illegal XML character””的回答。

有关归类,字符编码等的信息,请访问:Collations Info