如何将文件名列表转换为树形结构?

8

我有一个字符串数组,存储了一些文件路径:

path/to/folder/file.xxx
path/to/other/
path/to/file/file.xx
path/file.x
path/

我该如何将这个列表转换为树形结构?到目前为止,我有以下内容:
/// <summary>
/// Enumerates types of filesystem nodes.
/// </summary>
public enum FilesystemNodeType
{
    /// <summary>
    /// Indicates that the node is a file.
    /// </summary>
    File,

    /// <summary>
    /// Indicates that the node is a folder.
    /// </summary>
    Folder
}

/// <summary>
/// Represents a file or folder node.
/// </summary>
public class FilesystemNode
{
    private readonly ICollection<FilesystemNode> _children; 

    /// <summary>
    /// Initializes a new instance of the <see cref="FilesystemNode"/> class.
    /// </summary>
    public FilesystemNode()
    {
        _children = new LinkedList<FilesystemNode>();
    }

    /// <summary>
    /// Gets or sets the name of the file or folder.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the full path to the file or folder from the root.
    /// </summary>
    public string Path { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the node is a file or folder.
    /// </summary>
    public FilesystemNodeType Type { get; set; }

    /// <summary>
    /// Gets a list of child nodes of this node. The node type must be a folder to have children.
    /// </summary>
    public ICollection<FilesystemNode> Children
    {
        get
        {
            if (Type == FilesystemNodeType.Folder)
                return _children;

            throw new InvalidOperationException("File nodes cannot have children");
        }
    }
}

我有点不知道如何划分路径。任何以 / 结尾的路径都是目录,任何不以 / 结尾的路径都不是。

另外,如果我的输入没有包含文件夹的路径,我该怎么办呢?

例如,如果我的输入是:

path/to/file.c
path/file.c
path/

如何解释输入中不包含path/to/这一事实?

3个回答

10
这里有一个解决方案,可以生成NodeEntry项的嵌套字典(您可以根据需要替换您的文件信息类):
public class NodeEntry
{
    public NodeEntry()
    {
        this.Children = new NodeEntryCollection();
    }

    public string Key { get; set; }
    public NodeEntryCollection Children { get; set; }

}

public class NodeEntryCollection : Dictionary<string, NodeEntry>
{
    public void AddEntry(string sEntry, int wBegIndex)
    {
        if (wBegIndex < sEntry.Length)
        {
            string sKey;
            int wEndIndex;

            wEndIndex = sEntry.IndexOf("/", wBegIndex);
            if (wEndIndex == -1)
            {
                wEndIndex = sEntry.Length;
            }
            sKey = sEntry.Substring(wBegIndex, wEndIndex - wBegIndex);
            if (!string.IsNullOrEmpty(sKey)) {
                NodeEntry oItem;

                if (this.ContainsKey(sKey)) {
                    oItem = this[sKey];
                } else {
                    oItem = new NodeEntry();
                    oItem.Key = sKey;
                    this.Add(sKey, oItem);
                }
                // Now add the rest to the new item's children
                oItem.Children.AddEntry(sEntry, wEndIndex + 1);
            }
        }
    }
}

要使用上述内容,请创建一个新的集合:
        NodeEntryCollection cItems = new NodeEntryCollection();

然后,对于列表中的每一行:

        cItems.AddEntry(sLine, 0);

如何从子节点中选择子项,例如如何从directory1/subdirectory/files中获取subdirectory的子项? - m.qayyum

2
我受到了competent_tech的回答的启发,用“简单”的ObservableCollection<NodeEntry>替换了Dictionary<string, NodeEntry>,因为在这个字典中,“Key”信息将被存储两次:一次作为字典的键,一次作为NodeEntry类中的公共属性。
所以,基于“competent_tech”代码的示例如下:
public class NodeEntryObservableCollection : ObservableCollection<NodeEntry>
{
    public const string DefaultSeparator = "/";

    public NodeEntryObservableCollection(string separator = DefaultSeparator)
    {
        Separator = separator; // default separator
    }

    /// <summary>
    /// Gets or sets the separator used to split the hierarchy.
    /// </summary>
    /// <value>
    /// The separator.
    /// </value>
    public string Separator { get; set; }

    public void AddEntry(string entry)
    {
        AddEntry(entry, 0);
    }

    /// <summary>
    /// Parses and adds the entry to the hierarchy, creating any parent entries as required.
    /// </summary>
    /// <param name="entry">The entry.</param>
    /// <param name="startIndex">The start index.</param>
    public void AddEntry(string entry, int startIndex)
    {
        if (startIndex >= entry.Length)
        {
            return;
        }

        var endIndex = entry.IndexOf(Separator, startIndex);
        if (endIndex == -1)
        {
            endIndex = entry.Length;
        }
        var key = entry.Substring(startIndex, endIndex - startIndex);
        if (string.IsNullOrEmpty(key))
        {
            return;
        }

        NodeEntry item;
        item = this.FirstOrDefault(n => n.Key == key);
        if (item == null)
        {
            item = new NodeEntry(Separator) { Key = key };
            Add(item);
        }
        // Now add the rest to the new item's children
        item.Children.AddEntry(entry, endIndex + 1);
    }
}

public class NodeEntry
{
    public string Key { get; set; }

    public NodeEntryObservableCollection Children { get; set; }

    public NodeEntry(string separator = NodeEntryObservableCollection.DefaultSeparator)
    {
        Children = new NodeEntryObservableCollection(separator);
    }
}

这可以帮助我将数据绑定到 TreeView 中,就像这样:
<TreeView Name="trvMyTreeView">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:NodeEntry}" ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Key}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

有以下这样的示例代码:

IList<string> pathes = new List<string>
{
    "localhost",
    "remotehost.levelD.levelDB",
    "localhost.level1.level11",
    "localhost.level1",
    "remotehost.levelD.levelDA",
    "localhost.level2.level22",
    "remotehost.levelA",
    "remotehost",
    "remotehost.levelB",
    "remotehost.levelD",
    "localhost.level2",
    "remotehost.levelC"
};
SortedSet<string> sortedPathes = new SortedSet<string>(pathes);

var obsCollection = new NodeEntryObservableCollection(".");
foreach (var p in sortedPathes) { obsCollection.AddEntry(p); }

trvMyTreeView.ItemsSource = obsCollection;

0

将每一行按照'/'字符进行分割。如果字符串数组的长度为5,则前四个项目应该是目录,您需要测试最后一个是否为扩展名:

string.IsNullOrEmpty(new FileInfo("test").Extension)

如果像您的情况一样,即使是最后一个目录也始终有一个'/',那么拆分字符串数组的最后一项为空。
其余部分只涉及遍历树。在解析项目时,请检查第一个目录是否存在于根节点的Children属性中。如果不存在,则添加它;如果存在,则使用此目录并继续向下遍历。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接