XPathNavigator over Different Stores
The XPathNavigator facilitates the ability to perform XPath queries over any store implementing the IXPathNavigable interface. The classes that implement the IXPathNavigable interface are XPathDocument, XmlDocument, and XmlDataDocument.
The XPathDocument class allows XPath queries over streams of XML. It is highly optimized to perform XPath queries and XSLT transformations. This is the preferred class for XSLT transformations when XPath performance is the highest priority.
The XmlDocument class, in addition to providing an editable DOM view of the XML, also allows XPath over the data. The XPathDocument and the XmlDocument have equivalent behavior in terms of the results of queries over the same XML. The XmlDocument is less performant than the XPathDocument for XPath queries because of the editing capabilities in this class.
The XmlDataDocument class provides a fully compliant DOM that is synchronized with a Dataset. It inherits from the XmlDocument class and is used to obtain a hierarchical XML view of relational data. It can be used to execute XPath queries on the XML representation of the data stored in a Dataset.. For more information about the XmlDataDocument, see Synchronizing a DataSet with an XmlDataDocument.
In order to perform XPath queries on any of these classes, invoke the CreateNavigator function to return an XPathNavigator. The XPathNavigator can then be used to perform XPath queries over the document as described in the rest of this section.
It is also possible to extend the XPathNavigator class in order to define your own navigators. This requires creating a virtual node structure and implementing methods to navigate this structure. Once this node structure is written, the ability to perform XPath queries is enabled. The Select methods in the XPathNavigator class are implemented, and need only this structure to work. The following example implements an XPathNavigator over a file system.
Begin by creating the virtual node structure. This is a structure that exposes the underlying file system to contain all the information in the XML Infoset. This XML structure is exposed by overriding the methods in the XPathNavigator class that return information about nodes. In this example, directories and files will be exposed as elements. The tree structure of the file system will be mirrored in the tree structure of the exposed XML Infoset. Elements representing directories will have the local name and creation time of the directory as attributes. Elements representing files will have the local name, creation time, and length of the file as attributes. You can perform queries over this file system like /abc/xml
which selects all directories or files with local name xml
which are contained in directories with local name abc
.
In order to expose this structure, the following node properties must be overridden.
- NodeType
- LocalName
- Name
- Prefix
- Value
- BaseURI
- IsEmptyElement
- XmlLang
- NameTable
In order to expose this structure, the following attribute accessors must be overridden.
- HasAttributes
- GetAttributes
- MoveToAttributes
- MoveToFirstAttributes
- MoveToNextAttributes
In order to expose this structure, the following namespaces accessors must be overridden.
- GetNamespace
- MoveToNamespace
- MoveToFirstNamespace
- MoveToNextNamespace
Using these methods and properties, it is possible to fully expose the underlying structure as an XML Infoset.
Additionally, all LocalName, NameSpaceUri and Prefix strings must be added to a NameTable, given by the NameTable property. When the LocalName, NamespaceURI, and Prefix properties are returned, the string returned should come from the NameTable. Comparisons between names are done by object comparisons rather than by string comparisons, which are significantly slower.
In addition, methods to navigate this virtual node structure must also be implemented. These include:
- MoveToNext
- MoveToPrevious
- MoveToFirst
- MoveToFirstChild
- MoveToParent
- MoveToRoot
- MoveTo
- MoveToId
- IsSamePosition
- HasChildren
These methods expose the underlying structure as a tree. Once these methods and properties are implemented all XPath queries work over this virtual node set. For example, the following code creates a custom XPathNavigator that navigates through the directory on a hard drive. The program has C:\Program Files as the starting point of the navigation, and recursively walks through the files and folders, which for some machines, is quite a bit of output to send to the console. You can modify the code to start in any folder that is present on your hard drive.
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;
}
}
}
}