Skip to content

Instantly share code, notes, and snippets.

@tjntun
Created July 22, 2013 08:16
Show Gist options
  • Select an option

  • Save tjntun/6052167 to your computer and use it in GitHub Desktop.

Select an option

Save tjntun/6052167 to your computer and use it in GitHub Desktop.

Revisions

  1. tjntun created this gist Jul 22, 2013.
    265 changes: 265 additions & 0 deletions purl.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,265 @@
    /*
    * Purl (A JavaScript URL parser) v2.3.1
    * Developed and maintanined by Mark Perkins, [email protected]
    * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser
    * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details.
    */

    ;(function(factory) {
    if (typeof define === 'function' && define.amd) {
    define(factory);
    } else {
    window.purl = factory();
    }
    })(function() {

    var tag2attr = {
    a : 'href',
    img : 'src',
    form : 'action',
    base : 'href',
    script : 'src',
    iframe : 'src',
    link : 'href'
    },

    key = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'], // keys available to query

    aliases = { 'anchor' : 'fragment' }, // aliases for backwards compatability

    parser = {
    strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs
    loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
    },

    isint = /^[0-9]+$/;

    function parseUri( url, strictMode ) {
    var str = decodeURI( url ),
    res = parser[ strictMode || false ? 'strict' : 'loose' ].exec( str ),
    uri = { attr : {}, param : {}, seg : {} },
    i = 14;

    while ( i-- ) {
    uri.attr[ key[i] ] = res[i] || '';
    }

    // build query and fragment parameters
    uri.param['query'] = parseString(uri.attr['query']);
    uri.param['fragment'] = parseString(uri.attr['fragment']);

    // split path and fragement into segments
    uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');
    uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');

    // compile a 'base' domain attribute
    uri.attr['base'] = uri.attr.host ? (uri.attr.protocol ? uri.attr.protocol+'://'+uri.attr.host : uri.attr.host) + (uri.attr.port ? ':'+uri.attr.port : '') : '';

    return uri;
    }

    function getAttrName( elm ) {
    var tn = elm.tagName;
    if ( typeof tn !== 'undefined' ) return tag2attr[tn.toLowerCase()];
    return tn;
    }

    function promote(parent, key) {
    if (parent[key].length === 0) return parent[key] = {};
    var t = {};
    for (var i in parent[key]) t[i] = parent[key][i];
    parent[key] = t;
    return t;
    }

    function parse(parts, parent, key, val) {
    var part = parts.shift();
    if (!part) {
    if (isArray(parent[key])) {
    parent[key].push(val);
    } else if ('object' == typeof parent[key]) {
    parent[key] = val;
    } else if ('undefined' == typeof parent[key]) {
    parent[key] = val;
    } else {
    parent[key] = [parent[key], val];
    }
    } else {
    var obj = parent[key] = parent[key] || [];
    if (']' == part) {
    if (isArray(obj)) {
    if ('' !== val) obj.push(val);
    } else if ('object' == typeof obj) {
    obj[keys(obj).length] = val;
    } else {
    obj = parent[key] = [parent[key], val];
    }
    } else if (~part.indexOf(']')) {
    part = part.substr(0, part.length - 1);
    if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
    parse(parts, obj, part, val);
    // key
    } else {
    if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
    parse(parts, obj, part, val);
    }
    }
    }

    function merge(parent, key, val) {
    if (~key.indexOf(']')) {
    var parts = key.split('[');
    parse(parts, parent, 'base', val);
    } else {
    if (!isint.test(key) && isArray(parent.base)) {
    var t = {};
    for (var k in parent.base) t[k] = parent.base[k];
    parent.base = t;
    }
    if (key !== '') {
    set(parent.base, key, val);
    }
    }
    return parent;
    }

    function parseString(str) {
    return reduce(String(str).split(/&|;/), function(ret, pair) {
    try {
    pair = decodeURIComponent(pair.replace(/\+/g, ' '));
    } catch(e) {
    // ignore
    }
    var eql = pair.indexOf('='),
    brace = lastBraceInKey(pair),
    key = pair.substr(0, brace || eql),
    val = pair.substr(brace || eql, pair.length);

    val = val.substr(val.indexOf('=') + 1, val.length);

    if (key === '') {
    key = pair;
    val = '';
    }

    return merge(ret, key, val);
    }, { base: {} }).base;
    }

    function set(obj, key, val) {
    var v = obj[key];
    if (typeof v === 'undefined') {
    obj[key] = val;
    } else if (isArray(v)) {
    v.push(val);
    } else {
    obj[key] = [v, val];
    }
    }

    function lastBraceInKey(str) {
    var len = str.length,
    brace,
    c;
    for (var i = 0; i < len; ++i) {
    c = str[i];
    if (']' == c) brace = false;
    if ('[' == c) brace = true;
    if ('=' == c && !brace) return i;
    }
    }

    function reduce(obj, accumulator){
    var i = 0,
    l = obj.length >> 0,
    curr = arguments[2];
    while (i < l) {
    if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj);
    ++i;
    }
    return curr;
    }

    function isArray(vArg) {
    return Object.prototype.toString.call(vArg) === "[object Array]";
    }

    function keys(obj) {
    var key_array = [];
    for ( var prop in obj ) {
    if ( obj.hasOwnProperty(prop) ) key_array.push(prop);
    }
    return key_array;
    }

    function purl( url, strictMode ) {
    if ( arguments.length === 1 && url === true ) {
    strictMode = true;
    url = undefined;
    }
    strictMode = strictMode || false;
    url = url || window.location.toString();

    return {

    data : parseUri(url, strictMode),

    // get various attributes from the URI
    attr : function( attr ) {
    attr = aliases[attr] || attr;
    return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr;
    },

    // return query string parameters
    param : function( param ) {
    return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query;
    },

    // return fragment parameters
    fparam : function( param ) {
    return typeof param !== 'undefined' ? this.data.param.fragment[param] : this.data.param.fragment;
    },

    // return path segments
    segment : function( seg ) {
    if ( typeof seg === 'undefined' ) {
    return this.data.seg.path;
    } else {
    seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end
    return this.data.seg.path[seg];
    }
    },

    // return fragment segments
    fsegment : function( seg ) {
    if ( typeof seg === 'undefined' ) {
    return this.data.seg.fragment;
    } else {
    seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end
    return this.data.seg.fragment[seg];
    }
    }

    };

    }

    purl.jQuery = function($){
    if ($ != null) {
    $.fn.url = function( strictMode ) {
    var url = '';
    if ( this.length ) {
    url = $(this).attr( getAttrName(this[0]) ) || '';
    }
    return purl( url, strictMode );
    };

    $.url = purl;
    }
    };

    purl.jQuery(window.jQuery);

    return purl;

    });