65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 XPath 导航文件系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (11投票s)

2012 年 8 月 11 日

CPOL

5分钟阅读

viewsIcon

32339

downloadIcon

428

如何为文件系统实现 XPathNavigator

引言

本文有两个目标

  1. 它展示了如何创建自己的 XPathNavigator 实现,并使用它来评估 XPath 表达式,以及对并非为此目的设计的结构应用 XSLT 转换。
  2. 它提出了一种处理文件和文件夹的新替代方法,一些人可能会觉得它很有用。

什么是 XPathNavigator

XPathNavigator 是实现 XPath 文档模型并提供一种方法来遍历 XML 节点和评估 XPath 表达式的系统抽象类。与 XmlNodeXNode 不同,XPathNavigator 是一个游标,可以指向 XML 树中的任何节点并移动到另一个节点。XPathNavigator 也用作 XslCompiledTransform 的输入,因此 XPathNavigator 的任何实现都可以使用 XSLT 样式表进行转换。

XPathNavigator 实现存在于 .NET 的所有 XML 模型中,包括 XmlDocument 和 LINQ to XML。通常,可以为实现 IXPathNavigable 接口的类创建 XPathNavigator 继承者的实例。此接口包含一个名为 CreateNavigator 的方法。XmlNodeXPathDocument(一种仅通过 XPathNavigator 模型提供只读访问的特殊快速模型)类实现了 IXPathNavigable。但这并非总是如此。用于处理 XML 的最新库 - LINQ to XML - 允许使用扩展方法创建 XPathNavigator

值得一提的是,系统中集成的 XPathNavigator 实现都不是公共的。

如何实现 XPathNavigator

XPathNavigator 包含 116 个公共成员,其中 112 个可以被覆盖。好消息是,只有 20 个是抽象的,即必须实现。

它们是: 

属性

XmlNameTable NameTable
XPathNodeType NodeType
string LocalName
string Name
string NamespaceURI
string Prefix
string BaseURI
bool IsEmptyElement

方法

XPathNavigator Clone()
bool MoveToFirstAttribute()
bool MoveToNextAttribute()
bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope)
bool MoveToNextNamespace(XPathNamespaceScope namespaceScope)
bool MoveToNext()
bool MoveToPrevious()
bool MoveToFirstChild()
bool MoveToParent()
bool MoveTo(XPathNavigator other)
bool MoveToId(string id)
bool IsSamePosition(XPathNavigator other)

实现这 20 个成员足以完成一个工作的只读 XPathNavigator 实现。如果您想支持模型的修改,则需要覆盖更多成员,至少是那些会抛出 NotSupportedException 的成员。

void SetValue(string value)
XmlWriter PrependChild()
XmlWriter AppendChild()
XmlWriter InsertAfter()
XmlWriter InsertBefore()
XmlWriter CreateAttributes()
XmlWriter ReplaceRange(XPathNavigator lastSiblingToReplace)
void DeleteRange(XPathNavigator lastSiblingToDelete)

正如您所见,为了实现一个可写的 XPathNavigator,您还必须实现 XmlWriter

在本文中,我们只需要只读功能。 

文件系统作为 XML

XPath 模型实际上不仅可以应用于 XML,还可以应用于任何树状结构,因为文件系统由文件和目录组成。在我们的模型中,我们将文件和目录视为 XML 元素。在这个简单的实现中,我使用文件名作为 XML 元素的名称。这很方便,因为通过这种解决方案,XPath 表达式会非常类似于文件路径或本地 URI。出于同样的原因,我没有为文件节点使用任何命名空间或前缀:如果它们有前缀,XPath 就会变得更加繁琐。

我将文件属性(如完整名称、扩展名和文件属性)渲染为 XML 属性。为了简单起见,此实现中将不会有任何文本节点,并且文件的任何部分内容都不会包含在树中。

为文件系统实现 XPathNavigator

如上所述,XPathNavigator 是一个游标,可以围绕树移动并指向树的任何部分,包括元素(对我们来说是文件和文件夹)和属性。因此,我们将 XPathNavigator 中的所有调用委托给一个辅助类的内部实例。每种类型的节点将有一个类。

由于我们决定不处理命名空间,因此我们不需要实现某些成员。因此,以下 getter 将仅返回 String.Empty

public override string NamespaceURI { get { return String.Empty; } }
public override string Prefix  { get { return String.Empty; } }
public override string BaseURI  { get { return String.Empty; } }

以下方法将返回 false。

public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) { return false; }
public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) { return false; }

属性 Name 将返回与 LocalName 相同的值

public override string Name { return LocalName; }

我们将不为我们的节点定义 ID,因此 MoveToId 也将返回 false

public override bool MoveToId(string id) { return false; }

有些人看到 NameTable 属性可能会感到不安,因为它返回一个抽象类实例,而没有公共实现 XmlNameTable。此属性在 XSL 转换期间被调用,旨在通过实例而不是值来比较字符串来提高性能。希望您可以安全地从中返回 null。

public override XmlNameTable NameTable { get { return null; } }

哇!我们已经实现了 20 个强制成员中的 8 个!其余 12 个将委托给前面提到的内部实例。您可以将其视为导航器的状态对象。对于以 Move* 开头并返回布尔值的方法,我们将更改签名:状态中的方法将返回与导航器移动后将指向的节点关联的新状态实例。如果无法将导航器移动到指定位置,我们将返回 null,在这种情况下,XPathNavigator 中的相应方法将返回 false。

请参阅下面代表内部状态的抽象类的定义

/// <summary>
/// Abstract class that defines behaviour of the XPath navigator positioned on a specific item
/// </summary>
internal abstract class XPathItem
{
    public abstract string Name { get; }
    public abstract XPathItem MoveToFirstAttribute();
    public abstract XPathItem MoveToNextAttribute();
    public abstract XPathItem MoveToFirstChild();
    public abstract XPathItem MoveToNext();
    public abstract XPathItem MoveToPrevious();
    public abstract XPathNodeType NodeType { get; }
    public virtual string Value { get { return string.Empty; } }
    public abstract bool IsEmptyElement { get; }
    public abstract XPathItem MoveToParent();
    public abstract XPathItem MoveToAttribute(string name);
    public abstract bool IsSamePosition(XPathItem item);
}

现在我们可以实现我们 XPathNavigator 的其余部分了。

/// <summary>
/// An <see cref="XPathNavigator"/> implementation,
/// that allows navigation through local file system as if it were XML, 
/// with files and folders representing XML elements
/// </summary>
public class FileSystemXPathNavigator : XPathNavigator
{
    private XPathItem _item;

    /// <summary>
    /// Constructs a new instance of the file system XPath navigator rooted at the specified file or folder
    /// </summary>
    /// <param name="fileName">A path to the local file system item, 
    /// which will be the root element in the traversed XPath document model</param>
    public FileSystemXPathNavigator(string fileName)
    {
        _item = RootItem.CreateItem(fileName);
    }

    private FileSystemXPathNavigator(XPathItem item)
    {
        _item = item;
    }

    public override XPathNavigator Clone()
    {
        return new FileSystemXPathNavigator(_item);
    }

    public override bool IsEmptyElement
    {
        get { return _item.IsEmptyElement ; }
    }

    public override bool IsSamePosition(XPathNavigator other)
    {
        var o = other as FileSystemXPathNavigator;
        return o != null && o._item.IsSamePosition(_item);
    }

    public override string LocalName
    {
        get { return _item.Name; }
    }

    public override bool MoveTo(XPathNavigator other)
    {
        var o = other as FileSystemXPathNavigator;
        if (o != null)
        {
            _item = o._item;
            return true;
        }
        return false;
    }

    public override bool MoveToFirstAttribute()
    {
        return MoveToItem(_item.MoveToFirstAttribute());
    }

    private bool MoveToItem(XPathItem newItem)
    {
        if (newItem == null) return false;
        _item = newItem;
        return true;
    }

    public override bool MoveToFirstChild()
    {
        return MoveToItem(_item.MoveToFirstChild());
    }

    public override bool MoveToNext()
    {
        return MoveToItem(_item.MoveToNext());
    }

    public override bool MoveToNextAttribute()
    {
        return MoveToItem(_item.MoveToNextAttribute());
    }

    public override bool MoveToParent()
    {
        return MoveToItem(_item.MoveToParent());
    }

    public override bool MoveToPrevious()
    {
        return MoveToItem(_item.MoveToPrevious());
    }

    public override XPathNodeType NodeType
    {
        get { return _item.NodeType; }
    }

    public override string Value
    {
        get { return _item.Value; }
    }

    public override bool MoveToAttribute(string localName, string namespaceURI)
    {
        if (namespaceURI != String.Empty) return false;
        return MoveToItem(_item.MoveToAttribute(localName));
    }

    //more members
}

工作对象实现

以下类层次结构实现了对文件系统的导航并将其表示为 XML。 

class diagram

我不想深入讨论每个类的实现细节。只是涵盖一些最有趣的项目

  • System.IO.FileSystemInfo 类用于作为文件系统项属性的来源
  • protected internal abstract FileSystemInfo FileSystemInfo { get ; }
  • 由于文件名允许比 XML 元素更多的特殊符号,因此需要进行编码
  • public override string Name
    {
        get { return XmlConvert.EncodeLocalName(FileSystemInfo.Name); }
    }
  • 文件的完整路径决定了它们的等价性
  • public virtual string Path
    {
        get { return FileSystemInfo.FullName; }
    }
    public override bool IsSamePosition( XPathItem _item)
    {
        var fsi = _item as FileSystemItem;
        return fsi != null && Path == fsi.Path;
    }
  • 父节点,在大多数情况下对应于目录,负责获取下一个和上一个节点。为此,目录中的文件列表和当前索引存储在每个 DirectoryItem 实例中。
  • 属性项很灵活,可以定位在文件或目录的任何属性上。AttributeInfo 类是每种属性(很大程度上它们复制了 FileSystemInfo 类的属性)的享元。
  • 标志属性,如隐藏、系统等,默认视为 false。

控制台实用程序

我还创建了一个名为 xfiles 的控制台实用程序,它使用 FileSystemXPathNavigator 来评估 XPath 表达式并在文件系统上执行 XSLT 转换。如果使用 /? 参数执行,它会显示以下信息

Evaluates XPath expression and performs XSLT trasformation on the local file system
Copyright: Boris Dongarov, 2012

Usage:
xfiles.exe [path] -xpath:<xpath-expression>
  or
xfiles.exe [path] -xsl:<xslt-file>
where
path - a path to a file or a folder that will be a root of the XPath model (the current directory by default)
-xpath - an XPath expression to be applied to the file system
-xsl - a file name of the XSLT stylesheet to be applied to the file system
If neither option is specified, the XML will be rendered to the standard output

一些示例

xfiles.exe

以 XML 格式显示当前目录的内容。

xfiles.exe "C:\Program Files" -xpath:sum(//*[@Extension='.exe']/@Length)

显示“Program Files”文件夹中所有可执行文件的总大小。

xfiles.exe "C:\Program Files" -xpath://*[@Temporary]/@FullName > tempfiles.txt

将“Program Files”文件夹中的临时文件列表写入指定的文件。

xfiles C:\WINDOWS\SYSTEM32 -xsl:dirstructure.xslt > system32.htm

使用自定义 XSLT 样式表将系统目录的内容转换为 HTML。

结论

我希望本文向您展示了 XPathNavigator 的强大功能,并将帮助您实现自己的。您也可能会发现 xfiles 实用程序及其提供的工作方式(处理文件和目录)很有用。您可以在本文附加的源代码中找到所有源代码。   

© . All rights reserved.