0

問題文

私の現在のプロジェクトでは、多くの 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 つのオプションがあると思います。

  1. 結果が技術的に間違っていても、先頭の親参照を削除します
  2. 結果が技術的に正規化されていなくても、先頭の親参照を保持します
  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
4

1 に答える 1

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 スタイルに変換します。上記のすべてのテスト ケースで機能します。

于 2013-02-09T21:18:34.790 に答える