Getting an absolute URL from a relative one. (IE6 issue)

I'm currently using the following function to 'convert' a relative URL to an absolute one:

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

This works quite well in most browsers but IE6 insists on returning the relative URL still! It does the same if I use getAttribute('href').

The only way I've been able to get a qualified URL out of IE6 is to create an img element and query it's 'src' attribute - the problem with this is that it generates a server request; something I want to avoid.

So my question is: Is there any way to get a fully qualified URL in IE6 from a relative one (without a server request)?


Before you recommend a quick regex/string fix I assure you it's not that simple. Base elements + double period relative urls + a tonne of other potential variables really make it hell!

There must be a way to do it without having to create a mammoth of a regex'y solution??

49597 次浏览

If url does not begin with '/'

Take the current page's url, chop off everything past the last '/'; then append the relative url.

Else if url begins with '/'

Take the current page's url and chop off everything to the right of the single '/'; then append the url.

Else if url starts with # or ?

Take the current page's url and simply append url


Hope it works for you

How strange! IE does, however, understand it when you use innerHTML instead of DOM methods.

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

A bit ugly, but more concise than Doing It Yourself.

I found this blog post that suggests using an image element instead of an anchor:

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

That works to reliably expand a URL, even in IE6. But the problem is that the browsers that I have tested will immediately download the resource upon setting the image src attribute - even if you set the src to null on the next line.

I am going to give bobince's solution a go instead.

As long as the browser implements the <base> tag correctly, which browsers tend to:

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

Here's a jsfiddle where you can experiment with it: http://jsfiddle.net/ecmanaut/RHdnZ/

If it runs in the browser, this sort of works for me..

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

This solution works in all browsers.

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

However, it doesn't support relative URLs with ".." in them, like "../file.png".

URI.js seems to solve the issue:

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

See also http://medialize.github.io/URI.js/docs.html#absoluteto

Not testeed with IE6, but maybe helpful for others searching to the general issue.

I found on this blog another method that really looks like @bobince solution.

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

I found it a little more elegant, not a big deal.

You can make it work on IE6 just cloning the element:

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

(Tested using IETester on IE6 and IE5.5 modes)

This is the function I use to resolve basic relative URLs:

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

Test it on jsfiddle: https://jsfiddle.net/n11rg255/

It works both in the browser and in node.js or other environments.

I actually wanted an approach to this that didn't require modifying the original document (not even temporarily) but still used the browser's builtin url parsing and such. Also, I wanted to be able to provide my own base (like ecmanaught's answer). It's rather straightforward, but uses createHTMLDocument (could be replaced with createDocument to be a bit more compatible possibly):

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/