共用方式為


這兩個世界的最佳專案:結合 XPath 與 XmlReader

 

Dare Obasanjo 和 Howard 一文
Microsoft Corporation

2004 年 5 月 5 日

下載XPathReader.exe範例檔案

總結: Dare Obasanjo 討論 XPathReader,其可讓您使用 XPath 感知 XmlReader 以有效率的方式篩選及處理大型 XML 檔。 透過 XPathReader,使用者可以循序處理大型檔,並擷取 XPath 運算式比對的已識別子樹狀結構。 (11 個列印的頁面)

簡介

大約一年前,我閱讀了 Tim Bray 標題為 XML 太硬的程式設計人員的文章,他抱怨推播模型 API 的麻煩本質,例如 SAX 來處理大量的 XML 資料流程。 Tim Bray 將 XML 的理想程式設計模型描述為類似于使用 Perl 中的文字,其中一個可以使用正則運算式比對感興趣的專案來處理文字串流。 以下是 Tim Bray 文章的摘錄,其中顯示 XML 資料流程的理想化程式設計模型。

while (<STDIN>) {
  next if (X<meta>X);
  if    (X<h1>|<h2>|<h3>|<h4>X)
  { $divert = 'head'; }
  elsif (X<img src="/^(.*\.jpg)$/i>X)
  { &proc_jpeg($1); }
  # and so on...
}

Tim Bray 不是這個 XML 處理模型的唯一年份。 在過去數年,我所處理的各種人員都致力於建立程式設計模型,以類似正則運算式處理文字資料流程的方式處理 XML 檔的資料流程。 本文說明這項工作的決定性,也就是 XPathReader

尋找貸款書籍:XmlTextReader 解決方案

為了清楚指出相較于使用XmlReader的現有 XML 處理技術,XPathReader的生產力提升,我建立了執行基本 XML 處理工作的範例程式。 下列範例檔說明我所擁有的一些書籍,以及他們目前是否向朋友貸款。

 <books>
  <book publisher="IDG books" on-loan="Sanjay">
    <title>XML Bible</title>
    <author>Elliotte Rusty Harold</author>
  </book>
  <book publisher="Addison-Wesley">
    <title>The Mythical Man Month</title>
    <author>Frederick Brooks</author>
  </book>
  <book publisher="WROX">
    <title>Professional XSLT 2nd Edition</title>
    <author>Michael Kay</author>
  </book>
  <book publisher="Prentice Hall" on-loan="Sander" >
   <title>Definitive XML Schema</title>
   <author>Priscilla Walmsley</author>
  </book>
  <book publisher="APress">
   <title>A Programmer's Introduction to C#</title>
   <author>Eric Gunnerson</author>
  </book>
</books>
   

下列程式碼範例會顯示我已貸款書籍的人員名稱,以及我所貸款的書籍。 程式碼範例應該會產生下列輸出。

Sanjay was loaned XML Bible by Elliotte Rusty Harold 
Sander was loaned Definitive XML Schema by Priscilla Walmsley

XmlTextReader Sample: 
using System; 
using System.IO; 
using System.Xml;

public class Test{


    static void Main(string[] args) {

      try{ 
      XmlTextReader reader = new XmlTextReader("books.xml");
      ProcessBooks(reader);

      }catch(XmlException xe){
        Console.WriteLine("XML Parsing Error: " + xe);
      }catch(IOException ioe){
        Console.WriteLine("File I/O Error: " + ioe);
      }
    }  

    static void ProcessBooks(XmlTextReader reader) {
      
      while(reader.Read()){
      
        //keep reading until we see a book element 
        if(reader.Name.Equals("book") && 
      (reader.NodeType == XmlNodeType.Element)){ 
          
     if(reader.GetAttribute("on-loan") != null){ 
            ProcessBorrowedBook(reader);
          }else {
            reader.Skip();
          }
        }
      }
    }


   static void ProcessBorrowedBook(XmlTextReader reader){

 Console.Write("{0} was loaned ", 
                             reader.GetAttribute("on-loan"));
      
      
      while(reader.NodeType != XmlNodeType.EndElement && 
                                            reader.Read()){
       
       if (reader.NodeType == XmlNodeType.Element) {
          
     switch (reader.Name) {
            case "title":              
              Console.Write(reader.ReadString());
              reader.Read(); // consume end tag
              break;
            case "author":
              Console.Write(" by ");
              Console.Write(reader.ReadString());
              reader.Read(); // consume end tag
              break;
          }
        }
      }
      Console.WriteLine();
    }
}       

使用 XPath 作為 XML 的正則運算式

我們需要的第一件事是針對 XML 資料流程中感興趣的節點執行模式比對的方式,與文字資料流程中字串的正則運算式相同。 XML 已經有一種語言,可用來比對稱為 XPath的節點,這可作為良好的起點。 XPath 有一個問題,可防止它使用,而不需修改為以串流方式比對大型 XML 檔中節點的機制。 XPath 假設整個 XML 檔儲存在記憶體中,並允許需要多次傳遞檔的作業,或至少需要將大量 XML 檔儲存在記憶體中。 下列 XPath 運算式是這類查詢的範例:

/books/book[author='Frederick Brooks']/@publisher

如果書籍專案的子作者元素值為 'Frederick Querys',則查詢會傳回book元素的發行者屬性。 在串流剖析器上看到發行者屬性時,必須先快取書籍元素上的發行者屬性,直到子作者元素看到且檢查其值為止,否則無法執行此查詢。 視檔案大小和查詢而定,必須在記憶體中快取的資料量可能相當大,並找出要快取的資料可能相當複雜。 為了避免共同工作角色 Arpan Desai必須處理這些問題,請提出適用于 XML 順向處理的 XPath 子集提案。 這個 XPath 子集的本文說明 循序 XPath 簡介

循序 XPath 中的標準 XPath 文法有數項變更,但最大的變更是軸的使用限制。 現在,某些座標軸在述詞中有效,而其他座標軸只有在循序 XPath 運算式的非述詞部分才有效。 我們已將座標軸分類為三個不同的群組:

  • 一般座標軸: 提供目前節點內容的相關資訊。 它們可以在循序 XPath 運算式中的任何位置套用。
  • 向前軸: 提供資料流程中內容節點之前節點的相關資訊。 它們只能套用在位置路徑內容中,因為它們正在尋找「未來」節點。 範例為 「child.'' 如果 「child」 位於路徑中,我們可以成功選取指定路徑的子節點。 不過,如果 「child」 位於述詞中,我們就無法選取目前的節點,因為我們無法事先查看其子系來測試述詞運算式,然後倒轉讀取器以選取節點。
  • 反向軸: 基本上與正向軸相反。 例如「父系」。如果父系位於位置路徑中,我們想要傳回特定節點的父系。 同樣地,因為我們無法往後移動,所以無法在位置路徑或述詞中支援這些座標軸。

下表顯示 XPathReader 支援的 XPath軸:

類型 支援的位置
通用座標軸 attribute, namespace, self XPath 運算式中的任何位置
正向軸 child、descendant、descendant-or-self、following、following-sibling 除了述詞以外,XPath 運算式中的任何位置
反向軸 上階、上階或自我、父系、先前、上層同層級 不支援

XPathReader 不支援某些 XPath 函式,因為它們也需要在記憶體中快取 XML 檔的大型部分,或能夠回溯 XML 剖析器。 完全不支援 count () sum () 等函式,而 local-name () namespace-uri () 等函式只有在未指定任何引數 (時才能運作,也就是只有在要求內容節點上的這些屬性時,才會) 。 下表列出不支援或已在 XPathReader中限制某些功能的 XPath 函式。

XPath 函式 支援的子集 Description
number last () 不支援 無法在沒有緩衝的情況下運作
節點集) (計數 不支援 無法在沒有緩衝的情況下運作
string local-name (node-set?) string local-name () 無法使用節點集作為參數
string namespace-uri (node-set?) string namespace-uri () 無法使用節點集作為參數
(node-set?) 字串名稱 字串名稱 () 無法使用節點集作為參數
節點集 (數目總和) 不支援 無法在沒有緩衝的情況下運作

在 XPathReader 中對 XPath所做的最後一項主要限制是不允許測試專案或文位元組點的值。 XPathReader 不支援下列 XPath 運算式:

 /books/book[contains(.,'Frederick Brooks')]

如果上述查詢的字串包含文字 'Frederick Querys',則查詢會選取 book 元素。 為了能夠支援這類查詢,檔的大型部分可能需要快取, 而且 XPathReader 必須能夠回復其狀態。 不過,支援測試屬性、批註或處理指示的值。 XPathReader 支援下列 XPath運算式:

/books/book[contains(@publisher,'WROX')]

以上所述的 XPath 子集已足夠減少,讓一個子集提供記憶體有效率的串流 XPath 型 XML 剖析器,類似于與文字資料流程相符的正則運算式。

第一次查看 XPathReader

XPathReaderXmlReader的子類別,可支援上一節所述的 XPath 子集。 XPathReader可用來處理從 URL 載入的檔案,也可以分層在其他XmlReader實例上。 下表顯示XPathReader新增至XmlReader的方法。

方法 Description
比對 (XPathExpression) 測試讀取器目前所在的節點是否與 XPathExpression相符。
比對 (字串) 測試讀取器目前所在的節點是否與 XPath 字串相符。
(比對 int) 測試讀取器目前所在的節點是否與讀取器 XPathCollection中指定索引的 XPath 運算式相符。
MatchesAny (ArrayList) 測試讀取器目前所在的節點是否與清單中任何 XPathExpressions 相符。
ReadUntilMatch () 繼續讀取 XML 資料流程,直到目前的節點符合其中一個指定的 XPath 運算式為止。

下列範例會使用 XPathReader 來列印我的文件庫中每一本書的標題:

using System; 
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;

public class Test{
static void Main(string[] args) {

      try{ 
XPathReader xpr  = new XPathReader("books.xml", "//book/title"); 

            while (xpr.ReadUntilMatch()) {
               Console.WriteLine(xpr.ReadString()); 
             }      
            Console.ReadLine(); 
   }catch(XPathReaderException xpre){
      Console.WriteLine("XPath Error: " + xpre);
      }catch(XmlException xe){
         Console.WriteLine("XML Parsing Error: " + xe);
      }catch(IOException ioe){
         Console.WriteLine("File I/O Error: " + ioe);
      }
   }  
}

XPathReader對於使用XmlTextReader的傳統 XML 處理明顯優點是應用程式不需要在處理 XML 資料流程時持續追蹤目前的節點內容。 在上述範例中,應用程式程式碼不需要擔心其內容為顯示和列印的 title 元素是否為 書籍 元素的子系,因為 XPath 已經完成此動作。

另一個拼字是 XPathCollection 類別。 XPathCollectionXPathReader應該比對的 XPath 運算式集合。 XPathReader只會比對其 XPathCollection物件中包含的節點。 此比對是動態的,這表示您可以視需要在剖析程式期間從 XPathCollection 新增和移除 XPath 運算式。 這可讓效能優化,在需要測試之前,不會針對 XPath 運算式進行測試。 當 XPathCollection與 XPath 運算式比對節點時,也會用於指定XPathReader所使用的前置 <> 詞命名空間系結。 下列程式碼片段示範如何完成此動作:

XPathCollection xc  = new XPathCollection();
xc.NamespaceManager = new XmlNamespaceManager(new NameTable()); 
xc.NamespaceManager.AddNamespace("ex", "http://www.example.com"); 
xc.Add("//ex:book/ex:title"); 

XPathReader xpr  = new XPathReader("books.xml", xc); 

尋找貸款書籍:XPathReader 解決方案

現在我們已經看過 XPathReader,現在是時候查看使用 XmlTextReader來比較處理 XML 檔案的改善程度。 下列程式碼範例會使用「尋找貸款書籍:XmlTextReader 解決方案」一節中的 XML 檔案,並應該產生下列輸出:

Sanjay was loaned XML Bible by Elliotte Rusty Harold 
Sander was loaned Definitive XML Schema by Priscilla Walmsley

XPathReader Sample: 
using System; 
using System.IO; 
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;

public class Test{
static void Main(string[] args) {

      try{ 
         XmlTextReader xtr = new XmlTextReader("books.xml"); 
         
         XPathCollection xc = new XPathCollection();
         int onloanQuery = xc.Add("/books/book[@on-loan]");
         int titleQuery  = xc.Add("/books/book[@on-loan]/title");
         int authorQuery = xc.Add("/books/book[@on-loan]/author");

         XPathReader xpr  = new XPathReader(xtr, xc); 

         while (xpr.ReadUntilMatch()) {

            if(xpr.Match(onloanQuery)){
               Console.Write("{0} was loaned ", xpr.GetAttribute("on-loan"));
            }else if(xpr.Match(titleQuery)){
               Console.Write(xpr.ReadString());
            }else if(xpr.Match(authorQuery)){
               Console.WriteLine(" by {0}", xpr.ReadString());
            }

         }         

         Console.ReadLine(); 

   }catch(XPathReaderException xpre){
      Console.WriteLine("XPath Error: " + xpre);   
   }catch(XmlException xe){
         Console.WriteLine("XML Parsing Error: " + xe);
      }catch(IOException ioe){
         Console.WriteLine("File I/O Error: " + ioe);
      }
   }  
}

從原始程式碼區塊大幅簡化此輸出,幾乎是有效率的記憶體,而且非常類似于使用正則運算式處理文字資料流程。 我們似乎已達到 Tim Bray 適合用於處理大型 XML 資料流程的 XML 程式設計模型。

XPathReader 的運作方式

XPathReader會建立編譯成抽象語法樹狀結構的 XPath 運算式集合, (AST) ,然後在從基礎XmlReader接收傳入節點時逐步執行此語法樹狀結構,以比對 XML 節點。 藉由逐步執行 AST 樹狀結構,就會產生查詢樹狀結構,並推送至堆疊。 查詢所要比對的節點深度會根據XmlReaderDepth屬性來計算並進行比較,因為 XML 資料流程中遇到節點。 產生 XPath 運算式 AST 的程式碼是從System.Xml中類別的基礎程式碼取得 。Xpath,可在 共用來源通用語言基礎結構 1.0 版本中作為原始程式碼的一部分使用。

AST 中的每個節點都會實作定義下列三種方法的 IQuery 介面:

        internal virtual object GetValue(XPathReader reader);
        internal virtual bool MatchNode(XPathReader reader);
        internal abstract XPathResultType ReturnType()

GetValue方法會傳回輸入節點的值,相對於查詢運算式的目前層面。 MatchNode方法會測試輸入節點是否符合剖析的查詢內容,而ReturnType屬性則指定查詢運算式所評估的 XPath 類型。

XPathReader 的未來計畫

根據 Microsoft 中各種人員找到XPathReader的實用程度,包括此實作變化隨附的BizTalk Server,我決定為專案建立 GotDotNet 工作區。 我想要看到幾個功能,例如將部分函式從 EXSLT.NET 專案 整合至 XPathReader,並支援更廣泛的 XPath。 想要進一步開發 XPathReader 的開發人員可以加入 GotDotNet 工作區。

結論

XPathReader藉由利用 XPath 的強大功能,並將它與XmlReader的提取式 XML 剖析器模型彈性結合,提供處理 XML 資料流程的強大方式。 System.Xml的組合設計允許一個將XPathReader分層至XmlReader的其他實作,反之亦然。 使用 XPathReader 處理 XML 資料流程的速度幾乎與使用 XmlTextReader一樣快,但同時與使用 XmlDocument的 XPath 相同。 真正地,這兩個世界都是最好的。

Dare Obasanjo是 Microsoft WebData 小組的成員,除了其他事項之外,還開發.NET FrameworkSystem.Xml和 System.Data 命名空間內的元件、Microsoft XML Core Services (MSXML) ,以及 MICROSOFT Data Access Components (MDAC) 。

Howard Actors 是 WebData XML 小組上測試的軟體設計工程師,而且是 XPathReader 的主要開發人員。

歡迎在 GotDotNet 的 極端 XML 消息板上 張貼任何關於本文的問題或批註。