Konvertieren Sie den relativen Pfad mit JavaScript in einen absoluten

71

Es gibt eine Funktion, die mir URLs gibt wie:

./some.css
./extra/some.css
../../lib/slider/slider.css

Es ist immer ein relativer Weg.

Nehmen wir an, wir kennen den aktuellen Pfad der Seite und wissen http://site.com/stats/2012/nicht, wie ich diese relativen Pfade in echte konvertieren soll.

Wir sollten so etwas bekommen wie:

./some.css => http://site.com/stats/2012/some.css
./extra/some.css => http://site.com/stats/2012/extra/some.css
../../lib/slider/slider.css => http://site.com/lib/slider/slider.css

Keine jQuery, nur Vanille-Javascript.

Jaspis
quelle
7
Dies ist für Javascript-Anwendungen, keine reguläre Site.
Jasper

Antworten:

45

Dies sollte es tun:

function absolute(base, relative) {
    var stack = base.split("/"),
        parts = relative.split("/");
    stack.pop(); // remove current file name (or empty string)
                 // (omit if "base" is the current folder without trailing slash)
    for (var i=0; i<parts.length; i++) {
        if (parts[i] == ".")
            continue;
        if (parts[i] == "..")
            stack.pop();
        else
            stack.push(parts[i]);
    }
    return stack.join("/");
}
Bergi
quelle
Einige Websites haben interne Ressourcen mit ~, ~/media/style.css, läuft in diesen den anderen Tag
Daniel Lizik
@ Daniel_L: ~bedeutet normalerweise etwas anderes als base. Und es ist nicht gerade ein relativer Pfad :-) Wenn Sie Hilfe bei Pfaden benötigen, die eine Tilde enthalten, stellen Sie bitte eine neue Frage
Bergi
2
Für alle, die später darauf stoßen, hat die Verwendung von document.location.href als Basis für mich funktioniert.
Nixkuroi
2
Diese Lösung hat ein Problem, wenn die relative URL /beispielsweise mit a beginnt /some.css. Eine korrekte Implementierung würde in einem solchen Fall alle Elemente im Stapel nach dem Domänennamen entfernen.
Vineet
1
@Vineet Richtig, jedoch ist ein Pfad, der mit beginnt, /nicht relativ. Meine Funktion berücksichtigt nur Pfade, keine URIs.
Bergi
120

Der einfachste, effizienteste und korrekteste Weg, dies zu tun, ist die Verwendung der URL- API.

new URL("http://www.stackoverflow.com?q=hello").href;
//=> http://www.stackoverflow.com/?q=hello"

new URL("mypath","http://www.stackoverflow.com").href;
//=> "http://www.stackoverflow.com/mypath"

new URL("../mypath","http://www.stackoverflow.com/search").href
//=> "http://www.stackoverflow.com/mypath"

new URL("../mypath", document.baseURI).href
//=> "/programming/mypath"

In Bezug auf die Leistung ist diese Lösung mit der Verwendung von String-Manipulationen vergleichbar und doppelt so schnell wie das Erstellen von aTags.

Elad
quelle
2
Ich liebe diese Lösung, habe jedoch aufgrund mangelnder oder URL-Unterstützung nicht mit React Native funktioniert. Stattdessen habe ich https://www.npmjs.com/package/url verwendet , was gut funktioniert hat.
Jsaven
3
@hex Du hast Recht, dass die Verwendung window.location.hrefdiesen Nachteil hat. Ich habe die Antwort so bearbeitet, dass sie document.baseURIstattdessen verwendet wird.
Flimm
Dies ist keine allgemeine Lösung für die Entwicklungsumgebung.
SuperEye
Wie führe ich diese Lösung in Ubuntu aus?
vqlk
68

Javascript erledigt das für Sie. Es ist nicht erforderlich, eine Funktion zu erstellen.

var link = document.createElement("a");
link.href = "../../lib/slider/slider.css";
alert(link.protocol+"//"+link.host+link.pathname+link.search+link.hash);

// Output will be "http://www.yoursite.com/lib/slider/slider.css"

Aber wenn Sie es als Funktion brauchen:

var absolutePath = function(href) {
    var link = document.createElement("a");
    link.href = href;
    return (link.protocol+"//"+link.host+link.pathname+link.search+link.hash);
}

Update: Einfachere Version, wenn Sie den vollständigen absoluten Pfad benötigen:

var absolutePath = function(href) {
    var link = document.createElement("a");
    link.href = href;
    return link.href;
}
allenhwkim
quelle
4
Dies funktioniert nicht im IE, nicht einmal im IE11. Weitere Informationen finden Sie hier: stackoverflow.com/questions/470832/…
Oran Dennison
3
Funktioniert hervorragend (auch im IE) ... Ich habe festgestellt, dass beim einfachen Zugriff link.hrefnach dem Festlegen die aufgelöste URL zurückgegeben wird (dh die href muss nicht manuell neu erstellt werden).
Chris Baxter
@ ChrisBaxter Gleich hier. Ich bin etwas verärgert darüber, dass sich diese beliebte Antwort auf Protokoll, Host usw. bezieht, während href einfacher und sicherer aussieht. Gibt es einen Grund dafür? Ich würde gerne wissen ...
philippe_b
Es ist eigentlich eine DOM-Lösung
Lukasz Prus
6

Dies von MDN ist unzerbrechlich!

/*\
|*|
|*|  :: translate relative paths to absolute paths ::
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
|*|
|*|  The following code is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function relPathToAbs (sRelPath) {
  var nUpLn, sDir = "", sPath = location.pathname.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, "$1"));
  for (var nEnd, nStart = 0; nEnd = sPath.indexOf("/../", nStart), nEnd > -1; nStart = nEnd + nUpLn) {
    nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length;
    sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp("(?:\\\/+[^\\\/]*){0," + ((nUpLn - 1) / 3) + "}$"), "/");
  }
  return sDir + sPath.substr(nStart);
}

Beispielnutzung:

/* Let us be in /en-US/docs/Web/API/document.cookie */

alert(location.pathname);
// displays: /en-US/docs/Web/API/document.cookie

alert(relPathToAbs("./"));
// displays: /en-US/docs/Web/API/

alert(relPathToAbs("../Guide/API/DOM/Storage"));
// displays: /en-US/docs/Web/Guide/API/DOM/Storage

alert(relPathToAbs("../../Firefox"));
// displays: /en-US/docs/Firefox

alert(relPathToAbs("../Guide/././API/../../../Firefox"));
// displays: /en-US/docs/Firefox
Madmurphy
quelle
2
Relative Pfade mit einem führenden werden nicht behandelt /. Beispiel: relPathToAbs("/?foo=bar")sollte stattdessen "/? Foo = bar" zurückgeben, /questions/14780350/?foo=barwas bedeutet, dass ein Lead nicht richtig erkannt wird, /wenn der Pfad zurück zum Stamm gesendet wird.
Nucleon
@Nucleon Der Pfad ist der Teil der URL, der zwischen dem Protokollsegment ( http(s)://) und den Such- / Hash- Segmenten ( ?/ #) enthalten ist. Es ist nicht Anliegen dieser Funktion die trennen Pfad Teil von den Such / Hash - Teilen, auch weil Reguläre Ausdrücke können es machen sehr einfach:relPathToAbs("./?foo=bar#someHash".replace(/^[^\?#]*/, "$&"))
madmurphy
1
@ Nucleon Sorry, ich hatte deinen Kommentar falsch verstanden. Ein Pfad, der mit beginnt, /ist bereits ein absoluter Pfad! Daher macht es keinen Sinn, es als Argument für diese Funktion zu verwenden! Die Zeichenfolge "/?foo=bar" ist bereits ein absoluter Pfad.
Madmurphy
Genau, aber führen Sie Ihre Funktion unter der URL dieser Seite aus relPathToAbs("/?foo=bar"). Es kehrt nicht zurück /?foo=bar(weil es bereits absolut ist), es kehrt zurück /questions/14780350/?foo=bar.
Nucleon
1
@Nucleon Die Funktion erwartet, dass Sie einen relativen Pfad angeben. Die Zeichenfolge /?foo=barist ein falscher relativer Pfad (tatsächlich ist es ein absoluter Pfad). Ein gültiger relativer Pfad muss mit beginnen /^(?:\.\.?\/)*[^\/]/. ZB: /^(?:\.\.?\/)*[^\/]/.test("./hello/world")-> true; /^(?:\.\.?\/)*[^\/]/.test("../hi")-> true; /^(?:\.\.?\/)*[^\/]/.test("../././../foo")-> true; /^(?:\.\.?\/)*[^\/]/.test("/")-> false; /^(?:\.\.?\/)*[^\/]/.test("/?foo=bar")-> false;
Madmurphy
6

Wenn Sie eine Relation von relativ zu absolut für einen Link von einer benutzerdefinierten Webseite in Ihrem Browser durchführen möchten (nicht für die Seite, auf der Ihr Skript ausgeführt wird), können Sie eine erweiterte Version der von @Bergi vorgeschlagenen Funktion verwenden:

var resolveURL=function resolve(url, base){
    if('string'!==typeof url || !url){
        return null; // wrong or empty url
    }
    else if(url.match(/^[a-z]+\:\/\//i)){ 
        return url; // url is absolute already 
    }
    else if(url.match(/^\/\//)){ 
        return 'http:'+url; // url is absolute already 
    }
    else if(url.match(/^[a-z]+\:/i)){ 
        return url; // data URI, mailto:, tel:, etc.
    }
    else if('string'!==typeof base){
        var a=document.createElement('a'); 
        a.href=url; // try to resolve url without base  
        if(!a.pathname){ 
            return null; // url not valid 
        }
        return 'http://'+url;
    }
    else{ 
        base=resolve(base); // check base
        if(base===null){
            return null; // wrong base
        }
    }
    var a=document.createElement('a'); 
    a.href=base;

    if(url[0]==='/'){ 
        base=[]; // rooted path
    }
    else{ 
        base=a.pathname.split('/'); // relative path
        base.pop(); 
    }
    url=url.split('/');
    for(var i=0; i<url.length; ++i){
        if(url[i]==='.'){ // current directory
            continue;
        }
        if(url[i]==='..'){ // parent directory
            if('undefined'===typeof base.pop() || base.length===0){ 
                return null; // wrong url accessing non-existing parent directories
            }
        }
        else{ // child directory
            base.push(url[i]); 
        }
    }
    return a.protocol+'//'+a.hostname+base.join('/');
}

Es wird zurückkehren, nullwenn etwas nicht stimmt.

Verwendung:

resolveURL('./some.css', 'http://example.com/stats/2012/'); 
// returns http://example.com/stats/2012/some.css

resolveURL('extra/some.css', 'http://example.com/stats/2012/');
// returns http://example.com/stats/2012/extra/some.css

resolveURL('../../lib/slider/slider.css', 'http://example.com/stats/2012/');
// returns http://example.com/lib/slider/slider.css

resolveURL('/rootFolder/some.css', 'https://example.com/stats/2012/');
// returns https://example.com/rootFolder/some.css

resolveURL('localhost');
// returns http://localhost

resolveURL('../non_existing_file', 'example.com')
// returns null
Optimierer
quelle
2
function canonicalize(url) {
    var div = document.createElement('div');
    div.innerHTML = "<a></a>";
    div.firstChild.href = url; // Ensures that the href is properly escaped
    div.innerHTML = div.innerHTML; // Run the current innerHTML back through the parser
    return div.firstChild.href;
}

Dies funktioniert im Gegensatz zu einigen anderen Lösungen auch unter IE6 (siehe Abrufen einer absoluten URL von einer relativen URL (IE6-Problem) ).

Sebastien Lorber
quelle
Warum führen Sie "das aktuelle innerHTML zurück durch den Parser"?
Knu
Überprüfen Sie den Blog-Beitrag hier verlinkt: stackoverflow.com/a/22918332/82609
Sebastien Lorber
2

Die vorgeschlagene und akzeptierte Lösung unterstützt keine serverbezogenen URLs und funktioniert nicht mit absoluten URLs. Wenn mein Verwandter / sites / folder1 ist, funktioniert das zum Beispiel nicht.

Hier ist eine weitere Funktion, die vollständige, serverbezogene oder relative URLs sowie ../ für eine Ebene höher unterstützt. Es ist nicht perfekt, deckt aber viele Optionen ab. Verwenden Sie diese Option, wenn Ihre Basis-URL nicht die aktuelle Seiten-URL ist. Andernfalls gibt es bessere Alternativen.

    function relativeToAbsolute(base, relative) {
    //make sure base ends with /
    if (base[base.length - 1] != '/')
        base += '/';

    //base: https://server/relative/subfolder/
    //url: https://server
    let url = base.substr(0, base.indexOf('/', base.indexOf('//') + 2));
    //baseServerRelative: /relative/subfolder/
    let baseServerRelative = base.substr(base.indexOf('/', base.indexOf('//') + 2));
    if (relative.indexOf('/') === 0)//relative is server relative
        url += relative;
    else if (relative.indexOf("://") > 0)//relative is a full url, ignore base.
        url = relative;
    else {
        while (relative.indexOf('../') === 0) {
            //remove ../ from relative
            relative = relative.substring(3);
            //remove one part from baseServerRelative. /relative/subfolder/ -> /relative/
            if (baseServerRelative !== '/') {
                let lastPartIndex = baseServerRelative.lastIndexOf('/', baseServerRelative.length - 2);
                baseServerRelative = baseServerRelative.substring(0, lastPartIndex + 1);
            }
        }
        url += baseServerRelative + relative;//relative is a relative to base.
    }

    return url;
}

Hoffe das hilft. Es war wirklich frustrierend, dieses grundlegende Dienstprogramm nicht in JavaScript verfügbar zu haben.

Shai Petel
quelle
Soweit ich das beurteilen kann, würde dies mit protokollunabhängigen URLs nicht funktionieren? Z.B. //www.example.com/page.
Liam Gray
Nein, aber es ist einfach zu unterstützen, wenn Sie nur diese Anweisung ändern: else if (relative.indexOf (": //")> 0) und das: entfernen, aber ich behalte es so, da es das Protokoll von der Basis übernehmen kann , die ich sowieso dynamisch sende.
Shai Petel
2

Die href-Lösung funktioniert nur, wenn das Dokument geladen ist (zumindest in IE11). Das hat bei mir funktioniert:

link = link || document.createElement("a");
link.href =  document.baseURI + "/../" + href;
return link.href;

Siehe https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

Corey Alix
quelle
1
Ich denke, Sie sollten document.baseURIstattdessen window.location.hrefverwenden, um ein mögliches <base>Element zu berücksichtigen .
Flimm
0

Ich musste der akzeptierten Lösung einen Fix hinzufügen, da wir in unserer Angularjs-Navigation Schrägstriche nach # haben können.

function getAbsoluteUrl(base, relative) {
  // remove everything after #
  var hashPosition = base.indexOf('#');
  if (hashPosition > 0){
    base = base.slice(0, hashPosition);
  }

  // the rest of the function is taken from http://stackoverflow.com/a/14780463
  // http://stackoverflow.com/a/25833886 - this doesn't work in cordova
  // http://stackoverflow.com/a/14781678 - this doesn't work in cordova
  var stack = base.split("/"),
      parts = relative.split("/");
  stack.pop(); // remove current file name (or empty string)
               // (omit if "base" is the current folder without trailing slash)
  for (var i=0; i<parts.length; i++) {
    if (parts[i] == ".")
      continue;
    if (parts[i] == "..")
      stack.pop();
    else
      stack.push(parts[i]);
  }
  return stack.join("/");
}
Stanislav
quelle
Ich habe hinzugefügt if (charAt(0) !== ".") return relative;, um den Fall zu behandeln, in dem relativebereits ein absoluter Pfad vorhanden ist, sodass ich nicht zuerst prüfen muss, ob er relativ ist, bevor ich diese Funktion aufrufe.
mindplay.dk
0

Ich habe eine sehr einfache Lösung gefunden, um dies zu tun, während ich IE 10 (IE unterstützt die URL-API nicht) mithilfe der Verlaufs-API (IE 10 oder höher) unterstütze. Diese Lösung funktioniert ohne String-Manipulation.

function resolveUrl(relativePath) {
    var originalUrl = document.location.href;
    history.replaceState(history.state, '', relativePath);
    var resolvedUrl = document.location.href;
    history.replaceState(history.state, '', originalUrl);
    return resolvedUrl;
}

history.replaceState()document.locationLöst keine Browsernavigation aus, ändert jedoch relative und absolute Pfade und unterstützt diese.

Der einzige Nachteil dieser Lösung ist, dass der Titel des aktuellen Status verloren geht, wenn Sie bereits die Verlaufs-API verwenden und einen benutzerdefinierten Status mit einem Titel festgelegt haben.

Warum
quelle
0

Das wird funktionieren. aber nur, wenn Sie eine Seite mit dem Dateinamen öffnen. Es wird nicht gut funktionieren, wenn Sie einen Link wie diesen öffnen stackoverflow.com/page. es wird funktionieren mitstackoverflow.com/page/index.php

function reltoabs(link){
    let absLink = location.href.split("/");
    let relLink = link;
    let slashesNum = link.match(/[.]{2}\//g) ? link.match(/[.]{2}\//g).length : 0;
    for(let i = 0; i < slashesNum + 1; i++){
        relLink = relLink.replace("../", "");
        absLink.pop();
    }
    absLink = absLink.join("/");
    absLink += "/" + relLink;
    return absLink;
}
Mohamed Hesham
quelle