Your Potential, Our Passion!

sharpen(尽其在我)

2004年11月8日 #

转载:向 XPath 中添加自定义函数

Web 应用程序中处理数据库更新的两种方法

Microsoft Corporation

Prajakta Joshi

2002 年 10 月 8 日

摘要:特邀作家 Prajakta Joshi 讨论了如何使用 .NET 框架 SDK 中的 System.Xml API 为 XPath 创建自定义函数。主题涉及向 XPath 1.0 中添加扩展函数、展望 XPath 2.0 以及使用 XSLT 中的扩展函数。(14 页打印页)

*

XMLXSL公共新闻组中,对扩展函数 的请求是一个经常讨论的主题。撰写本文的动机是因为我注意到有大量的用户帖子涉及该主题。 XPath 1.0中的 XPath 表达式能够返回以下四个基本 XPath 数据类型之一:

字符串

数值

布尔值

节点集

XSLT 变量向表达式语言中引入了一个附加类型 — result tree fragment(结果树片段)。

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()、sum()、floor()、ceiling()、round()

XSLT 1.0 中的新增函数

document()、key()、format-number()、current()、unparsed-entity-uri()、generate-id()、system-property()

需要操作非 XPath 数据类型(例如,日期)或者用 XPath 数据类型执行功能强大的/自定义数据操作的 XML 开发人员通常需要额外的函数。本文旨在概述如何使用 Microsoft .NET 框架 SDK 中的 System.XmlAPI 来为 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 表达式中用户定义的函数和变量。

下图解释了我的解决方案内 System.Xml.Xsl 命名空间中 XsltContext 类、IXsltContextFunction 接口和 IXsltContextVariable接口的角色。

XsltContext 的角色

1. XsltContext 的角色

解决方案中的关键步骤

1.XPathExpression.SetContext(CustomContext) 提供一个 XPath 处理器 (XPathNavigator),它具有用来解析用户定义的函数和变量的自定义上下文。CustomContext(从抽象类 XsltContext 派生)实现两个关键方法:ResolveFunction()ResolveVariable()

2.当 XPathNavigator 在 XPathExpression 中遇到用户定义的函数时,它会针对自定义上下文调用 ResolveFunction() 方法。ResolveFunction() 返回从 IXsltContextFunction 派生的适当自定义函数。

3.XPathNavigator 在运行时使用所提供的参数针对这个自定义函数调用 Invoke() 方法。

4.当 XPathNavigatorXPathExpression 中检测到用户定义的变量时,它会针对自定义上下文调用 ResolveVariable() 方法。ResolveVariable() 返回从 IXsltContextVariable 派生的适当的自定义变量。

5.XPathNavigator 在运行时针对这个自定义变量调用 Evaluate() 方法。

我决定编写一个自定义的 XPath 函数 — Split(),使该函数的行为类似于 .NET SDK 中的 RegEx.Split()方法。下面介绍如何将所有这些代码片段合并在一起。

XsltContext 类的角色

首先,我实现了我的自定义 XsltContext,以便给 XPath 处理器提供有关解析用户定义的函数所需的信息。ResolveFunctionResolveVariableXsltContext 类的两个关键方法,用户必须重写它们才能实现自定义解析。这些方法在运行时由 XPathNavigator调用,以便解析对 XPath 查询表达式中用户定义的函数和变量的引用。

请注意,我在 CustomContext 类中封装了一个 ResolveVariable对象。此对象是 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 接口的角色

下一步是实现供 CustomContext 类使用的

IXsltContextFunction

接口。此对象的

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
)");

我需要实现 IXsltContextVariable接口并重写 Evaluate()方法(此方法在运行时

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() 方法用作自定义函数。

操作日期:对两个日期字符串进行比较是常见的操作。为了实现此目的,我使用了 System.DateTime.Compare()方法。

操作字符串: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.0XslTransform实现将命名空间 urn:schemas-microsoft-com:xslt 用作扩展命名空间。它具有对 <msxsl:node-set>扩展函数和 <msxsl:script>扩展元素的内置支持。

在 XSLT 中,可通过两种方法来实现用户定义的函数:

1.样式表脚本

2.向 XsltArgumentList中添加扩展对象

posted @ 2004-11-08 20:48 sharpen 阅读(645) 评论(0) 编辑

2004年11月5日 #

智能客户端高级开发

摘要: http://www.microsoft.com/china/msdn/events/featureevents/2004/SmartClientSeminar/index.aspx我的关于smart client开发的网络讲座 经过设计,智能客户端应用程序可以将胖客户端应用程序的优点与瘦客户端应用程序的部署和可管理性优点结合起来,然而,要完全实现智能客户端应用程序的优点,需要考虑许多体系结构和设...阅读全文

posted @ 2004-11-05 22:54 sharpen 阅读(943) 评论(0) 编辑

2004年11月4日 #

Smart Client开发: 使用AppUpdater组件

摘要: 本来今天打算介绍Updater Application Block的,后来想想一些朋友经常反馈说用将UAB集成到自己开发的应用程序中太麻烦了,询问有没有更简单的方法布署智能客户端。在Whidbey的ClickOnce出来之前,答案当然还是有的,那就是使用AppUpdater组件。在美国举行的TechED 2004上介绍的IssueVision,TaskVision这些经典的Smart Client...阅读全文

posted @ 2004-11-04 16:36 sharpen 阅读(2984) 评论(9) 编辑

.net开发从入门到精通Webcast

摘要: 可以直接访问:http://www.microsoft.com/china/msdn/events/featureevents/2004/MSDevTrainingCourse.mspx 大家如果对Webcast的内容有什么问题,可以直接和我联系,我会尽快解答。希望大家多多支持中文MSDN站点。下次我可能会把VS.net团队开发的内容放上去。1、Windows窗体应用程序开发 ...阅读全文

posted @ 2004-11-04 10:22 sharpen 阅读(754) 评论(0) 编辑

2004年11月3日 #

使用异常管理应用程序块Exception Management Application Block

异常管理应用程序块Exception Management Application Block

异常管理应用程序块提供了管理应用程序异常的灵活而又简单的方法。先来看一个最简单使用EMAB的步骤:
(1)添加组件引用:using Microsoft.ApplicationBlocks.ExceptionManagement;
(2)在捕获异常的时候调用异常管理
                        try
                  {
                        throw new Exception ("测试一个异常");
                  }
                  catch(Exception err)
                  {
                        ExceptionManager.Publish (err);
                        MessageBox.Show (err.Message );
                  }
异常err的信息此时被自动记录到系统事件日志中。

就这么简单!

ExceptionManager.Publish执行的时候,首先会到app.config(或者web.config/machine.config)文件里查

找mode属性设置成on的<exceptionManagement>节点,如果没有设置信息,它会调用默认的异常发布类(发布到事

件日志中)。所以,我们使用上面最简单的发布异常的例子里,我们没有必要在配置文件里做任何配置。

当然,我们也可以显式地在app.config文件中声明使用默认异常发布类:

配置文件的格式如下:
<configuration>
  <configSections>
    <section name="exceptionManagement"
             type="Microsoft.ApplicationBlocks.ExceptionManagement
                   .ExceptionManagerSectionHandler,
                   Microsoft.ApplicationBlocks.ExceptionManagement" />
  </configSections>

  <exceptionManagement mode="on/off">
    <publisher mode="on/off" assembly="AssemblyName" type="TypeName"
               exclude="(+)Type;(+)Type" include="(+)Type;(+)Type"
               exceptionFormat="value" /*xml or others*/
               customattr = "value" />
  </exceptionManagement>
</configuration>

我们来解释一下:
<configSections>
            <section name="exceptionManagement"

type="Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManagerSectionHandler,Microsoft.Ap

plicationBlocks.ExceptionManagement" />
      </configSections>

上面这个配置表示使用ExceptionManagerSectionHander类来读取exceptionManagement节点注册的Publisher类

<exceptionManagement mode="on">           
            <publisher assembly="Microsoft.ApplicationBlocks.ExceptionManagement"

type="Microsoft.ApplicationBlocks.ExceptionManagement.DefaultPublisher" logname="日志文件名"

applicationname="应用程序名" />
      </exceptionManagement>
这个配置信息表示将默认异常发布类注册成应用程序所使用的异常发布类:发布到事件日志的"日志文件名"文

件里(如果不设logname,则使用"应用程序”日志文件)。

我们可以自定义(一个或多个)异常发布类,并把它注册到app.config中,自定义Publisher类主要是要实现IExceptionPublisher接口的Publish方法。这也是很简单的,大家可以查看Exception Management Application block自带的Sample.

下一步就是把一个或多个异常发布类注册到配置文件中:
<exceptionManagement mode="on">
            <publisher assembly="ExceptionManagementQuickStartSamples" type="ExceptionManagementQuickStartSamples.ExceptionPublisher"  exclude="*" include="ExceptionManagementQuickStartSamples.LogonException, ExceptionManagementQuickStartSamples; ExceptionManagementQuickStartSamples.CustomAppException, ExceptionManagementQuickStartSamples" operatorMail="spli@microsoft.com"/>                       
            <publisher assembly="ExceptionManagementQuickStartSamples" type="ExceptionManagementQuickStartSamples.ExceptionXMLPublisher" exclude="*" include="+Microsoft.ApplicationBlocks.ExceptionManagement.BaseApplicationException, Microsoft.ApplicationBlocks.ExceptionManagement" exceptionFormat="xml" fileName="c:\QuickStartSamplesExceptionLog.xml"/>
      </exceptionManagement>

OK,就介绍到这吧。

posted @ 2004-11-03 16:02 sharpen 阅读(842) 评论(0) 编辑

2004年11月2日 #

Microsoft数据访问应用块 Data Acess Application Block V2 (2) :返回多表结果到同一个DataSet中

摘要: 如果要使用SqlHelper执行多个SqlCommand并将结果填到同一个DataSet的多个表中,除了在上一篇文章介绍过的使用 ExecuteDataset这个函数以外,还可以使用另一个功能强大的函数:FillDataSet,并且这个方法比ExecuteDataSet更为灵活,比如,您可以指定填充的表的名字,可以在多表之间建立TableRelation,等等。下面给出一段示例代码: &...阅读全文

posted @ 2004-11-02 16:54 sharpen 阅读(1107) 评论(0) 编辑

Microsoft数据访问应用块 Data Acess Application Block V2 (1)

摘要: Data Access Application Block 将访问 Microsoft SQL Server™ 数据库的性能和资源管理方面的最佳经验封装在一起。您可以很方便地在自己的 .NET 应用程序中将其作为构造块使用,从页减少了需要创建、测试和维护的自定义代码的数量。个人认为,DAAB最大的好处之一在于DAAB帮助开发者自动管理了数据库的连接. Connection对象是数据库访问组件中最为...阅读全文

posted @ 2004-11-02 16:11 sharpen 阅读(1597) 评论(2) 编辑

决定在blog中讨论一下微软的所有Application Block

摘要: Data Access Application Block for .NET v2Authorization and Profile Application Application BlockOffline Application Application Application BlockService Aggregation BlockPersistent Asynchronous Invoca...阅读全文

posted @ 2004-11-02 15:06 sharpen 阅读(1139) 评论(2) 编辑

2004年10月25日 #

Business Activity Monitor BAM.xls的DLL Hell问题

摘要: 打开BAM.xls进行业务活动监视的时候,会报如下错误:中文版:隐藏模块中编译错误:工具英文版:"Compile error in hidden module: Utility"由于BAM Project被密码保护,所以开发人员无法进行调试。出错原因正是大名鼎鼎的DLL地狱问题,Biztalk 2004 BAM模板中使用的是msado27.tlb(MSDA 2.7)库,如果你安装了最新的MSDA ...阅读全文

posted @ 2004-10-25 15:21 sharpen 阅读(867) 评论(0) 编辑

2004年10月21日 #

Biztalk Server 2004中在多个接收端口启动工作流

摘要: 用Web Service发布向导可以将Biztalk 工作流发布成Web Service让供应商或合作伙伴通过HTTP 和SOAP适配器远程调用,与此同时,对于内部的ERP系统,可能更希望通过File适配器调用,如何让一个工作流即可以通过SOAP传入的消息启动,又可以通过File Folder中的传入文件启动呢?方法还是很简单的,就是在一个Receive Port中添加多个Receive Loca...阅读全文

posted @ 2004-10-21 22:49 sharpen 阅读(1332) 评论(6) 编辑

导航

统计信息

News

搜索

 
 

常用链接

随笔档案

.net 开发

Biztalk Server 相关

SharePoint Portal Server 相关

读书笔记

管理咨询

朋友的Blog

微软解决方案框架(MSF)

智能客户端

最新评论

阅读排行榜

评论排行榜

推荐排行榜