Pobieranie bezwzględnego adresu URL z adresu względnego. (Problem z IE6)

80

Obecnie używam następującej funkcji, aby „przekonwertować” względny adres URL na bezwzględny:

function qualifyURL(url) {
    var a = document.createElement('a');
    a.href = url;
    return a.href;
}

Działa to całkiem dobrze w większości przeglądarek, ale IE6 nadal nalega na zwracanie względnego adresu URL! Robi to samo, jeśli używam getAttribute („href”).

Jedynym sposobem, w jaki mogłem uzyskać kwalifikowany adres URL z IE6, jest utworzenie elementu img i wysłanie zapytania o jego atrybut „src” - problem polega na tym, że generuje on żądanie serwera; coś, czego chcę uniknąć.

Moje pytanie brzmi: czy istnieje sposób na uzyskanie w pełni kwalifikowanego adresu URL w IE6 z adresu względnego (bez żądania serwera)?


Zanim polecisz szybką naprawę wyrażeń regularnych / ciągów, zapewniam, że nie jest to takie proste. Elementy bazowe + względne adresy URL z podwójnymi okresami + mnóstwo innych potencjalnych zmiennych to naprawdę piekło!

Musi istnieć sposób, aby to zrobić bez konieczności tworzenia gigantycznego rozwiązania regex'y?

James
źródło
1
Możesz użyć js-uri, aby przekształcić względny identyfikator URI na bezwzględny.
Gumbo
Dziękuję Gumbo, przypuszczam, że to musi wystarczyć. Chciałbym bardziej zwięzłe rozwiązanie, ale i tak dziękuję, nigdy nie wiedziałem, że ta klasa js-uri istnieje!
James
8
Słodki hack! Nie przejmuj się IE6. Zaoszczędzili mi godziny. Rządzisz.
Tom Harrison,
Nie udało mi się z tym zrobić, mam po prostu „foo” i chcę „ example.com/foo
Jaime Hablutzel
Wydaje się, że biblioteka js-uri nie robi tego, czego chce oryginalny plakat.
djsmith

Odpowiedzi:

47

Jak dziwnie! IE rozumie to jednak, kiedy używasz innerHTML zamiast metod DOM.

function escapeHTML(s) {
    return s.split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
}
function qualifyURL(url) {
    var el= document.createElement('div');
    el.innerHTML= '<a href="'+escapeHTML(url)+'">x</a>';
    return el.firstChild.href;
}

Trochę brzydkie, ale bardziej zwięzłe niż Zrób to sam.

bobince
źródło
Znalazłem to podobne rozwiązanie na blogu, który nie potrzebuje kodu do ucieczki: stackoverflow.com/a/22918332/82609
Sebastien Lorber
To podejście zastępuje null (U + 0000) przez (U + FFFD), zgodnie ze specyfikacją HTML .
Oriol,
26

O ile przeglądarka prawidłowo implementuje tag <base>, które przeglądarki zazwyczaj:

function resolve(url, base_url) {
  var doc      = document
    , old_base = doc.getElementsByTagName('base')[0]
    , old_href = old_base && old_base.href
    , doc_head = doc.head || doc.getElementsByTagName('head')[0]
    , our_base = old_base || doc_head.appendChild(doc.createElement('base'))
    , resolver = doc.createElement('a')
    , resolved_url
    ;
  our_base.href = base_url || '';
  resolver.href = url;
  resolved_url  = resolver.href; // browser magic at work here

  if (old_base) old_base.href = old_href;
  else doc_head.removeChild(our_base);
  return resolved_url;
}

Oto jsfiddle, w którym możesz poeksperymentować: http://jsfiddle.net/ecmanaut/RHdnZ/

ecmanaut
źródło
Na imprezę spóźnia się trzy lata, więc dojście na szczyt zajmie trochę czasu bez marketingu lub wielu ludzi, którzy mają problem i chcą konserwatywnego kodu i dokładnego rozwiązania.
ecmanaut
2
Poza obsługą dowolnych bazowych adresów URL, czym dokładnie różni się to od rozwiązania przedstawionego w pytaniu? Czy to działa na IE 6?
John,
1
@AmadeusDrZaius Nie powinno, ale jeśli chcesz, mogą być. Javascript dodaje automatyczny średnik tylko na końcu wiersza, gdy nie spowoduje to, że nadchodzący wiersz będzie nieprawidłową instrukcją. „, foo = 1” jest błędem składniowym, dlatego cała instrukcja var jest oceniana zbiorczo, bez wstawiania średników.
ecmanaut
2
@AndreasDietrich Dzieje się tak, ponieważ nie przekazujesz żadnego argumentu do base_urlparametru, więc staje się on undefinedi jest do niego przypisywany "undefined". Zamiast tego należy przekazać pusty ciąg. Lub, jeśli chcesz, aby drugi parametr był opcjonalny, użyj our_base.href = base_url || ""zamiast our_base.href = base_url
Oriol
1
Dobry pomysł, @Oriol - nie ma powodu, aby nie mieć bardziej przyjaznego domyślnego zachowania dla osób, które nie przekazują obu parametrów. Zintegrowany.
ecmanaut
16

Możesz sprawić, by działało w IE6, po prostu sklonując element:

function qualifyURL(url) {
    var a = document.createElement('a');
    a.href = url;
    return a.cloneNode(false).href;
}

(Testowane przy użyciu IETester w trybach IE6 i IE5.5)

Oriol
źródło
10

Znalazłem na tym blogu inną metodę, która naprawdę wygląda jak rozwiązanie @bobince.

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;
}

Wydało mi się to trochę bardziej eleganckie, nic wielkiego.

Sebastien Lorber
źródło
7

Wydaje się, że URI.js rozwiązuje problem:

URI("../foobar.html").absoluteTo("http://example.org/hello/world.html").toString()

Zobacz także http://medialize.github.io/URI.js/docs.html#absoluteto

Nie testowane z IE6, ale może być pomocne dla innych szukających informacji o ogólnym problemie.

koppor
źródło
1
Po stronie węzłów (do indeksowania itp.) Odpowiednia biblioteka jest dostępna przez npm install URIjs, a nie inna biblioteka o podobnej nazwie
y3sh
nazwa pakietu npm została zmieniona na urijs github.com/medialize/URI.js#using-urijs
Daniel Lizik
7

Właściwie chciałem podejścia do tego, które nie wymagałoby modyfikowania oryginalnego dokumentu (nawet nie tymczasowo), ale nadal korzystałem z wbudowanego analizowania adresów URL przeglądarki i tym podobnych. Chciałem też móc zapewnić własną bazę (na przykład odpowiedź ecmanaught). Jest to raczej proste, ale używa createHTMLDocument (może być zastąpione przez createDocument, aby być nieco bardziej kompatybilnym):

function absolutize(base, url) {
    d = document.implementation.createHTMLDocument();
    b = d.createElement('base');
    d.head.appendChild(b);
    a = d.createElement('a');
    d.body.appendChild(a);
    b.href = base;
    a.href = url;
    return a.href;
}

http://jsfiddle.net/5u6j403k/

Chris Hopman
źródło
1
Nie jestem pewien, czy czegoś mi brakuje, ale IE6 (ani 7, 8) nie obsługujedocument.implementation.createHTMLDocument
Oriol
Użyłem tego, gdy używałem aplikacji internetowej do ładowania i pobierania innych stron. W wywołaniu zwrotnym z jQuery.load $("#loadedHere").createElement("a").url="foo"skutkowało pustym adresem URL, więc musiałem uciec się do stworzenia osobnego dokumentu.
ericP
5

To rozwiązanie działa we wszystkich przeglądarkach.

/**
 * Given a filename for a static resource, returns the resource's absolute
 * URL. Supports file paths with or without origin/protocol.
 */
function toAbsoluteURL (url) {
  // Handle absolute URLs (with protocol-relative prefix)
  // Example: //domain.com/file.png
  if (url.search(/^\/\//) != -1) {
    return window.location.protocol + url
  }

  // Handle absolute URLs (with explicit origin)
  // Example: http://domain.com/file.png
  if (url.search(/:\/\//) != -1) {
    return url
  }

  // Handle absolute URLs (without explicit origin)
  // Example: /file.png
  if (url.search(/^\//) != -1) {
    return window.location.origin + url
  }

  // Handle relative URLs
  // Example: file.png
  var base = window.location.href.match(/(.*\/)/)[0]
  return base + url

Jednak nie obsługuje względnych adresów URL zawierających „..”, np. „../File.png”.

Feross
źródło
To ma pewne problemy. Na przykład zakładasz, że baza jest taka sama jak Windows i nie sądzę, aby to działało, jeśli mam parametr adresu URL w adresie URL. Powiedz /img/profile.php?url=https://google.com/logo.svg.
Teodors
3

Oto funkcja, której używam do rozwiązywania podstawowych względnych adresów URL:

function resolveRelative(path, base) {
    // Absolute URL
    if (path.match(/^[a-z]*:\/\//)) {
      return path;
    }
    // Protocol relative URL
    if (path.indexOf("//") === 0) {
      return base.replace(/\/\/.*/, path)
    }
    // Upper directory
    if (path.indexOf("../") === 0) {
        return resolveRelative(path.slice(3), base.replace(/\/[^\/]*$/, ''));
    }
    // Relative to the root
    if (path.indexOf('/') === 0) {
        var match = base.match(/(\w*:\/\/)?[^\/]*\//) || [base];
        return match[0] + path.slice(1);
    }
    //relative to the current directory
    return base.replace(/\/[^\/]*$/, "") + '/' + path.replace(/^\.\//, '');
}

Przetestuj na jsfiddle: https://jsfiddle.net/n11rg255/

Działa zarówno w przeglądarce, jak iw node.js lub w innych środowiskach.

lovasoa
źródło
2

Znalazłem ten post na blogu, który sugeruje użycie elementu obrazu zamiast kotwicy:

http://james.padolsey.com/javascript/getting-a-fully-qualified-url/

Działa to w celu niezawodnego rozwinięcia adresu URL, nawet w IE6. Problem polega jednak na tym, że testowane przeze mnie przeglądarki natychmiast pobierają zasób po ustawieniu atrybutu src obrazu - nawet jeśli ustawisz src na null w następnym wierszu.

Zamiast tego spróbuję zastosować rozwiązanie Bobince'a.

Jesse Hallett
źródło
0

Gdyby url nie zaczyna się od „/”

Weź adres URL bieżącej strony, odetnij wszystko poza ostatnim „/”; następnie dołącz względny adres URL.

W przeciwnym razie, jeśli urlzaczyna się od „/”

Weź adres URL bieżącej strony i odetnij wszystko na prawo od pojedynczego znaku „/”; następnie dołącz adres URL.

W przeciwnym razie, jeśli urlzaczyna się od # lub?

Wybierz adres URL bieżącej strony i po prostu dołącz url


Mam nadzieję, że to działa dla Ciebie

geowa4
źródło
2
Zapomniałeś, że adresy URL mogą zaczynać się od „//”, co powoduje, że są względne względem schematu. //foo.com/bar/
Scott Wolchok
1
zapomniałeś również o składni z kropkami .../../ (czy to pominięcie ma znaczenie, czy nie, zależy od tego, do czego jest wymagane wyjście)
hallvors
-1

Jeśli działa w przeglądarce, to działa dla mnie ...

  function resolveURL(url, base){
    if(/^https?:/.test(url))return url; // url is absolute
    // let's try a simple hack..
    var basea=document.createElement('a'), urla=document.createElement('a');
    basea.href=base, urla.href=url;
    urla.protocol=basea.protocol;// "inherit" the base's protocol and hostname
    if(!/^\/\//.test(url))urla.hostname=basea.hostname; //..hostname only if url is not protocol-relative  though
    if( /^\//.test(url) )return urla.href; // url starts with /, we're done
    var urlparts=url.split(/\//); // create arrays for the url and base directory paths
    var baseparts=basea.pathname.split(/\//); 
    if( ! /\/$/.test(base) )baseparts.pop(); // if base has a file name after last /, pop it off
    while( urlparts[0]=='..' ){baseparts.pop();urlparts.shift();} // remove .. parts from url and corresponding directory levels from base
    urla.pathname=baseparts.join('/')+'/'+urlparts.join('/');
    return urla.href;
  }
hallvors
źródło