Когда я строил свое расширение для поиска неиспользуемых правил CSS , мне нужен был способ квалифицировать любое значение href
в полный URI . Мне это нужно, потому что я хотел, чтобы он поддерживал таблицы стилей внутри условных комментариев IE , но, конечно, для Firefox это всего лишь комментарии — мне пришлось анализировать каждый узел комментария с помощью регулярного выражения, чтобы извлечь то, что внутри него, и, следовательно, значение href
я получил back всегда был просто строкой, а не свойством или определенным путем.
И это не первый раз, когда мне понадобилась эта способность, но в прошлом это были предсказуемые обстоятельства, когда я уже знал доменное имя и путь. Но здесь эти обстоятельства не были предсказуемы — мне нужно было решение, которое работало бы для любого доменного имени, любого пути и любого вида формата href
(помня, что значение href
может быть любым из нескольких форматов):
- родственник:
"test.css"
- родственник с каталогами:
"foo/test.css"
- родственник отсюда:
"./test.css"
- Относительно сверху вверх структура каталогов:
"../../foo/test.css"
- относительно корня http:
"/test.css"
- Абсолют:
"http://www.sitepoint.com/test.css"
- абсолютно с портом:
"http://www.sitepoint.com:80/test.css"
- абсолютно с другим протоколом:
"https://www.sitepoint.com/test.css"
Когда квалифицированы HREF?
Когда мы получаем href
с помощью JavaScript, возвращаемое значение имеет некоторые кросс-браузерные особенности. .href
всего происходит то, что значение, полученное с помощью сокращенного свойства .href
будет возвращаться как квалифицированный URI , тогда как значение, полученное с помощью getAttribute('href')
будет (и должно, согласно спецификации) возвращаться как значение литерального атрибута. Итак, с этой ссылкой:
<a id="testlink" href="/test.html">test page</a>
Мы должны получить эти значения:
document.getElementById('testlink').href == 'http://www.sitepoint.com/test.html'; document.getElementById('testlink').getAttribute('href') == '/test.html';
А в Opera, Firefox и Safari это действительно то, что мы получаем. Однако в Internet Explorer (все версии, вплоть до IE7 включительно) это не то, что происходит — в обоих примерах мы получаем полный URI , а не необработанное значение атрибута:
document.getElementById('testlink').href == 'http://www.sitepoint.com/test.html'; document.getElementById('testlink').getAttribute('href') == 'http://www.sitepoint.com/test.html';
Эта поведенческая причуда описана в недавней книге Кевина Янка и Кэмерона Адамса « Просто JavaScript» ; но это становится все более странным. Хотя это поведение применимо к href
обычной ссылки (элемент <a>
), если мы сделаем то же самое для таблицы стилей <link>
, мы получим совершенно противоположное поведение в IE . Этот HTML :
<link rel="stylesheet" type="text/css" href="/test.css" />
Производит этот результат:
document.getElementById('teststylesheet').href == '/test.css'; document.getElementById('teststylesheet').getAttribute('href') == '/test.css';
В обоих случаях мы получаем необработанное значение атрибута (тогда как в других браузерах мы получаем те же результаты, что и для якоря — .href
полностью квалифицирован, а getAttribute
выдает буквальное значение).
Тем не мение…
Помимо поведенческих причуд, я должен сказать, что поведение IE со ссылками почти всегда то, что я хочу. Извлечь путь или имя файла из URI довольно просто, но сделать наоборот гораздо сложнее.
Поэтому я написал вспомогательную функцию для этого. Он принимает href
в любом формате и возвращает квалифицированный URI на основе текущего местоположения документа (или, если значение уже квалифицировано, он возвращается без изменений):
//qualify an HREF to form a complete URI function qualifyHREF(href) { //get the current document location object var loc = document.location; //build a base URI from the protocol plus host (which includes port if applicable) var uri = loc.protocol + '//' + loc.host; //if the input path is relative-from-here //just delete the ./ token to make it relative if(/^(./)([^/]?)/.test(href)) { href = href.replace(/^(./)([^/]?)/, '$2'); } //if the input href is already qualified, copy it unchanged if(/^([az]+):///.test(href)) { uri = href; } //or if the input href begins with a leading slash, then it's base relative //so just add the input href to the base URI else if(href.substr(0, 1) == '/') { uri += href; } //or if it's an up-reference we need to compute the path else if(/^((../)+)([^/].*$)/.test(href)) { //get the last part of the path, minus up-references var lastpath = href.match(/^((../)+)([^/].*$)/); lastpath = lastpath[lastpath.length - 1]; //count the number of up-references var references = href.split('../').length - 1; //get the path parts and delete the last one (this page or directory) var parts = loc.pathname.split('/'); parts = parts.splice(0, parts.length - 1); //for each of the up-references, delete the last part of the path for(var i=0; i<references; i++) { parts = parts.splice(0, parts.length - 1); } //now rebuild the path var path = ''; for(i=0; i<parts.length; i++) { if(parts[i] != '') { path += '/' + parts[i]; } } path += '/'; //and add the last part of the path path += lastpath; //then add the path and input href to the base URI uri += path; } //otherwise it's a relative path, else { //calculate the path to this directory path = ''; parts = loc.pathname.split('/'); parts = parts.splice(0, parts.length - 1); for(var i=0; i<parts.length; i++) { if(parts[i] != '') { path += '/' + parts[i]; } } path += '/'; //then add the path and input href to the base URI uri += path + href; } //return the final uri return uri; }
Еще один для инструментария!