65.9K
CodeProject 正在变化。 阅读更多。
Home

向 XSL 添加自定义 XPath 函数

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (9投票s)

2003年3月3日

3分钟阅读

viewsIcon

77795

downloadIcon

434

本文向您展示如何将上下文相关的 XPath 函数添加到 XSL 转换中。 这是通过使用反射来实现的。

引言

XSL 转换是可视化 XML 的强大方法。 扩展对象的存在使其更加强大。 但是,事实证明扩展对象非常有限。 它们无法检索转换的当前上下文或当前文档。 此外,它们的返回类型仅限于数字和字符串(返回 IXPathNavigable 受到限制。有关更多详细信息,请参阅此处)。 本文介绍了针对第一个问题的解决方法。

为什么要使用上下文相关的 XPath 函数

一个简单但非常强大的应用是 eval 函数。 它评估包含在字符串或变量中的 XPath 查询。 例如,假设您有以下内容

<xsl:variable name='vTest'>123456</xsl:variable>
<xsl:variable name= 'vQuery'>$vTest</xsl:variable>

如果您使用 xsl:value-of 获取 vQuery 的值,您将得到 $vTest 作为结果。 另一方面,<xsl:value-of select='eval($vTest)'/> 将评估为 123456。 没有内置这样的函数,所以我们将自己编写它。

背景

要阅读本文,您应该对 C# 和 Microsoft 的 .NET XML 解析器有很好的了解。

工作原理

看起来 Microsoft 已经有了允许用户编写上下文相关函数的想法。 查看 System.Xml.Xsl 命名空间中的 2 个类 IXsltContextFunctionXsltContext。 您可以创建自己的派生自以上 2 个类的上下文和函数类,但您只能在程序化的 XPath 查询中使用它们。 我在 Microsoft 的 KB 中找到的一篇文章“建议”您使用扩展对象而不是派生自 IXsltContextFunction 的类来编写自己的 XPath 函数。 实际上,事实证明这并不是建议 - 这是唯一的方法。 无法向转换添加上下文相关的 XPath 扩展函数。 我反编译了在 XSLT 中解析函数的代码 (System.Xml.Xsl.XsltCompileContext.ResolveFunction),这是我得到的结果

public virtual IXsltContextFunction ResolveFunction(string prefix, 
              string name, System.Xml.XPath.XPathResultType[] argTypes){
    IXsltContextFunction local0;
    string local1;
    object local2;
    MethodInfo local3;

    local0 = null;
    if (prefix == String.Empty)
        local0 = XsltCompileContext.s_FunctionTable.get_Item(name) 
                                           as IXsltContextFunction;
    else {
        local1 = this.LookupNamespace(prefix);
        if (local1 == "urn:schemas-microsoft-com:xslt" && name == "node-set")
            local0 = XsltCompileContext.s_FuncNodeSet;
        else {
            local3 = this.GetExtentionMethod(local1, name, argTypes, local2);
            if (local2 == null)
                throw new XsltException("Xslt_ScriptInvalidPrefix", prefix);
            if (local3 != null)
                local0 = new FuncExtension(local2, local3);
        }
    }
    if (local0 == null)
        throw new XsltException("Xslt_UnknownXsltFunction", name);
    if ((int) argTypes.Length < local0.Minargs || 
             local0.Maxargs < (int) argTypes.Length)
        throw new XsltException("Xslt_WrongNumberArgs", name, 
                       Convert.ToString((int) argTypes.Length));
    return local0;
}

如您所见,XSLT 可以解析 3 种类型的函数

  • 位于根 XML 命名空间中的函数
  • msxsl:node-set 函数
  • 扩展对象上的函数

从上面的代码可以看出,插入上下文相关函数的唯一方法是将其放入静态哈希表 s_FunctionTable 字段中。 正如您已经猜到的,System.Xml.Xsl.XsltCompileContext 是一个受保护的类,s_FunctionTable 也是如此。 要访问它,我们将使用反射

static Hashtable XslFunctionTable
{
    get
    {
        Assembly xmlAssembly = Assembly.LoadWithPartialName("System.Xml");
        Type tXsltCompileContext = xmlAssembly.GetType
                    ("System.Xml.Xsl.XsltCompileContext");
        FieldInfo finf = tXsltCompileContext.GetField("s_FunctionTable", 
                           BindingFlags.NonPublic | BindingFlags.Static);
        return finf.GetValue(null) as Hashtable;
    }
}

一旦我们有了哈希表,我们就可以轻松地从中添加或删除函数。 哈希表在 System.Xml.Xsl.XsltCompileContext 的静态构造函数中初始化,因此我们应避免在静态构造函数中添加和删除函数

缺点

这实际上是一种黑客行为。 它在 .NET framework 1.0 上运行良好,但在该框架的未来版本中可能无法正常工作。 我希望 Microsoft 在未来解决这个问题。 此外,您只能在根 XML 命名空间中添加函数。

可能的改进

System.Xml 程序集是使用其部分名称定位的。 这不好,因为 .NET framework 的未来版本将使用更新的 XML 解析器,并且该黑客行为将不起作用。 应该使用完整名称代替。

© . All rights reserved.