【问题标题】:Why does en-dash (–) trigger illegal XML character error (C#/SSMS)?为什么破折号 (–) 会触发非法 XML 字符错误 (C#/SSMS)?
【发布时间】:2025-12-04 23:55:01
【问题描述】:

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

我们正在使用 C# 调用基于 Java 的 Web 服务。根据返回的强类型数据,我们正在创建一个将传递给 SQL Server 的 XML 文件。网络服务数据使用 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 会导致错误呢?从我的研究看来,

...只有少数需要转义的实体:,\,' 和 & 在 HTML 和 XML。 Source

...其中的破折号不是一个。编码版本(将 替换为&amp;#8211;)工作正常。

更新

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

【问题讨论】:

  • 在省略 XML 声明并仅提供 declare @xml XML = '&lt;records&gt; &lt;r RecordName="Option - Foo" /&gt; &lt;r RecordName="Option – Bar" /&gt; &lt;/records&gt;'; 时,您是否得到相同的 SQL 错误?
  • 省略声明时有效。但是-这种不好的做法不会导致其他问题吗?它不是我所追求的修复,而是原因。
  • 或尝试 encoding="utf-16" .NET 可能不是问题。它是 MSSQL。
  • 这是为什么.....*.com/questions/3760788/…的背景

标签: c# sql-server xml tsql


【解决方案1】:

请允许我回答我自己的问题,以便我自己完全理解它。我不会接受这个作为答案;正是其他答案的结合将我引到这里。如果此答案对您将来有帮助,请也为其他帖子点赞。

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

原问题的原因

此变量以 UTF-8 编码声明 XML,但如果不以 UTF-8 编码,则无法使用实体破折号。这是错误的:

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>';

【讨论】:

    【解决方案2】:

    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 字符只是一大块,简单易行……

    其他字符也可以编码。有“c2”和“c3”代码(look here)。 c3-codes 需要三个块进行编码。但内部使用的 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 位字符都是“普通”字符,应该毫无问题地编码。 “扩展 ASCII”取决于代码表并且可能会有所不同。 150 可能是破折号或其他东西。 UTF8 使用一些棘手的编码来允许奇怪的字符是“合法的”。显然(这对我来说也是新的)内部使用的 UTF16 无法处理 c3 字符。

    【讨论】:

    • 是的,我认为这是一种解决方法,但问题是为什么会发生这种行为。请查看相关更新了解更多信息。
    • 对于 UTF8,它应该在此处被称为 UTF8 字符:fileformat.info/info/unicode/char/2013/index.htm。这一切对我来说都很奇怪......:-/
    • @EvilDr 您在哪里看到它是合法的 UTF8 字符?据我所知,您的链接列表 转义序列 用于不同的编码。这0xE2 0x80 0x93 (e28093) 不是清楚地表明它不是合法的UTF8 字符吗?
    • utf-8 不是 unicode 的 子集,破折号在 utf-8 中是 3 个字节,这非常好,因为 utf-8 是可变长度编码。然而,它在 utf-16 中有 2 个字节,这可能是字符串文字和 xml 解析器的编码存在问题的原因。
    • @EvilDr 没问题,编码愉快!
    【解决方案3】:

    你能修改 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>'
    

    【讨论】:

    • 不,它会抛出一个关于无法切换编码类型的错误,这是预期的。
    • 是的。我已经知道这一点,它是一个方便的解决方法,问题是为什么会发生这种情况?
    • 请查看问题更新。如果它是一个有效的 UTF8 字符,为什么它会触发 SSMS?
    • 更新了答案
    【解决方案4】:

    MSDN guidelines 说:

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

    【讨论】:

    • 不确定我是否理解。请在这里代表我采取什么行动?我需要定义 DTD 吗?
    • @EvilDr:- 是的,您可以尝试一下,否则解决方法是使用 N 使其成为 unicode,正如 Alex 指出的那样。
    • 这种类型的东西有标准 DTD吗? .Net 当然不会为我生成它
    • @EvilDr:- 我认为不存在或者可能我不知道。
    最近更新 更多