为什么en-dash( – )会触发非法的XML字符错误(C#/ SSMS)?

这不是关于如何克服“XML解析:…非法xml字符”错误的问题,而是关于它为什么会发生的问题? 我知道有修复( 1,2,3 ),但在选择最佳解决方案之前需要知道问题出在哪里(导致错误的原因是什么?)。

我们使用C#调用基于Java的Web服务。 从返回的强类型数据中,我们创建了一个将传递给SQL Server的XML文件。 Web服务数据使用UTF-8进行编码,因此在C#中我们创建文件,并在适当的地方指定UTF-8:

var encodingType = Encoding.UTF8; // logic removed... var xdoc = new XDocument(); xdoc.Declaration = new XDeclaration("1.0", encodingType.WebName, "yes"); // logic removed... System.IO.File.WriteAllText(xmlFullPath, xdoc.Declaration.ToString() + xdoc.Document.ToString(), encodingType); 

这将在磁盘上创建一个包含以下(缩写)数据的XML文件:

      

请注意,在第二条记录中, -不同。 我相信第二个实例是en-dash 。

如果我在Firefox / IE / VS2015中打开该XML文件。 它打开没有错误。 W3C XMLvalidation器也可以正常工作。 但是,SSMS 2012不喜欢它:

 declare @xml XML = '   '; 

XML解析:第3行,第25个字符,非法xml字符

那么为什么en-dash会导致错误呢? 从我的研究来看,似乎是这样

…只需要转义的几个实体:,\,’和&HTML和XML。 资源

…其中en-dash不是一个。 编码版本(替换 )工作正常。

UPDATE

根据输入,人们声明en-dash不被识别为UTF-8,但它在此列出http://www.fileformat.info/info/unicode/char/2013/index.htm所以,as as as一个完全合法的角色,为什么SSMS在以XML格式传递时不会读取它(使用UTF-8或UTF-16)?

你能修改XML编码声明吗? 如果是这样;

 declare @xml XML = N'   '; select @xml (No column name)  

推测编辑

这两个都失败了非法的xml字符

 set @xml = '' set @xml = '' 

因为它们将非unicode varchar传递给XML解析器; 字符串包含Unicode,因此必须这样处理,即作为nvarchar (utf-16)(否则包含的3个字节被误解为多个字符,并且一个或多个不在XML的可接受范围内)

这确实将nvarchar字符串传递给解析器,但由于无法切换编码而失败:

 set @xml = N'' 

这是因为nvarchar (utf-16)字符串被传递给XML解析器,但是XML文档声明它的utf-8并且在两个编码中不相同

一切都是utf-16

 set @xml = N'' 

请允许我回答我自己的问题,以便我自己完全理解。 我不接受这个作为答案; 这是导致我在这里的其他答案的组合。 如果这个答案在将来对您有所帮助,请同时推荐其他post。

基本的基本规则是带有Unicode字符的XML应该由SQL Server传递给Unicode并进行解析 。 因此C#应该生成XML为UTF-16; SSMS和.Net默认。

原始问题的原因

此变量使用UTF-8编码声明XML,但如果没有以UTF-8编码,则无法使用实体en-dash。 这是错的:

 DECLARE @badxml xml = '   '; 

XML解析:第3行,第29个字符,非法xml字符

另一种不起作用的方法是在XML中将UTF-8切换为UTF-16。 这里的字符串不是unicode,因此隐式转换失败:

 DECLARE @xml xml = '   '; 

XML解析:第1行,字符56,无法切换编码

解决方案

有效的替代方案是:

1)保留为UTF-8但在实体上使用hex编码( 参考 ):

 DECLARE @xml xml = '   '; 

2)如上所述,但在实体上使用十进制编码( 参考 ):

 DECLARE @xml xml = '   '; 

3)包含原始实体,但在声明中删除UTF-8编码(SSMS然后应用UTF-16;默认值):

 DECLARE @xml xml = '   '; 

4)保留UTF-16声明,但将XML转换为Unicode(注意前面的N在转换为XML之前):

 DECLARE @xml xml = N'   '; 

SQL Sever内部使用UTF-16。 让编码消失或转换为unicode

您正在寻找的原因:指定UTF-8时,此字符未知。

 --without your directive, SQL Server picks its default declare @xml XML = '   '; select @xml; --or UNICODE, but you must use UTF-16 declare @xml2 XML = CAST('    ' AS NVARCHAR(MAX)); select @xml2 

UPDATE

UTF-8意味着有8位用于传输信息的块。 基本字符只是一个块,很容易…

其他字符可以编码。 有“c2”和“c3”代码( 看这里 )。 c3代码需要三个块进行编码。 但内部使用的UTF16需要2个字节的编码字符。

希望现在很清楚……

更新2

此代码将向您显示,Hyphen具有ASCII代码45和您的en-dash 150:

 DECLARE @x VARCHAR(100)= ''; WITH RunningNumbers AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM sys.objects ) SELECT SUBSTRING(@x,Nmbr,1), ASCII(SUBSTRING(@x,Nmbr,1)) AS ASCII_Code FROM RunningNumbers WHERE ASCII(SUBSTRING(@x,Nmbr,1)) IS NOT NULL; 

看看这里 7位的所有字符都是“普通的”,应编码没有问题。 “扩展ASCII”取决于代码表,可能会有所不同。 150可能是冲刺或其他东西。 UTF8使用一些棘手的编码来允许奇怪的字符是“合法的”。 显然(这对我来说也是新的)内部使用的UTF16无法处理c3字符。

MSDN指南说:

SQLXML 4.0依赖于对SQL Server中提供的DTD的有限支持。 SQL Server允许在xml数据类型数据中使用内部DTD,该数据可用于提供默认值并将实体引用替换为其扩展内容。 SQLXML“按原样”(包括内部DTD)将XML数据传递给服务器。 您可以使用第三方工具将DTD转换为XML架构(XSD)文档,并使用内联XSD架构将数据加载到数据库中。