XslCompiledTransform 忽略 .NET Framework 4.6 及更高版本上的 DTD 架构
XslCompiledTransform ignores DTD schema on .NET Framework 4.6 and above
目标是用 运行ning XSL t运行sformation 在 DTD 中指定的键(短 vs full/ref 标签名称)交换 XML 标签:
- XSL 确定 XML 是哪种格式:短格式或完整格式
- XSL 试图从 DTD
中为 XML 标签找到相反的键(short for full,full for short)
- XSL 用找到的相反键替换 XML 标签
所以 XML
<!DOCTYPE Note SYSTEM "note.dtd">
<Note>
<To>Tove</To>
<From>Jani</From>
<Heading>Reminder</Heading>
<Body>Don't forget me this weekend</Body>
</Note>
变成
<note>
<x1>Tove</x1>
<x2>Jani<x2/>
<x3>Reminder</x3>
<x4>Don't forget me this weekend</x4>
<note>
对于此任务,使用了以下 DTD(仅指定一个替换以保持其代表性)
<!ELEMENT Note (To,From,Heading,Body)>
<!ELEMENT To (#PCDATA)>
<!ATTLIST To
refname (To) #FIXED "To"
shortname (to) #FIXED "x1">
<!ELEMENT From (#PCDATA)>
<!ELEMENT Heading (#PCDATA)>
<!ELEMENT Body (#PCDATA)>
以下 XSL 正在 运行
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:variable name="target">
<xsl:choose>
<xsl:when test="local-name(/*)='Note'">short</xsl:when>
<xsl:otherwise>reference</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="*">
<xsl:variable name="target-name">
<xsl:choose>
<xsl:when test="($target='short') and not(@shortname)">
<xsl:value-of select="name()"/>
</xsl:when>
<xsl:when test="$target='short'">
<xsl:value-of select="@shortname"/>
</xsl:when>
<xsl:when test="not(@refname)">
<xsl:value-of select="name()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@refname"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$target-name}">
<xsl:copy-of select="@*[not(name()='refname' or name()='shortname')]"/>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="text()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
它在 .NET Framework 4.5(或 MSBuild 14)及更低版本中完美运行,但一旦代码 运行s 在更高版本上,则替换不会发生!
在这个问题上花了几个小时后,我可以说 DTD 在更高版本中被完全忽略了:我故意将错误的 DTP 路径放到 XML 顶部:
- .NET Framework 4.5 及更低版本 - 预期错误:
An error has occurred while opening external DTD 'file:///.../1note.dtd': Could not find file 'C:\...note.dtd'.
- .NET Framework 4.6 及更高版本 - 没有 t运行 信息,返回输入 XML。
我写了下面的 C# 代码来演示这个
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using Xunit;
public class Tests
{
private const string DtdDirectoryPath = @"c:\source\...\Resources";
private const string DtdFileName = "note.dtd";
private const string RootTag = "Note";
private string xml = @"
<Note>
<To>Tove</To>
<From>Jani</From>
<Heading>Reminder</Heading>
<Body>Don't forget me this weekend</Body>
</Note>";
[Fact]
public void Test()
{
// copy the DTD to the expected location
var dtdFilePath = Path.Combine(Environment.CurrentDirectory, DtdFileName);
File.Copy(Path.Combine(DtdDirectoryPath, DtdFileName), dtdFilePath, overwrite: true);
XslCompiledTransform xslt;
using (var stringReader = new StringReader(Resources.SwitchTagNamesXSL))
using (var xmlReader = new XmlTextReader(stringReader))
{
xslt = new XslCompiledTransform(enableDebug: true);
xslt.Load(xmlReader);
}
// add the DTD definition at the XML top in order the XSL transformation pass
this.xml = this.xml.Insert(
0,
new XDocumentType(RootTag, publicId: null, systemId: DtdFileName, internalSubset: null).ToString());
Console.WriteLine("Initial XML with DTD at the top");
Console.WriteLine(this.xml);
var output = new XDocument();
using (var writer = output.CreateWriter())
using (var stringReader = new StringReader(this.xml))
using (var reader = XmlReader.Create(
stringReader,
new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse })) // this flag is important to force DTD Processing
{
xslt.Transform(reader, writer);
}
Console.WriteLine("Output");
Console.WriteLine(output);
}
}
看起来该功能在最新的 .NET Framework 中以某种方式丢失了,作为解决方法我一直在考虑编写自定义代码
- XSL 脚本或
- 在 C#
中执行 运行 信息
但是我希望有人能知道一些关于不当行为的事情。
PS 在我低估 DTD 被完全忽略之前,我花了很多时间在谷歌上搜索这个问题,并做了很多没有发生的实验:
- 不同的测试框架:XUnin、NUnit
- 运行 在不同的机器上
- x64 x86
- 在 XslCompiledT运行sform
中启用脚本
- 为 XslCompiledT运行sform 设置不同的设置
我认为您的 XmlReaderSettings 需要明确定义一个 XmlResolver
,例如
new XmlReaderSettings() { DtdProcessing = DtdProcessing.Parse, XmlResolver = new XmlUrlResolver() }
The default is a new XmlUrlResolver with no credentials. Starting with
the .NET Framework 4.5.2, this setting has a default value of null.
目标是用 运行ning XSL t运行sformation 在 DTD 中指定的键(短 vs full/ref 标签名称)交换 XML 标签:
- XSL 确定 XML 是哪种格式:短格式或完整格式
- XSL 试图从 DTD 中为 XML 标签找到相反的键(short for full,full for short)
- XSL 用找到的相反键替换 XML 标签
所以 XML
<!DOCTYPE Note SYSTEM "note.dtd">
<Note>
<To>Tove</To>
<From>Jani</From>
<Heading>Reminder</Heading>
<Body>Don't forget me this weekend</Body>
</Note>
变成
<note>
<x1>Tove</x1>
<x2>Jani<x2/>
<x3>Reminder</x3>
<x4>Don't forget me this weekend</x4>
<note>
对于此任务,使用了以下 DTD(仅指定一个替换以保持其代表性)
<!ELEMENT Note (To,From,Heading,Body)>
<!ELEMENT To (#PCDATA)>
<!ATTLIST To
refname (To) #FIXED "To"
shortname (to) #FIXED "x1">
<!ELEMENT From (#PCDATA)>
<!ELEMENT Heading (#PCDATA)>
<!ELEMENT Body (#PCDATA)>
以下 XSL 正在 运行
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:variable name="target">
<xsl:choose>
<xsl:when test="local-name(/*)='Note'">short</xsl:when>
<xsl:otherwise>reference</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="*">
<xsl:variable name="target-name">
<xsl:choose>
<xsl:when test="($target='short') and not(@shortname)">
<xsl:value-of select="name()"/>
</xsl:when>
<xsl:when test="$target='short'">
<xsl:value-of select="@shortname"/>
</xsl:when>
<xsl:when test="not(@refname)">
<xsl:value-of select="name()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@refname"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$target-name}">
<xsl:copy-of select="@*[not(name()='refname' or name()='shortname')]"/>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="text()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
它在 .NET Framework 4.5(或 MSBuild 14)及更低版本中完美运行,但一旦代码 运行s 在更高版本上,则替换不会发生! 在这个问题上花了几个小时后,我可以说 DTD 在更高版本中被完全忽略了:我故意将错误的 DTP 路径放到 XML 顶部:
- .NET Framework 4.5 及更低版本 - 预期错误:
An error has occurred while opening external DTD 'file:///.../1note.dtd': Could not find file 'C:\...note.dtd'.
- .NET Framework 4.6 及更高版本 - 没有 t运行 信息,返回输入 XML。
我写了下面的 C# 代码来演示这个
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using Xunit;
public class Tests
{
private const string DtdDirectoryPath = @"c:\source\...\Resources";
private const string DtdFileName = "note.dtd";
private const string RootTag = "Note";
private string xml = @"
<Note>
<To>Tove</To>
<From>Jani</From>
<Heading>Reminder</Heading>
<Body>Don't forget me this weekend</Body>
</Note>";
[Fact]
public void Test()
{
// copy the DTD to the expected location
var dtdFilePath = Path.Combine(Environment.CurrentDirectory, DtdFileName);
File.Copy(Path.Combine(DtdDirectoryPath, DtdFileName), dtdFilePath, overwrite: true);
XslCompiledTransform xslt;
using (var stringReader = new StringReader(Resources.SwitchTagNamesXSL))
using (var xmlReader = new XmlTextReader(stringReader))
{
xslt = new XslCompiledTransform(enableDebug: true);
xslt.Load(xmlReader);
}
// add the DTD definition at the XML top in order the XSL transformation pass
this.xml = this.xml.Insert(
0,
new XDocumentType(RootTag, publicId: null, systemId: DtdFileName, internalSubset: null).ToString());
Console.WriteLine("Initial XML with DTD at the top");
Console.WriteLine(this.xml);
var output = new XDocument();
using (var writer = output.CreateWriter())
using (var stringReader = new StringReader(this.xml))
using (var reader = XmlReader.Create(
stringReader,
new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse })) // this flag is important to force DTD Processing
{
xslt.Transform(reader, writer);
}
Console.WriteLine("Output");
Console.WriteLine(output);
}
}
看起来该功能在最新的 .NET Framework 中以某种方式丢失了,作为解决方法我一直在考虑编写自定义代码
- XSL 脚本或
- 在 C# 中执行 运行 信息
但是我希望有人能知道一些关于不当行为的事情。
PS 在我低估 DTD 被完全忽略之前,我花了很多时间在谷歌上搜索这个问题,并做了很多没有发生的实验:
- 不同的测试框架:XUnin、NUnit
- 运行 在不同的机器上
- x64 x86
- 在 XslCompiledT运行sform 中启用脚本
- 为 XslCompiledT运行sform 设置不同的设置
我认为您的 XmlReaderSettings 需要明确定义一个 XmlResolver
,例如
new XmlReaderSettings() { DtdProcessing = DtdProcessing.Parse, XmlResolver = new XmlUrlResolver() }
The default is a new XmlUrlResolver with no credentials. Starting with the .NET Framework 4.5.2, this setting has a default value of null.