Repository navigation
Expand file tree
/
Copy pathUrlPath.cs
More file actions
44 lines (40 loc) · 1.6 KB
/
Copy pathUrlPath.cs
File metadata and controls
44 lines (40 loc) · 1.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
namespace ShellDocs.Core;
// App-relative URL path helpers. Dots are ordinary characters, so "v0.2.1" stays a folder segment.
public static class UrlPath
{
// Leading slash, no trailing slash (except "/" itself), query/fragment stripped.
public static string Normalize(string? url)
{
var s = (url ?? "").Trim();
var cut = s.IndexOfAny(['?', '#']);
if (cut >= 0) s = s[..cut];
if (!s.StartsWith('/')) s = "/" + s;
while (s.Length > 1 && s.EndsWith('/')) s = s[..^1];
return s;
}
// Segment-aware: "/docs/v0.30/x" is not under "/docs/v0.3". Case-insensitive,
// matching NavigationGraph.ResolveByUrl.
public static bool IsUnder(string? path, string? root)
{
var p = Normalize(path);
var r = Normalize(root);
if (r == "/") return true;
if (!p.StartsWith(r, StringComparison.OrdinalIgnoreCase)) return false;
return p.Length == r.Length || p[r.Length] == '/';
}
// "/components/button" for a page below root, "" for root itself, null when outside.
public static string? RelativeTo(string? path, string? root)
{
if (!IsUnder(path, root)) return null;
var p = Normalize(path);
var r = Normalize(root);
return r == "/" ? (p == "/" ? "" : p) : p[r.Length..];
}
public static string Combine(string root, string? relative)
{
var r = Normalize(root);
if (string.IsNullOrEmpty(relative) || relative == "/") return r;
var rel = relative.StartsWith('/') ? relative : "/" + relative;
return Normalize(r == "/" ? rel : r + rel);
}
}