問題文
私の現在のプロジェクトでは、多くの UNIX スタイルのパスを処理する必要があります。最も重要なのは、パスの結合とパスの正規化です。
正規化とは、元のパスの「意味」を維持しながら、現在のディレクトリへの参照 ( .
)、親ディレクトリへの参照 ( ..
)、および冗長なスラッシュ ( ) をすべて削除することを意味します。//
例: パス/foo/bar
と/foo/./bar
および/foo/baz/../bar
すべてが同じディレクトリを指しています。ただし、単純な文字列比較を行うと、それらが異なるパスであることが明らかに示されます。そのため、パスがすべて同じように扱われるようにパスを正規化しようとしています。
これを行うためのコードは、実際にはそれほど難しくありませんでした。私のコードは質問の一番下にあります。しかし、まだ問題が 1 つあります。
現在、ユーザーは次のようなパスを入力できます。
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
ご覧のとおり、正規化された出力値が再び結合されると、パスは元の意味を失います。先頭の親参照を削除しなければ、これは発生しませんでした。
したがって、不明な親参照を持つ相対パスの場合、3 つのオプションがあると思います。
- 結果が技術的に間違っていても、先頭の親参照を削除します
- 結果が技術的に正規化されていなくても、先頭の親参照を保持します
- エラーをスローする
天才がより良いアイデアを思いつくことを願っています。しかし、もしそうでなければ、あなたはどうしますか?
コード
考えられるすべてのユースケースについてコードをまだテストしていませんが、比較的 (しゃれを意図して) 安定しているはずです。
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