Reputation:
How do I convert an absolute url to a relative url?
Converting relative to absolute is trival
const rel = "../images/doggy.jpg"
const base = "https://example.com/pages/page.html?foo=bar#baz";
function relToAbs(baseHref, relHref) {
const url = new URL(rel, base);
return `${url.origin}${url.pathname}`;
}
console.log(base, rel);
Going the opposite direction seems to require a bunch of code.
Here's what I tried.
const pageURL1 = 'https://example.com/bar/baz.html?bla=blap#hash';
const pageURL2 = 'https://example.com/bar/?bla=blap#hash';
const absURL1 = 'https://example.com/bar/image/doggy.jpg';
const absURL2 = 'https://example.com/image/kitty.jpg';
const absURL3 = 'https://example.com/bar/birdy.jpg';
const tests = [
{ pageHref: pageURL1, absHref: absURL1, expected: 'image/doggy.jpg', },
{ pageHref: pageURL1, absHref: absURL2, expected: '../image/kitty.jpg', },
{ pageHref: pageURL1, absHref: absURL3, expected: 'birdy.jpg', },
{ pageHref: pageURL2, absHref: absURL1, expected: 'image/doggy.jpg', },
{ pageHref: pageURL2, absHref: absURL2, expected: '../image/kitty.jpg', },
{ pageHref: pageURL2, absHref: absURL3, expected: 'birdy.jpg', },
];
for (const {pageHref, absHref, expected} of tests) {
const actual = absToRel(pageHref, absHref);
console.log(absHref, actual, actual === expected ? 'pass' : 'FAIL');
}
function absToRel(pageHref, absHref) {
const pagePaths = dirPaths(pageHref).slice(0, -1);
const absPaths = dirPaths(absHref);
if (pagePaths[0] !== absPaths[0]) {
return absHref; // not same domain
}
// remove same paths
const firstDiffNdx = firstNonMatchingNdx(pagePaths, absPaths);
pagePaths.splice(0, firstDiffNdx);
absPaths.splice(0, firstDiffNdx);
// for each path still on page add a ..
return [
...(new Array(pagePaths.length).fill('..')),
...absPaths,
].join('/');
}
function firstNonMatchingNdx(a, b) {
let ndx = 0;
while (ndx < a.length && a[ndx] === b[ndx]) {
++ndx;
}
return ndx;
}
function dirPaths(href) {
const url = new URL(href);
return [url.origin, ...url.pathname.split('/')];
}
Upvotes: 3
Views: 2258
Reputation: 1720
@Spidy's answer is good. I used it with a check that the input URL-s are not already relative as that would make the URL constructor throw an exception. Here is my simple test for relative URL-s...
function isRelativeUrl(href) {
return href !== new URL(href, "http://a/b").toString();
}
Upvotes: 0
Reputation: 40002
You can use the built-in URL class to help with parsing the URL. That way your code can just focus on the individual path elements and ignore search params, domains, schemas, etc...
Here's what I came up with.
I'm not promising this is perfect or foolproof but hopefully it is close and gets you closer to what you are looking for.
function pathParts(path) {
let parts = path.split('/');
let start = 0;
let end = parts.length;
// Remove initial blank part from root /
if( !parts[0] ) {
start = 1;
}
// Remove trailing / part
if( !parts[parts.length - 1] ) {
end -= 1;
}
return parts.slice(start, end);
}
function absToRel(pageHref, absHref) {
var pageUrl = new URL(pageHref);
var absUrl = new URL(absHref);
// If the urls have different schemas or domain, relative is not possible
if( pageUrl.origin !== absUrl.origin ) {
return absHref;
}
var pagePath = pathParts(pageUrl.pathname)
var absPath = pathParts(absUrl.pathname)
// Remove common root paths
while( absPath.length && pagePath[0] === absPath[0] ) {
absPath.shift();
pagePath.shift();
}
// For each path segment left on pagePath, we'll need a relative ..
pagePath.fill('..');
let relPath = [
...pagePath,
...absPath
].join('/');
// If the paths do not require traversal up, use current directory relative path
if( relPath[0] !== '.' ) {
relPath = './' + relPath;
}
return relPath;
}
Upvotes: 4