将自定义函数添加到 XPath
Prajakta Joshi
Microsoft Corporation
2002 年 10 月 8 日
总结:来宾作者 Prajakta Joshi 讨论如何使用 .NET Framework SDK 中的 System.Xml API 为 XPath 创建自定义函数。 主题包括向 XPath 1.0 添加扩展函数、期待 XPath 2.0 以及使用 XSLT 中的扩展函数。 ) (14 个打印页
扩展函数请求是 XML 和 XSL 公共新闻组上经常讨论的主题。 编写本文的动机源于观察有关本主题的大量用户帖子。
XPath 1.0 中的 XPath 表达式可以返回四种基本 XPath 数据类型之一:
- String
- Number
- 布尔
- Node-set
XSLT 变量在表达式语言中引入了其他类型, 即结果树片段。
XPath 中 的核心函数库 和一 些特定于 XSLT 的其他函数 提供了用于操作 XPath 数据类型的基本工具。 快速浏览这些函数可发现,这不是满足所有用户需求的详尽集。
XPath 类型 | 函数 |
---|---|
节点集 | last () , position () , count () , id () , local-name () , namespace-uri () , name () |
字符串 | string () 、concat () 、starts-with () 、contains () 、substring-before () 、substring-after () 、substring () 、string-length () 、normalize-space () 、translate () |
布尔 | boolean () , not () , true () , false () , lang () |
Number | number () , sum () , floor () , ceiling () , round () |
XSLT 1.0 新增内容 | document () , key () , format-number () , current () , unparsed-entity-uri () , generate-id () , system-property () |
需要操作非 XPath 数据类型 ((如日期) )或使用 XPath 数据类型执行强大的/自定义数据操作的 XML 开发人员通常需要其他函数。 本文旨在概述如何使用 Microsoft .NET Framework SDK 中的 System.Xml API 为 XPath 实现自定义函数。
两个字符串的比较
当我编写我的第一个 XPath 查询时,我需要对两个字符串执行不区分大小写的比较。 我的books.xml如下所示:
<bookstore xmlns:my="urn:http//mycompany.com/">
<book style="young adult">
<title>Harry Potter and the Goblet of Fire</title>
<author>
<first-name>Mary</first-name>
<last-name>Gradpre</last-name>
</author>
<my:price>8.99</my:price>
</book>
<book style="young fiction">
<title>Lord of the Rings</title>
<author>
<first-name>J.</first-name>
<last-name>Tolkien</last-name>
</author>
<my:price>22.50</my:price>
</book>
</bookstore>
我在 XPath 1.0 字符串函数中查找了类似于 String.Compare () 的函数。 可在解决方案中使用的最接近的函数是 translate () 。 我在System.Xml中使用 XPath 类解决了以下代码段的问题 。XPath 命名空间:
using System;
using System.Xml;
using System.Xml.XPath;
public class sample
{
public static void Main(string []args)
{
// Load source XML into XPathDocument.
XPathDocument xd = new XPathDocument(args[0], XmlSpace.Preserve);
// Create XPathNavigator from XPathDocument.
XPathNavigator nav = xd.CreateNavigator();
XPathExpression expr;
expr =
nav.Compile("/bookstore/book/title[translate(.,'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') = 'HARRY POTTER AND THE GOBLET OF FIRE']");
XPathNodeIterator iterator = nav.Select(expr);
// Iterate through selected nodes.
while (iterator.MoveNext())
{
Console.WriteLine("Book title: {0}", iterator.Current.Value);
}
}
}
此代码生成以下输出:
Book title: Harry Potter and the Goblet of Fire
解决方案有点长,但我解决了我的问题。 几天后编写 XPath 查询时,我想在正则表达式匹配项定义的位置将字符串拆分为子字符串数组。
<?xml version="1.0" encoding="utf-8" ?>
<Books>
<Book>
<Title>Stephen Hawking's Universe: The Cosmos Explained</Title>
<Authors>David Filkin, Stephen Hawking</Authors>
</Book>
<Book>
<Title>Writing Secure Code</Title>
<Authors>Michael Howard, David LeBlanc</Authors>
</Book>
</Books>
我想从以逗号分隔的元素列表中<Authors>
找出第 n 个作者的姓名 ,这是一个有点复杂的字符串操作问题。 我认为这是了解如何为 XPath 实现自定义扩展函数的好时机。
在 XPath 中实现扩展函数
我发现 XPath 1.0 建议没有定义扩展函数的机制。 但是,好消息是,我可以向 XPath 处理器提供自定义执行上下文,以解析 XPath 表达式中的用户定义的函数和变量。
下图说明了 XsltContext 类、IXsltContextFunction 接口以及解决方案中System.Xml.Xsl 命名空间中的 IXsltContextVariable 接口的作用。
图 1. XsltContext 的角色
解决方案中的关键步骤
- XPathExpression.SetContext (CustomContext) 为 XPath 处理器 (XPathNavigator) 提供自定义上下文,用于解析用户定义的函数和变量。 从抽象类 XsltContext 派生的 CustomContext 实现了两个关键方法:ResolveFunction () 和 ResolveVariable () 。
- 当 XPathNavigator 在 XPathExpression 中看到用户定义的函数时,它会在自定义上下文中调用 ResolveFunction () 方法。 ResolveFunction () 返回相应的自定义函数,该函数派生自 IXsltContextFunction。
- XPathNavigator 在运行时使用提供的参数对此自定义函数调用 Invoke () 方法。
- 当 XPathNavigator 在 XPathExpression 中检测到用户定义的变量时,它会在自定义上下文中调用 ResolveVariable () 方法。 ResolveVariable () 返回从 IXsltContextVariable 派生的相应自定义变量。
- XPathNavigator 在运行时对此自定义变量调用 Evaluate () 方法。
我决定编写一个自定义 XPath 函数 Split () , 该函数的行为类似于 .NET SDK 中的 RegEx.Split () 方法。 以下是我把所有碎片放在一起的方式。
XsltContext 类的角色
首先,我实现了自定义 XsltContext ,以便为 XPath 处理器提供有关解析用户定义函数的必要信息。 ResolveFunction 和 ResolveVariable 是 XsltContext 类的两个关键方法,必须重写这些方法才能实现自定义解析。 这些函数在运行时由 XPathNavigator 调用,以解析对 XPath 查询表达式中用户定义的函数和变量的引用。
请注意,我在 CustomContext 类中封装了 XsltArgumentList 对象。 此对象是 XPath 表达式中变量的容器。
public class CustomContext : XsltContext
{
// XsltArgumentList to store my user defined variables
private XsltArgumentList m_ArgList;
// Constructors
public CustomContext()
{}
public CustomContext(NameTable nt) : base(nt)
{
}
public CustomContext(NameTable nt, XsltArgumentList argList) : base(nt)
{
m_ArgList = argList;
}
// Returns the XsltArgumentList that contains custom variable definitions.
public XsltArgumentList ArgList
{
get
{
return m_ArgList;
}
}
// Function to resolve references to my custom functions.
public override IXsltContextFunction ResolveFunction(string prefix,
string name, XPathResultType[] ArgTypes)
{
XPathRegExExtensionFunction func = null;
// Create an instance of appropriate extension function class.
switch (name)
{
case "Split":
// Usage
// myFunctions:Split(string source, string Regex_pattern, int n) returns string
func = new XPathRegExExtensionFunction("Split", 3, 3, new
XPathResultType[] {XPathResultType.String, XPathResultType.String,
XPathResultType.Number}, XPathResultType.String);
break;
case "Replace":
// Usage
// myFunctions:Replace(string source, string Regex_pattern,
string replacement_string) returns string
func = new XPathRegExExtensionFunction("Replace", 3, 3, new
XPathResultType[] {XPathResultType.String, XPathResultType.String,
XPathResultType.String}, XPathResultType.String);
break;
}
return func;
}
// Function to resolve references to my custom variables.
public override IXsltContextVariable ResolveVariable(string prefix, string name)
{
// Create an instance of an XPathExtensionVariable.
XPathExtensionVariable Var;
Var = new XPathExtensionVariable(name);
return Var;
}
public override int CompareDocument(string baseUri, string nextbaseUri)
{
return 0;
}
public override bool PreserveWhitespace(XPathNavigator node)
{
return true;
}
public override bool Whitespace
{
get
{
return true;
}
}
}
IXsltContextFunction 接口的角色
下一步是实现 IXsltContextFunction 接口以供 CustomContext 类使用。 此对象上的 Invoke () 方法在运行时由 XPathNavigator 使用提供的参数调用。
public class XPathRegExExtensionFunction : IXsltContextFunction
{
private XPathResultType[] m_ArgTypes;
private XPathResultType m_ReturnType;
private string m_FunctionName;
private int m_MinArgs;
private int m_MaxArgs;
// Methods to access the private fields.
public int Minargs
{
get
{
return m_MinArgs;
}
}
public int Maxargs
{
get
{
return m_MaxArgs;
}
}
public XPathResultType[] ArgTypes
{
get
{
return m_ArgTypes;
}
}
public XPathResultType ReturnType
{
get
{
return m_ReturnType;
}
}
// Constructor
public XPathRegExExtensionFunction(string name, int minArgs, int
maxArgs, XPathResultType[] argTypes, XPathResultType returnType)
{
m_FunctionName = name;
m_MinArgs = minArgs;
m_MaxArgs = maxArgs;
m_ArgTypes = argTypes;
m_ReturnType = returnType;
}
// This method is invoked at run time to execute the user defined function.
public object Invoke(XsltContext xsltContext, object[] args,
XPathNavigator docContext)
{
Regex r;
string str = null;
// The two custom XPath extension functions
switch (m_FunctionName)
{
case "Split":
r = new Regex(args[1].ToString());
string [] s1 = r.Split(args[0].ToString());
int n = Convert.ToInt32(args[2]);
if (s1.Length < n)
str = "";
else
str = s1[n - 1];
break;
case "Replace":
r = new Regex(args[1].ToString());
string s2 = r.Replace(args[0].ToString(), args[2].ToString());
str = s2;
break;
}
return (object) str;
}
}
IXsltContextVariable 接口的角色
XPath 表达式可以包含用户定义的变量引用,例如:
XPathExpression expr1 = nav.Compile("myFunctions:Split(string(.), ',', $var)");
若要启用变量 $var的运行时解析,我需要实现 IXsltContextVariable 接口并重写 Evaluate () 方法,该方法在运行时由 XPathNavigator 调用。
public class XPathExtensionVariable : IXsltContextVariable
{
// The name of the user-defined variable to resolve
private string m_VarName;
public XPathExtensionVariable(string VarName)
{
m_VarName = VarName;
}
// This method is invoked at run time to find the value of the user defined variable.
public object Evaluate(XsltContext xsltContext)
{
XsltArgumentList vars = ((CustomContext) xsltContext).ArgList;
return vars.GetParam(m_VarName, null);
}
public bool IsLocal
{
get
{
return false;
}
}
public bool IsParam
{
get
{
return false;
}
}
public XPathResultType VariableType
{
get
{
return XPathResultType.Any;
}
}
}
总结
最后,我使用了 XPathExpression.SetContext () 方法,向其传递了自定义上下文对象。 图 (1 汇总了解决方案中的所有步骤。 请注意, XsltContext 类继承自 XmlNamespaceManager,并使用 AddNamespace () 向集合添加了自定义命名空间。
using System;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.Text.RegularExpressions;
public class sample
{
public static void Main(string []argc)
{
// Load source XML into XPathDocument.
XPathDocument doc = new XPathDocument("books.xml", XmlSpace.Preserve);
// Create XPathNavigator from XPathDocument.
XPathNavigator nav = doc.CreateNavigator();
// Add user-defined variable to the XsltArgumentList.
XsltArgumentList varList = new XsltArgumentList();
varList.AddParam("var", "", 2);
// Compile the XPathExpression.
// Note that the compilation step only checks the query expression
// for correct XPath syntax.
// User defined functions and variables are not resolved.
XPathExpression expr1 = nav.Compile("myFunctions:Split(string(.), ',', $var)");
// Create an instance of a custom XsltContext object.
CustomContext cntxt = new CustomContext(new NameTable(), varList);
// Add a namespace definition for myFunctions prefix.
cntxt.AddNamespace("myFunctions", "http://myXPathExtensionFunctions");
// Associate the custom context with the XPathExpression object.
expr1.SetContext(cntxt);
XPathNodeIterator it = nav.Select("/Books/Book/Authors");
while (it.MoveNext())
{
Console.WriteLine("Authors: {0}", it.Current.Value);
Console.WriteLine("Second author: {0}", it.Current.Evaluate(expr1));
}
}
}
运行此代码生成了以下输出:
Authors: David Filkin, Stephen Hawking
Second author: Stephen Hawking
Authors: Michael Howard, David LeBlanc
Second author: David LeBlanc
扩展函数的其他用途
发现使用扩展函数的机制后,我在使用 XPath 编程时,在许多其他数据操作情况下都使用了扩展函数。 其他一些情况包括:
- 操作日期: 将两个字符串作为日期进行比较是一个常见操作。 为此,我使用了 System.DateTime.Compare () 方法。
- 操作数字:Math 类为常见数学运算提供了一组详尽的方法。 我使用了 Abs () 方法作为自定义函数。
- 操作字符串:String 类为常见字符串操作提供了一组详尽的方法。 我发现 ToUpper () 和 ToLower () 方法作为自定义函数非常有用。
这个列表确实是无穷无尽的。 根据你的特定情况,可以在自定义函数中使用任何 .NET 类。
未来方向:XPath 2.0
随着 W3C XML 架构数据类型与 XPath 2.0 的集成更加紧密, Xquery 1.0 和 XPath 2.0 函数和运算符 将为 XML 开发人员提供比 XPath 1.0 中当前更丰富的函数库。 这并不能完全消除 XPath 2.0 中对用户定义的函数的需求。 扩展机制对于 XML 的强大且可扩展的查询语言来说不可或缺。
XSLT 中的扩展函数
XSLT 1.0 的 XslTransform 实现使用命名空间 urn:schemas-microsoft-com:xslt 作为扩展命名空间。 它内置了对 <msxsl:node-set> 扩展函数和 <msxsl:script> 扩展元素的支持。
可通过两种方法在 XSLT 中实现用户定义的函数:
- 样式表脚本
- 将扩展对象添加到 XsltArgumentList
深入阅读
- W3C XPath 1.0 建议
- Microsoft 知识库文章:如何:在 Visual C# .NET 中执行 XPath 查询时实现和使用自定义扩展函数
- 样式表参数和扩展对象的 XsltArgumentList
Acknowledgement
我要感谢达雷·奥巴桑乔对本文的帮助。
Prajakta Joshi 是 Microsoft WebData XML 团队的成员,致力于System.Xml中的 XSLT 实现。.NET Framework的 Xsl 命名空间。
可随时在 GotDotNet 上的 Extreme XML 消息板上 发布有关本文的任何问题或评论。