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

时间:2016-04-04 12:14:48

标签: c# sql-server xml tsql

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

我们使用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不是一个。编码版本(用替换&#8211;)可以正常工作。

更新

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

4 个答案:

答案 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 &#x2013; Bar" />
</records>';

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

DECLARE @xml xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<records>
  <r RecordName="Option &#8211; 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个字节的编码字符。

希望现在很清楚......

更新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)

MSDN guidelines说:

  

SQLXML 4.0依赖于SQL中提供的对DTD的有限支持   服务器。 SQL Server允许在xml数据类型数据中使用内部DTD,   可用于提供默认值和替换实体   引用及其扩展内容。 SQLXML传递XML数据   &#34;原样&#34; (包括内部DTD)到服务器。你可以转换   使用第三方工具和加载DTD到XML Schema(XSD)文档   具有内联XSD架构的数据进入数据库。