这不是关于如何克服“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文件:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
<r RecordName="Option - Foo" />
<r RecordName="Option – Bar" />
</records>
请注意,在第二条记录中,-
与–
不同。我相信第二个例子是en-dash。
如果我在Firefox / IE / VS2015中打开该XML文件。它打开没有错误。 W3C XML validator也可以。但是,SSMS 2012不喜欢它:
declare @xml XML = '<?xml version="1.0" encoding="utf-8" standalone="yes"?><records>
<r RecordName="Option - Foo" />
<r RecordName="Option – Bar" />
</records>';
XML解析:第3行,第25个字符,非法xml字符
那么为什么en-dash会导致错误呢?从我的研究来看,似乎是
...只有少数几个需要转义的实体:&lt;,&gt;,\,'和&amp;在HTML和 XML。 Source
......其中en-dash不是一个。编码版本(用–
替换–
)可以正常工作。
根据输入,人们声明en-dash未被识别为UTF-8,但它在此列出http://www.fileformat.info/info/unicode/char/2013/index.htm 那么,作为一个完全合法的角色,为什么SSMS在以XML格式传递时不会读取它(使用UTF-8或UTF-16)?
答案 0 :(得分: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>';
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>';
XML解析:第1行,第56个字符,无法切换编码
有效的替代方案是:
1)保留为UTF-8,但在实体(reference)上使用十六进制编码:
DECLARE @xml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
<r RecordName="Option – Bar" />
</records>';
2)如上所述,但在实体(reference)上使用十进制编码:
DECLARE @xml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
<r RecordName="Option – Bar" />
</records>';
3)包括原始实体,但在声明中删除UTF-8编码(SSMS然后应用UTF-16;默认值):
DECLARE @xml xml = '<?xml version="1.0" standalone="yes"?>
<records>
<r RecordName="Option – Bar" />
</records>';
4)保留UTF-16声明,但将XML转换为Unicode(在转换为XML之前请注意前面的N
):
DECLARE @xml xml = N'<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<records>
<r RecordName="Option – Bar" />
</records>';
答案 1 :(得分: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>
这两个都失败了 非法xml字符 :
set @xml = '<?xml version="1.0" encoding="utf-8"?><x> – </x>'
set @xml = '<?xml version="1.0" encoding="utf-16"?><x> – </x>'
因为它们将非unicode varchar
传递给XML解析器;字符串包含Unicode,因此必须这样处理,即作为nvarchar
(utf-16)(否则包含–
的3个字节被误解为多个字符,并且一个或多个不在可接受的范围内XML的范围)
这会将nvarchar
字符串传递给解析器,
但由于 无法切换编码 :
set @xml = N'<?xml version="1.0" encoding="utf-8"?><x> – </x>'
这是因为nvarchar
(utf-16)字符串被传递给XML解析器,但XML文档声明它的utf-8并且–
在两种编码中不相同
一切都是utf-16
set @xml = N'<?xml version="1.0" encoding="utf-16"?><x> – </x>'
答案 2 :(得分:4)
SQL Sever内部使用UTF-16。让编码消失或转换为unicode
您正在寻找的原因:指定UTF-8时,此字符未知。
--without your directive, SQL Server picks its default
declare @xml XML =
'<records>
<r RecordName="Option - Foo" />
<r RecordName="Option – Bar" />
</records>';
select @xml;
--or UNICODE, but you must use UTF-16
declare @xml2 XML =
CAST('<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<records>
<r RecordName="Option - Foo" />
<r RecordName="Option – Bar" />
</records>' AS NVARCHAR(MAX));
select @xml2
UTF-8意味着有8位用于传输信息的块。 base 字符只是一个块,很容易...
其他字符也可以进行编码。有&#34; c2&#34;和&#34; c3&#34;代码(look here)。 c3代码需要三个块进行编码。但内部使用的UTF16需要2个字节的编码字符。
希望现在很清楚......
此代码将向您显示,连字符具有ASCII码45和您的短划线150:
DECLARE @x VARCHAR(100)=
'<r RecordName="Option - Foo" /><r RecordName="Option – Bar" />';
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;
看看here所有7位字符都是&#34;普通&#34;并应编码没有问题。 &#34;扩展的ASCII&#34;取决于代码表,可能会有所不同。 150可能是冲刺或其他东西。 UTF8使用一些棘手的编码来允许奇怪的字符是&#34;合法&#34;。显然(这对我来说也是新的)内部使用的UTF16无法处理c3字符。
答案 3 :(得分:2)
SQLXML 4.0依赖于SQL中提供的对DTD的有限支持 服务器。 SQL Server允许在xml数据类型数据中使用内部DTD, 可用于提供默认值和替换实体 引用及其扩展内容。 SQLXML传递XML数据 &#34;原样&#34; (包括内部DTD)到服务器。你可以转换 使用第三方工具和加载DTD到XML Schema(XSD)文档 具有内联XSD架构的数据进入数据库。