Share via


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 式は、次の 4 つの基本的な XPath データ型のいずれかを返すことができます。

  • String
  • Number
  • Boolean
  • Node-set

XSLT 変数は、式言語の 結果ツリー フラグメントに追加の型を導入します。

XPath の コア関数ライブラリいくつかの XSLT 固有の追加関数 は、XPath データ型を操作するための基本的な機能を提供します。 これらの関数を簡単に見ると、これはすべてのユーザーニーズに対して完全なセットではないことが明らかになります。

XPath の種類 関数
ノード セット last()、position()、count()、id()、local-name()、namespace-uri()、name()
String string()、concat()、starts-with()、contains()、substring-before()、substring-after()、substring()、string-length()、normalize-space()、translate()
Boolean 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 用のカスタム関数を実装する方法の概要を説明することです。

2 つの文字列の比較

最初の XPath クエリを作成するときに、2 つの文字列の大文字と小文字を区別しない比較を実行する必要があります。 私の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>

私は要素のコンマ区切りのリストからn番目 の著者の名前を見つけたかった- 少し複雑な文字列操作の <Authors> 問題。 XPath 用のカスタム拡張関数を実装する方法を学ぶのに良い時期だと思いました。

XPath での拡張関数の実装

XPath 1.0 の推奨事項では拡張関数のメカニズムが定義されていないことを理解しました。 しかし、XPath 式のユーザー定義関数と変数を解決するために、XPath プロセッサにカスタム実行コンテキストを提供できることは良いニュースでした。

次の図では、ソリューションの System.Xml.Xsl 名前空間からの XsltContext クラス、IXsltContextFunction インターフェイス、および IXsltContextVariable インターフェイスの役割について説明します。

図 1. XsltContext のロール

ソリューションの主な手順

  1. XPathExpression.SetContext(CustomContext) は、ユーザー定義関数と変数を解決するためのカスタム コンテキストを XPath プロセッサ (XPathNavigator) に提供します。 抽象クラス XsltContext から派生した CustomContext は、ResolveFunction()ResolveVariable() の 2 つのキー メソッドを実装します。
  2. XPathNavigator が XPathExpression でユーザー定義関数を見ると、カスタム コンテキストで ResolveFunction() メソッドを呼び出します。 ResolveFunction() はIXsltContextFunction から派生した適切なカスタム関数を返します。
  3. XPathNavigator は、指定された引数を使用して、実行時にこのカスタム関数で Invoke() メソッドを呼び出します。
  4. XPathNavigator は、XPathExpression でユーザー定義変数を検出すると、カスタム コンテキストで ResolveVariable() メソッドを呼び出します。 ResolveVariable()、IXsltContextVariable から派生した適切なカスタム変数を返します。
  5. XPathNavigator は、実行時にこのカスタム変数に対して Evaluate() メソッドを呼び出します。

.NET SDK の RegEx.Split() メソッドと同様に動作するカスタム XPath 関数 Split() を記述することにしました。 ここでは、すべての部分をまとめる方法です。

XsltContext クラスのロール

まず、ユーザー定義関数の解決に必要な情報を XPath プロセッサに提供するために、カスタム XsltContext を実装しました。 ResolveFunctionResolveVariable は、カスタム解決を実装するためにオーバーライドする必要がある XsltContext クラスの 2 つの主要なメソッドです。 これらは実行時に 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 インターフェイスの役割

次の手順は、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)");

変数$varのランタイム解決を有効にするには、IXsltContextVariable インターフェイスを実装し、実行時に XPathNavigator によって呼び出される 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 を使用したプログラミング中に他の多くのデータ操作の状況で使用しました。 その他の状況には、次のようなものがあります。

  • 日付の操作: 2 つの文字列を日付として比較することは一般的な操作です。 私はこの目的のために System.DateTime.Compare() メソッドを使用しました。
  • 数値の操作:Math クラスには、一般的な算術演算用のメソッドの完全なセットが用意されています。 私はカスタム関数として Abs() メソッドを使用しました。
  • 文字列の操作:String クラスは、一般的な文字列操作用のメソッドの完全なセットを提供します。 ToUpper() メソッドと ToLower() メソッドはカスタム関数として非常に便利であることがわかりました。

このリストは確かに無限です。 特定の状況に応じて、カスタム関数で任意の .NET クラスを使用できます。

今後の方向性: XPath 2.0

W3C XML スキーマのデータ型が XPath 2.0 と統合されるにつれて、 Xquery 1.0 および XPath 2.0 の関数と演算子 は、XPath 1.0 に現在存在する関数よりも豊富な関数ライブラリを XML 開発者に提供します。 これにより、XPath 2.0 でのユーザー定義関数の必要性が完全に解消されるわけではありません。 拡張のメカニズムは、XML の強力で拡張可能なクエリ言語に不可欠です。

XSLT の拡張関数

XSLT 1.0の XslTransform 実装では、拡張名前空間として名前空間 urn:schemas-microsoft-com:xslt が使用されます。 msxsl:node-set> 拡張関数と <msxsl:script> 拡張要素のサポート<が組み込まれています。

XSLT でユーザー定義関数を実装するには、次の 2 つの方法があります。

  1. スタイル シート スクリプト
  2. XsltArgumentList への拡張オブジェクトの追加

もっと読む

Acknowledgement

私はこの記事を見直す際の彼の助けを借りてデアオバサンジョに感謝したいと思います.

Prajakta Joshi は Microsoft WebData XML チームのメンバーであり、System.Xmlの XSLT 実装に取り組みます。.NET Frameworkの Xsl 名前空間。

この記事に関する質問やコメントは、GotDotNet の Extreme XML メッセージ ボード に自由に投稿できます。