跨越不同存放區的 XPathNavigator
XPathNavigator 提供了可以在任何實作 IXPathNavigable 介面的存放區上執行 XPath 查詢的功能。實施 IXPathNavigable 介面的類別為 XPathDocument、XmlDocument 和 XmlDataDocument。
XPathDocument 類別允許在 XML 資料流上的 XPath 查詢。同時它也針對執行 XPath 查詢和 XSLT 轉換方面進行高度最佳化。當最優先考慮 XPath 的效能時,此類別是最適合 XSLT 轉換的類別。
XmlDocument 類別除了提供可編輯 XML 的 DOM 檢視之外,也允許在資料上使用 XPath。XPathDocument 和 XmlDocument 對相同的 XML 的查詢結果上擁有相同的行為。對於 XPath 查詢而言,XmlDocument 的效能比 XPathDocument 差,這是因為此類別中編輯功能的關係。
XmlDataDocument 類別提供完全相容的 DOM,它和 DataSet 是同步的。它繼承 XmlDocument 類別而被用來取得關聯資料的階層式 XML 檢視。它可用來對儲存於 Dataset 以 XML 呈現的資料進行 Xpath 查詢。如需 XmlDataDocument 的詳細資訊,請參閱使用 XmlDataDocument 同步處裡 DataSet。
為了能在這些類別中執行 XPath 查詢,請叫用 (Invoke) CreateNavigator 函式以傳回 XPathNavigator。然後便可以使用 XPathNavigator 在文件上執行 XPath 查詢,如本章節中其他部分中所述。
您也可以擴充 XPathNavigator 類別,以便定義自己的巡覽器。這需要建立虛擬節點結構並實作方法來巡覽這個結構。一旦撰寫了節點結構,便啟用了執行 XPath 查詢的能力。在 XPathNavigator 類別中的 Select 方法已被實作,並且只需要這個結構來執行。下列範例在檔案系統上實作 XPathNavigator。
從建立虛擬節點結構開始。這個結構會公開基礎檔案系統,以在 XML Infoset 中包含所有的資訊。這個 XML 結構透過覆寫傳回關於節點資訊之 XPathNavigator 類別中的方法來公開。在這個範例中,目錄和檔案將以項目的方式公開。檔案系統樹狀結構將被鏡射至公開的 XML Infoset 的樹狀結構中。表示目錄的項目會把目錄的區域名稱和建立時間當成屬性。表示檔案的項目會把檔案的區域名稱、建立時間和長度當成屬性。您可以對檔案系統執行查詢,例如,/abc/xml
會選取包含在具有 abc
區域名稱之目錄中,所有區域名稱為 xml
的目錄或檔案。
為了公開這個結構,您必須覆寫下列節點屬性。
- NodeType
- LocalName
- Nane
- Prefix
- Value
- BaseURI
- IsEmptyElement
- XmlLang
- NameTable
為了公開這個結構,您必須覆寫下列屬性存取子 (Accessor)。
- HasAttributes
- GetAttributes
- MoveToAttributes
- MoveToFirstAttributes
- MoveToNextAttributes
為了公開這個結構,您必須覆寫下列命名空間存取子。
- GetNamespace
- MoveToNamespace
- MoveToFirstNamespace
- MoveToNextNamespace
使用這些方法和屬性,可以將基礎結構完全公開為 XML Infoset。
此外,所有的 LocalName、NameSpaceUri 和 Prefix 字串必須加入至由 NameTable 屬性所提供的 NameTable 中。當 LocalName、NamespaceURI 和 Prefix 屬性被傳回時,字串應該是從 NameTable 傳回。名稱之間的比較是透過物件比較完成,而不是透過明顯比較慢的字串比較完成。
此外,用來巡覽這個虛擬節點結構的方法也同樣需要實作。這些方法包括:
- MoveToNext
- MoveToPrevious
- MoveToFirst
- MoveToFirstChild
- MoveToParent
- MoveToRoot
- MoveTo
- MoveToId
- IsSamePosition
- HasChildren
這些方法將基礎結構公開為樹狀結構。一旦實作了這些方法和屬性,所有 XPath 查詢便可在此虛擬節點集上作業。例如,下列程式碼建立可以巡覽硬碟目錄之自訂的 XPathNavigator。程式以 C:\Program Files 做為巡覽的起點,並反覆在檔案與資料夾之間遊走,這對於某些機器而言,需傳送大量輸出到主控台。您可以修改程式碼以便從您硬碟目前的資料夾開始。
using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
public class Sample {
static FileSystemNavigator fsn;
public static void Main() {
// This is the hard code for C:\ProgramFiles. You can modify this
// to point to a folder on your own machine for test purposes.
fsn = new FileSystemNavigator(@"C:\Program Files");
if (fsn.MoveToFirstChild()) {
ShowNavigator(String.Empty);
}
}
public static void ShowNavigator(string indent) {
do {
Console.WriteLine("{0}Name: {1}", indent, fsn.Name);
if (fsn.HasChildren) {
fsn.MoveToFirstChild();
ShowNavigator(indent + " ");
fsn.MoveToParent();
}
} while (fsn.MoveToNext());
}
}
public class FileSystemNavigator: XPathNavigator
{
static int[] NumberOfAttributes = { 2, 3 };
//The NodeTypes used.
public enum NodeTypes { Root, Element, Attribute, Text };
internal NavigatorState state;
//Attribute names: For example a type 1 element will have a type 2 attribute named "Length"
static String[][] AttributeIds = new String[2][]
{
new String[] {"Name", "CreationTime"},
new String[] {"Name", "CreationTime", "Length"}
};
NameTable nametable;
public FileSystemNavigator( String rootNode )
{
FileSystemInfo document = Directory.CreateDirectory(rootNode);
nametable = new NameTable();
nametable.Add(String.Empty);
//if the Directory does not exist then rootNode must be a file.
if( !document.Exists )
{
//create the file if it does not already exists
FileStream tempStream = File.Open(rootNode,System.IO.FileMode.OpenOrCreate);
tempStream.Close();
document = new FileInfo(rootNode);
}
if( document.Exists )
{
state = new NavigatorState(document);
}
else
{
throw (new Exception("Root node must be a directory or a file"));
}
}
public FileSystemNavigator( FileSystemInfo document )
{
nametable = new NameTable();
nametable.Add(String.Empty);
if( document.Exists )
{
state = new NavigatorState(document);
}
else
{
throw (new Exception("Root node must be a directory or a file"));
}
}
public FileSystemNavigator( FileSystemNavigator navigator )
{
state = new NavigatorState(navigator.state);
nametable = (NameTable)navigator.NameTable;
}
public override XPathNavigator Clone()
{
return new FileSystemNavigator(this);
}
//NodeProperties
public override XPathNodeType NodeType
{
get
{
switch (state.Node)
{
case NodeTypes.Root :
return XPathNodeType.Root;
case NodeTypes.Element :
return XPathNodeType.Element;
case NodeTypes.Attribute :
return XPathNodeType.Attribute;
case NodeTypes.Text :
return XPathNodeType.Text;
}
return XPathNodeType.All;
}
}
public override string LocalName
{
get
{
nametable.Add(Name);
return nametable.Get(Name);
}
}
public override string Name
{
get
{
switch (state.Node)
{
case NodeTypes.Text :
return state.TextValue;
case NodeTypes.Attribute :
return state.AttributeText;
case NodeTypes.Element :
return state.ElementText;
default :
return String.Empty;
}
}
}
public override string NamespaceURI
{
get
{
return nametable.Get(String.Empty);
}
}
public override string Prefix
{
get { return nametable.Get(String.Empty); }
}
public override string Value
{
get {
return state.TextValue;
}
}
public override String BaseURI
{
get { return String.Empty; }
}
public override bool IsEmptyElement
{
get
{
if(state.ElementType == 1)
return true;
else
return false;
}
}
public override string XmlLang
{
get{return "en-us";}
}
public override XmlNameTable NameTable
{
get
{
return nametable;
}
}
//Attribute Accessors
public override bool HasAttributes
{
get
{
if((state.Node != NodeTypes.Root) && (state.Node != NodeTypes.Attribute) && (state.Node != NodeTypes.Text))
return true;
else
return false;
}
}
public override string GetAttribute( string localName, string namespaceURI )
{
if( HasAttributes)
{
int i;
for(i = 0; i < NumberOfAttributes[state.ElementType]; i++)
{
if( AttributeIds[state.ElementType][i] == localName )
break;
}
if( i < NumberOfAttributes[state.ElementType] )
{
int TempAttribute = state.Attribute;
NodeTypes TempNodeType = state.Node;
state.Attribute = i;
state.Node = NodeTypes.Attribute;
String AttributeValue = state.TextValue;
state.Node = TempNodeType;
state.Attribute = TempAttribute;
return AttributeValue;
}
}
return String.Empty;
}
public override bool MoveToAttribute( string localName, string namespaceURI )
{
if( state.Node == NodeTypes.Attribute )
MoveToElement();
if( state.Node == NodeTypes.Element )
{
int i;
for(i = 0; i < AttributeCount; i++)
if( AttributeIds[state.ElementType][i] == localName )
{
state.Attribute = i;
state.Node = NodeTypes.Attribute;
return true;
}
}
return false;
}
public override bool MoveToFirstAttribute()
{
if( state.Node == NodeTypes.Attribute )
MoveToElement();
if( AttributeCount > 0 )
{
state.Attribute = 0;
state.Node = NodeTypes.Attribute;
return true;
}
return false;
}
public override bool MoveToNextAttribute()
{
int TempAttribute = -1;
if( state.Node == NodeTypes.Attribute )
{
TempAttribute = state.Attribute;
MoveToElement();
}
if( (TempAttribute + 1) < AttributeCount )
{
state.Attribute = TempAttribute + 1;
state.Node = NodeTypes.Attribute;
return true;
}
state.Node = NodeTypes.Attribute;
state.Attribute = TempAttribute;
return false;
}
//Namespace Accesors
public override string GetNamespace(string localname)
{
return String.Empty;
}
public override bool MoveToNamespace(string Namespace)
{
return false;
}
public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope)
{
return false;
}
public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope)
{
return false;
}
//Tree Navigation
public override bool MoveToNext()
{
int NextElement = IndexInParent + 1;
FileSystemNavigator TempState = (FileSystemNavigator) this.Clone();
if ( MoveToParent() )
{
if( MoveToChild(NextElement) )
return true;
}
this.state = new NavigatorState(TempState.state);
return false;
}
public override bool MoveToPrevious()
{
int NextElement = IndexInParent - 1;
FileSystemNavigator TempState = (FileSystemNavigator) this.Clone();
if ( MoveToParent() )
{
if( MoveToChild(NextElement) )
return true;
}
this.state = new NavigatorState(TempState.state);
return false;
}
public override bool MoveToFirst()
{
FileSystemNavigator TempState = (FileSystemNavigator) this.Clone();
if ( MoveToParent() )
{
if( MoveToChild(0) )
return true;
}
this.state = new NavigatorState(TempState.state);
return false;
}
public override bool MoveToFirstChild()
{
FileSystemNavigator TempState = (FileSystemNavigator) this.Clone();
if( MoveToChild(0) )
return true;
this.state = new NavigatorState(TempState.state);
return false;
}
public override bool MoveToParent()
{
switch(state.Node)
{
case NodeTypes.Root:
return false;
default:
if( state.Root != state.Doc.FullName )
{
if( state.Doc is DirectoryInfo )
state.Doc = ((DirectoryInfo) state.Doc).Parent;
else if( state.Doc is FileInfo )
state.Doc = ((FileInfo) state.Doc).Directory;
state.Node = NodeTypes.Element;
state.Attribute = -1;
state.ElementType = 0;
if( state.Root != state.Doc.FullName )
{
FileSystemInfo[] FileSystemEnumerator = ( ((DirectoryInfo) state.Doc).Parent).GetFileSystemInfos();
for(int i = 0; i < FileSystemEnumerator.Length; i++ )
{
if( FileSystemEnumerator[i].Name == state.Doc.Name )
{
state.ElementIndex = i;
}
}
}
else
{
state.ElementIndex = 0;
}
return true;
}
else
{
MoveToRoot();
return true;
}
}
}
public override void MoveToRoot()
{
state.Node=NodeTypes.Root;
state.Doc = new FileInfo(state.Root);
state.Attribute = -1;
state.ElementType = -1;
state.ElementIndex = -1;
}
public override bool MoveTo( XPathNavigator other )
{
if( other is FileSystemNavigator )
{
this.state = new NavigatorState( ((FileSystemNavigator) other).state);
return true;
}
return false;
}
public override bool MoveToId( string id )
{
return false;
}
public override bool IsSamePosition( XPathNavigator other )
{
if( other is FileSystemNavigator )
{
if( state.Node == NodeTypes.Root )
{
return (((FileSystemNavigator) other).state.Node == NodeTypes.Root);
}
else
{
return (state.Doc.FullName == ((FileSystemNavigator) other).state.Doc.FullName);
}
}
return false;
}
public override bool HasChildren
{
get
{
return (ChildCount > 0);
}
}
/***************Helper Methods*****************************************/
//This is a helper method. Move the XPathNavigator from an attribute
// to its associated element.
public bool MoveToElement()
{
state.Attribute = -1;
state.Node = NodeTypes.Element;
if ( state.Doc is DirectoryInfo )
state.ElementType = 0;
else
state.ElementType = 1;
return true;
}
//Gets the index of this node if it is an element or the index of
// the element node associated with the attribute.
public int IndexInParent
{
get
{
return state.ElementIndex;
}
}
//Helper method. Move to child i of the current node.
public bool MoveToChild( int i )
{
if( i >= 0 )
{
if( state.Node == NodeTypes.Root && i == 0)
{
state.Doc = Directory.CreateDirectory(state.Root);
state.ElementType = 0;
if( !state.Doc.Exists )
{
FileStream tempStream = File.Open(state.Root,System.IO.FileMode.OpenOrCreate);
tempStream.Close();
state.Doc = new FileInfo(state.Root);
state.ElementType = 1;
}
state.Node = NodeTypes.Element;
state.Attribute = -1;
state.ElementIndex = 0;
return true;
}
else if( state.Node == NodeTypes.Element && state.ElementType == 0 )
{
FileSystemInfo[] DirectoryEnumerator = ( (DirectoryInfo) state.Doc).GetFileSystemInfos();
if( i < DirectoryEnumerator.Length )
{
state.Node = NodeTypes.Element;
state.Attribute = -1;
state.ElementIndex = i;
if( DirectoryEnumerator[i] is DirectoryInfo)
{
state.Doc = DirectoryEnumerator[i];
state.ElementType = 0;
}
else if( DirectoryEnumerator[i] is FileInfo)
{
state.Doc = DirectoryEnumerator[i];
state.ElementType = 1;
}
return true;
}
}
}
return false;
}
//returns the number of attributes that the current node has
public int AttributeCount
{
get
{
if( state.Node != NodeTypes.Root )
{
return NumberOfAttributes[state.ElementType];
}
return 0;
}
}
//Helper method. Returns the number of children that the current node has.
public int ChildCount
{
get
{
switch(state.Node)
{
case NodeTypes.Root:
return 1;
case NodeTypes.Element:
if( state.ElementType == 0 )
{
return (((DirectoryInfo) state.Doc).GetFileSystemInfos()).Length;
}
return 0;
default:
return 0;
}
}
}
//This class keeps track of the state the navigator is in.
internal class NavigatorState
{
//Represents the element that the navigator is currently at.
public FileSystemInfo doc;
//The directory or file at the top of the tree
String root;
//The type of attribute that the current node is. -1 if the
// navigator is not currently positioned on an attribute.
public int attribute;
//elementType of 0 is a directory and elementType of 1 is a file
public int elementType;
public int elementIndex;
//The type of the current node
public NodeTypes node;
public NavigatorState(FileSystemInfo document)
{
Doc = document;
Root = doc.FullName;
Node = NodeTypes.Root;
Attribute = -1;
ElementType = -1;
ElementIndex = -1;
}
public NavigatorState(NavigatorState NavState)
{
Doc = NavState.Doc;
Root = NavState.Root;
Node = NavState.Node;
Attribute = NavState.Attribute;
ElementType = NavState.ElementType;
ElementIndex = NavState.ElementIndex;
}
public FileSystemInfo Doc
{
get
{
return doc;
}
set
{
doc = value;
}
}
public String Root
{
get
{
return root;
}
set
{
root = value;
}
}
public int Attribute
{
get
{
return attribute;
}
set
{
attribute = value;
}
}
public int ElementType
{
get
{
return elementType;
}
set
{
elementType = value;
}
}
public int ElementIndex
{
get
{
return elementIndex;
}
set
{
elementIndex = value;
}
}
public NodeTypes Node
{
get
{
return node;
}
set
{
node = value;
}
}
//Returns the TextValue of the current node
public String TextValue
{
get
{
switch(Node)
{
case NodeTypes.Root :
return null;
case NodeTypes.Element :
return null;
case NodeTypes.Attribute :
if( ElementType == 0 )
{
DirectoryInfo dInfo = (DirectoryInfo ) Doc;
switch(Attribute)
{
case 0: return dInfo.Name;
case 1: return dInfo.CreationTime.ToString();
}
}
else if( ElementType == 1 )
{
FileInfo fInfo = (FileInfo ) Doc;
switch(Attribute)
{
case 0: return fInfo.Name;
case 1: return (String) fInfo.CreationTime.ToString();
case 2: return (String) fInfo.Length.ToString();
}
}
break;
case NodeTypes.Text :
return null;
}
return null;
}
}
//returns the value of the attribute
public String AttributeText
{
get
{
if( Node == NodeTypes.Attribute )
return AttributeIds[ElementType][Attribute];
return null;
}
}
//Returns the name of the element.
public String ElementText
{
get
{
return doc.Name;
}
}
}
}