7

I'm wondering if someone else can come up with a better way of simplifying relative URLs on either the basis of System.Uri or other means that are available in the standard .NET framework. My attempt works, but is pretty horrendous:

public static String SimplifyUrl(String url)
{
    var baseDummyUri = new Uri("http://example.com", UriKind.Absolute);
    var dummyUri = new Uri(baseDummyUri , String.Join("/",
        Enumerable.Range(0, url.Split(new[] { ".." }, StringSplitOptions.None).Length)
                  .Select(i => "x")));

    var absoluteResultUri = new Uri(dummyUri, url);
    var resultUri = dummyUri.MakeRelativeUri(absoluteResultUri);
    return resultUri.ToString();
}

This gives, for example:

./foo -> foo
foo/./bar -> foo/bar
foo/../bar -> bar

The problem that makes this so awkward is that Uri itself doesn't seem to simplify URLs that are relative and MakeRelativeUri also only works on absolute URLs. So to trick the Uri class into doing what I want, I construct a base URL that has the appropriate amount of nesting.

I could also use System.IO.Path, but then I'd have to search-and-replace backlashes to slashes...

There has to be a better way, right?

John
  • 6,693
  • 3
  • 51
  • 90

2 Answers2

10

You can do it with one-liner:

new Uri(new Uri("http://example.com/"), url).AbsolutePath.TrimStart('/');

The following test shows the results:

        [Theory]
        [InlineData("./foo", "foo")]
        [InlineData("/foo", "foo")]
        [InlineData("foo", "foo")]
        [InlineData("foo/./bar", "foo/bar")]
        [InlineData("/foo/./bar", "foo/bar")]
        [InlineData("/foo/../bar", "bar")]
        [InlineData("foo/../bar", "bar")]
        public void Test(string url, string expected)
        {
            var result = new Uri(new Uri("http://example.com/"), url).AbsolutePath.TrimStart('/');
            Assert.Equal(expected, result);
        }

Of course, if you want to leave / in the beginning just remove TrimStart('/')

Piotr Stapp
  • 19,392
  • 11
  • 68
  • 116
0

This is a parsing problem. The tokens are separated by "/"; the token ".." reduces the previous token (if any) while the token "." is a no-op.

There's a little bit of a twist which is that after we break the string with "/" we need to keep track of whether it was an absolute path (starting at "/") or relative path (starting at "."). We need this in case the reduced path is empty.

public static class UrlHelper
{
    static public string Simplify(this string url)
    {
        string[] sourcePath = (url ?? "").Trim().Split('/');
        if (sourcePath.Count() == 0) { throw new ArgumentException(); }
        string startingAt = (sourcePath[0] == "")? "/": ".";

        Stack<string> simplifiedPath = new Stack<string>();
        foreach (string dir in sourcePath) {
            if (dir == ".") {
                continue;
            } else if (dir == ".." && simplifiedPath.Count > 0) {
                simplifiedPath.Pop();
                continue;
            }
            simplifiedPath.Push(dir);
        }
        string reducedPath = (simplifiedPath.Count == 0)? "": simplifiedPath.Reverse().Aggregate((path, dir) => path + "/" + dir);
        return (reducedPath == "") ? startingAt : reducedPath;
    }
}

A bit of testing:

    static void Main(string[] args)
    {
        Console.WriteLine("./foo".Simplify());         // => foo
        Console.WriteLine("foo/./bar".Simplify());     // => foo/bar
        Console.WriteLine("foo/../bar".Simplify());    // => bar
        Console.WriteLine("foo/bar/../..".Simplify()); // => .
        Console.WriteLine("/foo/bar/../..".Simplify()); // => /
        Console.WriteLine("../bar".Simplify());        // => ../bar
        Console.WriteLine("..".Simplify());            // => ..
        Console.WriteLine(".".Simplify());             // => .
        Console.WriteLine("/foo".Simplify());          // => /foo
        Console.WriteLine("/".Simplify());             // => /
        Console.ReadKey();
    }
sjb-sjb
  • 1,112
  • 6
  • 14
  • After I wrote this I noticed another answer, https://stackoverflow.com/questions/128990/absolute-url-from-base-relative-url-in-c-sharp?rq=1 – sjb-sjb Mar 22 '19 at 03:46