如何在运行时向XslCompiledTransform添加任意名称空间?

本文关键字:任意名 空间 添加 XslCompiledTransform 运行时 | 更新日期: 2023-09-27 18:12:17

我试图对定时文本标记语言(TTML)文档执行一个非常简单的转换。下面是一个极简的html文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<tt xml:lang="en" xmlns="http://www.w3.org/2006/04/ttaf1"
    xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling">
    <head>
    </head>
    <body>
        <div xml:lang="en" style="1">
            <p begin="00:00:00.20" dur="00:00:02.26">&gt;&gt; One time entry<br/>with a line break.</p>
        </div>
    </body>
</tt>

注意文档的默认命名空间。这就是我遇到的问题的关键。

这是我使用的变换。这就是全部内容,非常简单。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
    xmlns:tt="http://www.w3.org/2006/04/ttaf1">
    <xsl:output method="text" indent="yes" />
    <xsl:strip-space elements="*" />
    <xsl:preserve-space elements="tt:p"/>
    <!-- The indentation of the close tag for the following node is crucial to the transformed layout. Don't move it! -->
    <xsl:template match="tt:p"><xsl:apply-templates />&#160;
</xsl:template>
    <xsl:template match="tt:p/text()"><xsl:copy />&#160;</xsl:template>
</xsl:stylesheet>

我们的数据集有数百个文档,它们并不都具有相同的默认名称空间。但是,从上面的XSLT中可以看到,转换很简单,并且基于核心数据元素<p />,因此最终名称空间的变化并不重要。

由于XSLT文件具有显式定义的名称空间前缀(tt),因此上面的XSL目前处理一些源文件。但是,当遇到具有不同默认命名空间的另一个源文件时,这不起作用。

因此,我需要找到一种方法,为已知前缀向XSLT提供任意的未知名称空间值。我有可以用来查找源文档默认名称空间的代码:

XPathDocument sourceDoc = new XPathDocument("sourcefile.xml");
XPathNavigator sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string defNS = sourceNS.Single(n => n.Key =="").Value;

这正确地给了我http://www.w3.org/2006/04/ttaf1。然而,我似乎不知道该怎么处理这个值。我已经搜索和实验了许多小时,试图以某种方式为XslCompiledTransform实例提供变量名称空间。看起来在变换本身的区域内没有任何东西可以带走它。

我已经尝试显式地将XSLT文件加载到XmlDocument中,以便操作名称表(在上述XSLT中删除显式的xmlns:tt="..."名称空间声明之后):

XslCompiledTransform objXsl = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XPathDocument sourceDoc;
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav, xslNav;
XmlNamespaceManager xslNsManager;
sourceDoc = new XPathDocument("sourcefile.xml");
sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
xslDoc.Load("transform.xslt");
xslNsManager = new XmlNamespaceManager(xslDoc.NameTable);
xslNsManager.AddNamespace("tt", sourceNS.Single(n => n.Key =="").Value);
xslNav = xslDoc.CreateNavigator();
objXsl.Load(xslNav);
objXsl.Transform(sourceNav, null, writer);

这一直运行到实际的转换,在那里我得到一个XslLoadException,说明Prefix 'tt' is not defined.

在这一点上我很茫然。我从搜索中找到的所有内容都讨论了将名称空间放入XSLT文档(我已经有了这个文档,并且它可以很好地用于一个名称空间值)。我在MSDN文档或其他地方找不到任何解释如何动态地将命名空间定义添加到转换中的内容。

有人有什么想法吗?

如何在运行时向XslCompiledTransform添加任意名称空间?

一位同事建议我只在XSL文档中添加/操作变量名称空间。我使用名称表更改的尝试朝着正确的方向前进,但它不起作用。以下是我根据他的建议想出的办法:

XslCompiledTransform xslXform = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav;
sourceNav = new XPathDocument(sourceFile).CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string ttNamespace = sourceNS.Single(n => n.Key == "").Value;
xslDoc.Load(xslFile);
xslDoc.DocumentElement.SetAttribute("xmlns:tt", ttNamespace);
xslXform.Load(xslDoc.CreateNavigator());
xslXform.Transform(sourceNav, null, writer);

这个可以工作,但是我觉得有点粗糙。我认为核心问题是我不理解(Xml | XPath) Document类型和与它们关联的名称表/命名空间管理器之间的关系。

我试了一下,这个成功了。

using System;
using System.Xml.Xsl;
class Sample {
    static public void Main(){
        XslCompiledTransform xslt = new XslCompiledTransform();
        xslt.Load("transform.xslt");
        xslt.Transform("sourcefile.xml", "result.txt");
    }
}