Снова разбираем URL

Как было сказано в одном из комментариев на эту вечную тему: «Примерно раз в год, кто-то изобретает этот велосипед, и спешит поделиться с Хабром». Новый год уже наступил, и мне хотелось бы поддержать эту добрую традицию. Предлагаемый мной способ решает задачу, что называется «в лоб» и к нему хорошо подходит крылатая фраза одного из персонажей замечательной советской комедии «Бриллиантовая рука»: «Зато дешево, надежно и практично!».

Если внимательно посмотреть на структуру URL, то можно заметить, что она имеет скорее «позиционную», чем «синтаксическую» природу и поэтому я не буду пытаться разбирать URL при помощи регулярных выражений, а просто разобью URL на части и, строго в соответствии с названиями методов Array.prototype, просто «распихаю» части URL по свойствам одноименного объекта.

Как уже упоминалось, типичная структура URL представляет собой следующую строку:

структура url

Спецификация URL предусматривает наличие еще двух компонентов адреса — логин и пароль, но поскольку я предлагаю решение практической задачи, то эта часть в «парсере» опущена. Мне не приходилось сталкиваться с URL подобного вида, поскольку так решать задачу авторизации можно, пожалуй, только 15 января. Типичных практических задач, с которыми мне приходится иметь дело обычно всего две:

  1. Получить значение параметра из search
  2. Изменить значение одного или нескольких параметров в search и вернуть обновленный URL для последующего применения

Листинг функции небольшой, поэтому приведу его полностью:

function toUrl(url, key, value) {
  if(url=='') url=window.location.href; 
  if(url.indexOf('//')<0) url='//'+url;

  // protocol
  var u=url.split(/\/?\//); 
  url={}; url.protocol=u.shift()+'//';

  // hostname
  url.hostname=u.shift().split(':'); 
  if(url.hostname.length>1) url.port=':'+url.hostname.pop()
  url.hostname=url.hostname.pop().split('@').pop();

  // сохраним порядок для последующей свертки
  url.pathname='/'; url.pagename=''; 

  try { 
    // hash
    url.search=u.pop().split(/#/); 
    if(url.search.length==2) url.sh='#'+url.search.pop();
    url.search=url.search.shift().split(/\?/);
    
    // pagename
    url.pagename=url.search.shift(); url.search=url.search[0]
  
    // pathname
    url.pathname+=u.join('/')+(u.length?'/':'');
  
    // разбор search, (c) Steven Benner, 2010
    try {
      var u = {};
      url.search.replace(
        new RegExp('([^?=&]+)(=([^&]*))?', 'g'),
        function($0, $1, $2, $3) { 
           u[$1] = decodeURIComponent($3); 
        }
      );
      url.search=u;
    } catch(e) { 
      delete url.search; 
    }
  } catch(e) { };

  // вернем URL как объект, где search так же
  // представлен объектом {key:value}
  if(!key && !value) return(url);

  // возвращаем значение ключа
  if(key && !value) return(url.search[key]);

  // roll up url
  var roll = function(url,search){
    var out='';
    for(var key in url) {
      out+=(search?'&'+key+'=':'')+(key=='search'?'?'+roll(url[key],1).substr(1):url[key]);
    };
    return out;
  };
  
  // устанавливаем значение ключа
  if(value) url.search[key]=value.toString(); 
  
  // сворачиваемся, с учетом порядка следования свойств
  return(roll(url));    
}

// применение
console.log(toUrl('http://site.my:81/home/path/page.htm?a=1&b=2#hash','a',5));

Если передан только URL, то функция возвращает как раз то, о чем я писал выше — объект, в котором вся структура URL «распихана» по свойствам. В дополнении к стандартному представлению из pathname выделен pagename, т.е. страница сайта, которая бывает востребована на практике.

Конечно, совсем без регулярных выражений не обошлось — разбор search параметров был предложен еще в 2010 году Стивеном Беннером (Steven Benner). Все же не стоит плодить велосипеды сверх необходимого.

Поздравляю всех читателей этого замечательного сайта с Новым Годом и Рождеством!
Теги:
javascript, url, parsing

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.