Статьи

Работа с неквалифицированными значениями HREF

Когда я строил свое расширение для поиска неиспользуемых правил 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; } 

Еще один для инструментария!