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不是一个.编码版本(替换–为–)工作正常.
根据输入,人们声明en-dash不被识别为UTF-8,但它在此列出http://www.fileformat.info/info/unicode/char/2013/index.htm 所以,as as一个完全合法的字符,为什么SSMS在以XML格式传递时不会读取它(使用UTF-8或UTF-16)?
请允许我回答我自己的问题,以便我自己完全理解.我不接受这个作为答案; 这是引导我到这里的其他答案的组合.如果这个答案将来会对您有所帮助,请同时推荐其他帖子.
基本的基本规则是带有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 – 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 – 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)
你能修改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)
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\nRun Code Online (Sandbox Code Playgroud)\n\nUTF-8 意味着有 8 位块用于携带信息。基本角色只是一大块,很容易相处......
\n\n其他字符也可以被编码。有“c2”和“c3”代码(请参见此处)。c3 代码需要三个块进行编码。但内部使用的 UTF16 需要 2 字节编码字符。
\n\n希望现在已经清楚了......
\n\n此代码将向您显示连字符的 ASCII 代码为 45,连字符的 ASCII 代码为 150:
\n\nDECLARE @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;\nRun Code Online (Sandbox Code Playgroud)\n\n看看这里所有 7 位字符都是“普通”字符,编码应该没有问题。“扩展 ASCII”取决于代码表并且可能会有所不同。150 可能是短破折号或其他内容。UTF8 使用一些棘手的编码来允许奇怪的字符“合法”。显然(这对我来说也是新的)内部使用的 UTF16 无法处理 c3 字符。
\n