将varchar()中的重音字符转换为XML,导致非法的XML字符"

时间:2015-07-07 16:38:34

标签: sql-server xml unicode character-encoding type-conversion

我有一个应用程序写的表。该字段是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

有没有办法彻底转换它?

我找到了这个线程,它讨论了varchar不支持&#34;非ASCII字符&#34;,但显然我是非unicode。是的,我可以这样做:

SELECT CONVERT(XML,REPLACE(@ poit,&#39; encoding =&#34; utf-8&#34;&#39;,&#39;&#39;)

但这是最好的方法吗?

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

2 个答案:

答案 0 :(得分:3)

&lt; TL; DR&gt; 如果您只是想要答案而没有完整的解释,请向下滚动到“结论”。但是,你真的应该花点时间阅读解释?&lt; / TL; DR&gt;

这里有一些事情发生:

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

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

  3. Windows(以及.NET,SQL Server等)使用UTF-16 Little Endian。代码页<12> UTF-16LE中的SELECT字符Latin Capital Letter I with Acute作为值205(例如Í)存在,这就是为什么它适用于您的原因移除SELECT ASCII('Í'), CHAR(205);以及为什么您没有将该字符放在encoding="utf-8"文字和变量中“丢失”该字符。但是,如该链接页面所示,UTF-8编码中的字节序列是195,141(是的,两个字节)。这意味着,如果这个字符真的是UTF-8编码的话,放在UTF-16LE环境中就不会出现这个字符。

    XML转换查看该字符的字节值205(单字节,因为它当前是VARCHAR数据),并尝试提供UTF-16LE等效于 序列的UTF-8。除了205本身在UTF-8中不存在。所以你需要添加下一个字符,它是一个大写字母 - “A”,其值为65.虽然UTF-8中有两个字节的序列,但它们都不是205,65。这就是为什么你得到的VARCHAR错误。

  4. 由于屏幕上的文本必须是UTF-16LE,如果源真的是UTF-8,那么底层的UTF-8字节序列必须转换为UTF-16LE。 illegal xml character的基础字节序列是195,141。因此,我们可以通过执行以下操作,从Code Page 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);
    

    数据仍为<test>VÍA</test> VARCHAR仍在encoding="utf-8"元素中!

  5. 如果将数据保留为<xml>,则仅VARCHAR值的以下更改有效:

    encoding=

    这假设源编码确实是“Windows-1252”,这是Microsoft的Latin1_General版本,它是Latin1_General排序规则的基础。

    但是,如果它与当前数据库的默认排序规则的代码页相同,则无需指定“编码”,因为假设任何VARCHAR数据都是如此。

  6. 最后,SQL Server中的DECLARE @poit VARCHAR(100); SET @poit = '<?xml version="1.0" encoding="Windows-1252"?><test>VÍA</test>'; SELECT CONVERT(XML, @poit); 数据是UTF-16LE,与XMLNCHAR(以及NVARCHAR相同,但是没有人应该再使用它了)。

  7. 结论

    1. 使用XML作为字符串(而非NTEXT)时,请使用NVARCHAR(MAX)的数据类型。

    2. 对于没有任何改变字符的字符串(即屏幕上的所有内容看起来都很完美),只需删除VARCHAR即可。没有必要用encoding="utf-8"替换它,因为UTF-16变量或文字中的值的本质(即以大写字母为前缀的字符串 - NVARCHAR

    3. 关于使用N代替VARCHAR(MAX)甚至XML以节省空间,请注意NVARCHAR(MAX)数据类型是内部优化的,以便元素和属性名称只在字典中存储一次,因此没有完全写出的XML字符串版本那么多的开销。因此,虽然XML类型将字符串存储为UTF-16LE,但 如果 ,XML文档具有大量重复的元素和/或属性名称,那么使用XML类型实际上可能导致占用空间小于使用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];
      

      如您所见,对于基于元素的XML,ElementBasedXmlBytes 1717896 ElementBasedVarCharBytes 5889081 XmlElementSizeRelativeToVarcharElementSize 29.170867237180130482100 AttributeBasedXmlBytes 1544661 AttributeBasedVarCharBytes 3461864 XmlAttributeSizeRelativeToVarCharAttributeSize 44.619343798600984902900 数据类型的大小是XML版本的29%,对于基于属性的XML,VARCHAR(MAX)数据类型是XML版本的44%。

答案 1 :(得分:1)

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

    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中使用替换调用转换,性能应该没问题,它将完成工作。

参考:http://xml.silmaril.ie/characters.html&lt; - 向下滚动,您会看到有关utf-8和&amp; UTF-16。希望这有帮助!