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

Evi*_*lDr 8 c# xml t-sql sql-server

这不是关于如何克服"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);
Run Code Online (Sandbox Code Playgroud)

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

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
  <r RecordName="Option - Foo" />
  <r RecordName="Option – Bar" />
</records>
Run Code Online (Sandbox Code Playgroud)

请注意,在第二条记录中,-与...不同.我相信第二个例子是冲刺.

如果我在Firefox/IE/VS2015中打开该XML文件.它打开没有错误.在W3C XML验证程序也能正常工作.但是,SSMS 2012不喜欢它:

declare @xml XML = '<?xml version="1.0" encoding="utf-8" standalone="yes"?><records>
  <r RecordName="Option - Foo" />
  <r RecordName="Option – Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

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

...只需要转义的几个实体:<,>,\,'和&HTML和XML. 资源

...其中en-dash不是一个.编码版本(替换&#8211;)工作正常.

UPDATE

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

Evi*_*lDr 6

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

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

原始问题的原因

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

DECLARE @badxml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
  <r RecordName="Option – Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

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

DECLARE @xml xml = '<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<records>
  <r RecordName="Option – Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

解决方案

有效的替代方案是:

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

DECLARE @xml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
  <r RecordName="Option &#x2013; Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

DECLARE @xml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
  <r RecordName="Option &#8211; Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

DECLARE @xml xml = '<?xml version="1.0" standalone="yes"?>
<records>
  <r RecordName="Option – Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)

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

DECLARE @xml xml = N'<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<records>
  <r RecordName="Option – Bar" />
</records>';
Run Code Online (Sandbox Code Playgroud)


Ale*_* K. 5

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

declare @xml XML = N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><records>
  <r RecordName="Option - Foo" />
  <r RecordName="Option – Bar" />
</records>';

select @xml

(No column name)
<records><r RecordName="Option - Foo" /><r RecordName="Option – Bar" /></records>
Run Code Online (Sandbox Code Playgroud)

推测编辑

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

set @xml = '<?xml version="1.0" encoding="utf-8"?><x> – </x>'
set @xml = '<?xml version="1.0" encoding="utf-16"?><x> – </x>'
Run Code Online (Sandbox Code Playgroud)

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

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

set @xml = N'<?xml version="1.0" encoding="utf-8"?><x> – </x>'
Run Code Online (Sandbox Code Playgroud)

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

这一切都有效,因为一切都是utf-16

set @xml = N'<?xml version="1.0" encoding="utf-16"?><x> – </x>'
Run Code Online (Sandbox Code Playgroud)


Shn*_*ugo 5

SQL Sever 内部使用 UTF-16。要么放弃编码,要么转换为 unicode

\n\n

您查找的原因:指定UTF-8时,该字符是未知的。

\n\n
--without your directive, SQL Server picks its default\ndeclare @xml XML = \n\'<records>\n  <r RecordName="Option - Foo" />\n  <r RecordName="Option \xe2\x80\x93 Bar" />\n</records>\';\nselect @xml;\n\n--or UNICODE, but you must use UTF-16\ndeclare @xml2 XML = \nCAST(\'<?xml version="1.0" encoding="utf-16" standalone="yes"?>\n<records>\n  <r RecordName="Option - Foo" />\n  <r RecordName="Option \xe2\x80\x93 Bar" />\n</records>\' AS NVARCHAR(MAX));\n\nselect @xml2\n
Run Code Online (Sandbox Code Playgroud)\n\n

更新

\n\n

UTF-8 意味着有 8 位块用于携带信息。基本角色只是一大块,很容易相处......

\n\n

其他字符也可以被编码。有“c2”和“c3”代码(请参见此处)。c3 代码需要三个块进行编码。但内部使用的 UTF16 需要 2 字节编码字符。

\n\n

希望现在已经清楚了......

\n\n

更新2

\n\n

此代码将向您显示连字符的 ASCII 代码为 45,连字符的 ASCII 代码为 150:

\n\n
DECLARE @x VARCHAR(100)=\n\'<r RecordName="Option - Foo" /><r RecordName="Option \xe2\x80\x93 Bar" />\';\n\nWITH RunningNumbers AS\n(\n    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr\n    FROM sys.objects\n)\nSELECT SUBSTRING(@x,Nmbr,1), ASCII(SUBSTRING(@x,Nmbr,1)) AS ASCII_Code\nFROM RunningNumbers\nWHERE ASCII(SUBSTRING(@x,Nmbr,1)) IS NOT NULL;\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n