Obtener una URL absoluta de una relativa. (Problema IE6)

80

Actualmente estoy usando la siguiente función para 'convertir' una URL relativa en una absoluta:

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

Esto funciona bastante bien en la mayoría de los navegadores, pero IE6 insiste en devolver la URL relativa. Hace lo mismo si uso getAttribute ('href').

La única forma en que he podido obtener una URL calificada de IE6 es crear un elemento img y consultar su atributo 'src'; el problema con esto es que genera una solicitud de servidor; algo que quiero evitar.

Entonces, mi pregunta es: ¿Hay alguna forma de obtener una URL completamente calificada en IE6 de una relativa (sin una solicitud del servidor)?


Antes de recomendar una corrección rápida de expresiones regulares / cadenas, le aseguro que no es tan simple. ¡Los elementos base + URL relativas de doble período + una tonelada de otras variables potenciales realmente lo hacen un infierno!

¿Debe haber una manera de hacerlo sin tener que crear una gigantesca solución de expresiones regulares?

James
fuente
1
Puede usar js-uri para resolver el URI relativo a uno absoluto.
Gumbo
Gracias Gumbo, supongo que esto tendrá que ser suficiente. Me hubiera gustado una solución más concisa, pero gracias de todos modos, ¡nunca supe que existía esta clase js-uri!
James
8
¡Dulce truco! No se preocupe por IE6. Me ahorró horas. Tú Molas.
Tom Harrison
No lo hice funcionar con esto, solo tengo "foo" y quiero " example.com/foo "
Jaime Hablutzel
La biblioteca js-uri no parece hacer lo que quiere el póster original.
djsmith

Respuestas:

47

¡Que extraño! IE, sin embargo, lo entiende cuando usa innerHTML en lugar de métodos 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;
}

Un poco feo, pero más conciso que Hágalo usted mismo.

bobince
fuente
Encontré esta solución similar en un blog que no necesita el código para escapar: stackoverflow.com/a/22918332/82609
Sebastien Lorber
Este enfoque reemplaza nulo (U + 0000) con (U + FFFD), de acuerdo con las especificaciones HTML .
Oriol
26

Siempre que el navegador implemente la etiqueta <base> correctamente, qué navegadores tienden a:

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

Aquí hay un jsfiddle donde puede experimentar con él: http://jsfiddle.net/ecmanaut/RHdnZ/

ecmanaut
fuente
Es tres años tarde para la fiesta, por lo que tomará un tiempo llegar a la cima sin que el marketing o mucha gente tenga el problema y quiera una solución precisa y conservadora de código.
Ecmanaut
2
Además de admitir URL base arbitrarias, ¿en qué se diferencia exactamente de la solución presentada en la pregunta? ¿Funciona en IE 6?
Juan
1
@AmadeusDrZaius No debería, pero puede serlo si lo desea. Javascript solo agrega un punto y coma automático al final de una línea cuando hacerlo no hará que la próxima línea sea una declaración no válida. ", foo = 1" es un error de sintaxis y, por lo tanto, toda la instrucción var se evalúa de forma masiva, sin inserción de punto y coma.
ecmanaut
2
@AndreasDietrich Eso se debe a que no pasa ningún argumento al base_urlparámetro, por lo que se convierte en undefinedy se encadena a "undefined". En su lugar, deberías pasar la cadena vacía. O, si desea que el segundo parámetro sea opcional, utilice en our_base.href = base_url || ""lugar de our_base.href = base_url..
Oriol
1
Buena idea, @Oriol: no hay razón para no tener un comportamiento predeterminado más amigable para las personas que no pasan ambos parámetros. Integrado.
Ecmanaut
16

Puede hacer que funcione en IE6 simplemente clonando el elemento:

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

(Probado con IETester en los modos IE6 e IE5.5)

Oriol
fuente
10

Encontré en este blog otro método que realmente se parece a la solución @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;
}

Lo encontré un poco más elegante, no gran cosa.

Sebastien Lorber
fuente
7

URI.js parece resolver el problema:

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

Véase también http://medialize.github.io/URI.js/docs.html#absoluteto

No probado con IE6, pero tal vez sea útil para otros que busquen el problema general.

koppor
fuente
1
En el lado del nodo de las cosas (para rastreo, etc.), la biblioteca correcta aquí está disponible a través de npm install URIjs, no la otra biblioteca con un nombre similar
y3sh
el paquete npm nombrado ha cambiado a urijs github.com/medialize/URI.js#using-urijs
Daniel Lizik
7

De hecho, quería un enfoque para esto que no requiriera modificar el documento original (ni siquiera temporalmente) pero aún usaba el análisis de URL incorporado del navegador y demás. Además, quería poder proporcionar mi propia base (como la respuesta de ecmanaught). Es bastante sencillo, pero usa createHTMLDocument (podría reemplazarse con createDocument para ser un poco más compatible posiblemente):

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
fuente
1
No estoy seguro si me falta algo, pero IE6 (ni 7, 8) no es compatibledocument.implementation.createHTMLDocument
Oriol
Usé esto cuando estaba usando una aplicación web para cargar y raspar otras páginas. En la devolución de llamada de jQuery.load, $("#loadedHere").createElement("a").url="foo"resultó en una URL vacía, así que tuve que recurrir a la creación de un documento separado.
ericP
5

Esta solución funciona en todos los navegadores.

/**
 * 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

Sin embargo, no admite URL relativas con ".." en ellas, como "../file.png".

Feross
fuente
Esto tiene algunos problemas. Por ejemplo, está asumiendo que la base es la misma que Windows y no creo que esto funcione si tengo un parámetro de url en url. Decir /img/profile.php?url=https://google.com/logo.svg.
Teodors
3

Esta es la función que uso para resolver URL relativas básicas:

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(/^\.\//, '');
}

Pruébelo en jsfiddle: https://jsfiddle.net/n11rg255/

Funciona tanto en el navegador como en node.js u otros entornos.

lovasoa
fuente
2

Encontré esta publicación de blog que sugiere usar un elemento de imagen en lugar de un ancla:

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

Eso funciona para expandir una URL de manera confiable, incluso en IE6. Pero el problema es que los navegadores que he probado descargarán inmediatamente el recurso al configurar el atributo image src, incluso si configura src en nulo en la siguiente línea.

En su lugar, voy a probar la solución de bobince.

Jesse Hallett
fuente
0

Si urlno comienza con '/'

Tome la URL de la página actual, elimine todo lo que pase de la última '/'; luego agregue la URL relativa.

De lo contrario, si urlcomienza con '/'

Toma la URL de la página actual y corta todo a la derecha del sencillo '/'; luego agregue la URL.

De lo contrario, si urlcomienza con # o?

Tome la URL de la página actual y simplemente agregue url


Espero que funcione para ti

geowa4
fuente
2
Olvidó que las URL pueden comenzar con "//", lo que las hace relativas al esquema. //foo.com/bar/
Scott Wolchok
1
también olvidó la sintaxis relativa con puntos ../../ (si esta omisión es importante o no, depende de para qué se requiera la salida)
hallvors
-1

Si se ejecuta en el navegador, esto funciona para mí.

  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
fuente