使用未知父引用规范化路径

时间:2013-02-07 02:21:22

标签: vb.net path normalization

问题陈述

我当前的项目需要处理很多unix风格的路径,最重要的是加入路径和规范化路径。

通过规范化,我的意思是删除对当前目录(.)的所有引用,对父目录(..)的引用和冗余正斜杠(//),同时保留原意路径的“含义”。

例如:路径/foo/bar/foo/./bar以及/foo/baz/../bar都指向同一目录。但是,进行简单的字符串比较显然表明它们是不同的路径。这就是为什么我试图规范路径,以便它们都被视为相同。

执行此操作的代码实际上并不是非常难以编写。您可以在问题的底部找到我的代码。但是仍有一个问题我遇到了麻烦。

目前,用户可以输入如下所示的路径:

input:      /../../../foo/bar
normalized: /foo/bar

因为这是一个绝对路径,所以它正确地解析为根(foo/bar)目录中的/

但是,当输入是相对路径时,我不可能回溯所需的步骤量,因为我不知道父目录的名称。

input:      ../../../foo/bar
normalized: foo/bar

想象一下,完整路径是/a/b/c/d/../../../foo/bar,在这种情况下算法产生:

input:      /a/b/c/d/../../../foo/bar
normalized: /a/foo/bar

当出于某种原因,路径被分为/a/b/c/d../../../foo/bar时,就会出现问题。

input:      /a/b/c/d/
input:      ../../../foo/bar
normalized: /a/b/c/d
normalized: foo/bar

joined: /a/b/c/d/foo/bar

正如您所看到的,当标准化输出值重新连接在一起时,路径已失去其原始含义。如果我没有删除前导父引用,就不会发生这种情况。

所以我猜我有三个选项,如果是具有未知父引用的相对路径:

  1. 删除前导父引用,即使结果在技术上是错误的
  2. 保留前导父引用,即使结果在技术上未规范化
  3. 抛出错误
  4. 我希望有些天才能想出更好的主意。但如果没有,你会做什么?

    代码

    我还没有针对每个可能的用例测试我的代码,但它应该相对(双关语)稳定。

    Public MustInherit Class UnixPath
    
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Gets whether the specified path is an absolute path.
        ''' </summary>
        Public Shared Function IsAbsolute(path As String) As Boolean
            Return path.StartsWith(UnixPath.Separator, StringComparison.InvariantCulture)
        End Function
    
        ''' <summary>
        ''' Normalizes a string path, taking care of '..' and '.' parts.
        ''' </summary>
        Public Shared Function Normalize(path As String) As String
            If String.IsNullOrEmpty(path) Then
                Return String.Empty
            End If
            Dim oldPath = path.Split(New Char() {UnixPath.Separator}, StringSplitOptions.RemoveEmptyEntries)
            Dim newPath As New Stack(Of String)
            Dim skipCount As Integer = 0
    
            For i = oldPath.GetUpperBound(0) To oldPath.GetLowerBound(0) Step -1
                If String.Equals(oldPath(i), UnixPath.CurrentDirectory, StringComparison.InvariantCulture) Then
                    Continue For
                ElseIf String.Equals(oldPath(i), UnixPath.ParentDirectory, StringComparison.InvariantCulture) Then
                    skipCount += 1
                ElseIf skipCount > 0 Then
                    skipCount -= 1
                Else
                    newPath.Push(oldPath(i))
                End If
            Next
    
            If UnixPath.IsAbsolute(path) Then
                Return UnixPath.Join(UnixPath.Separator, UnixPath.Join(newPath.ToArray))
            Else
                For i = 1 To skipCount
                    newPath.Push(UnixPath.ParentDirectory)
                Next
                Return UnixPath.Join(newPath.ToArray)
            End If
        End Function
    
        ''' <summary>
        ''' Combines an array of string paths.
        ''' </summary>
        Public Shared Function Join(ParamArray paths As String()) As String
            Dim builder As New StringBuilder
            Dim count = paths.GetUpperBound(0)
    
            For i = paths.GetLowerBound(0) To count
                If String.IsNullOrEmpty(paths(i)) Then
                    Continue For
                End If
                builder.Append(paths(i).TrimEnd(UnixPath.Separator))
                If i = count Then
                    Exit For
                End If
    
                builder.Append(UnixPath.Separator)
            Next
    
            Return builder.ToString
        End Function
    
        Public Shared ReadOnly Property CurrentDirectory As String
            Get
                Return "."
            End Get
        End Property
    
        Public Shared ReadOnly Property ParentDirectory As String
            Get
                Return ".."
            End Get
        End Property
    
        Public Shared ReadOnly Property Separator As Char
            Get
                Return "/"c
            End Get
        End Property
    
    End Class
    

1 个答案:

答案 0 :(得分:0)

仅删除重复的...,才能删除/,不会保留原始路径的含义。但是,目前尚不清楚你想要做什么。可能你真的不需要以这种方式“规范化”你的路径。

无论如何,这是一个想法:

Dim inputPath As String = "/../../../foo/bar"
Dim outputPath As String = inputPath.Replace(".", "").Replace("..", "")
Dim outputPathLength As Integer
Do
  outputPathLength = outputPath.Length
  outputPath = outputPath.Replace("//", "/")
Loop Until outputPathLength = outputPath.Length

为了更好的实现 - 一个也会考虑你的情况#2,还有更多,你应该使用正则表达式。

编辑:可能会被某些人视为黑客攻击,但这是另一种选择:

Dim inputPath As String = "../../../foo/bar"
Dim outPath As String = IO.Path.GetFullPath(IO.Path.Combine("C:\", inputPath))
Dim newPath As String = outPath.Substring(outPath.IndexOf("\") + 1).Replace("\", "/")
If inputPath.StartsWith("/") Then newPath = "/" & newPath
MsgBox(newPath)

它使用文件系统规范化路径,并转换回unix样式。适用于上述所有测试用例。