UNPKG

tiny-uri

Version:

Lightweight Javascript library for handling URLs

473 lines (429 loc) 11.2 kB
'use strict'; /** * Class to manage URL paths */ class Path { /** * @param {string} f - string path * @param {object} ctx - context of Uri class */ constructor(f, ctx = {}) { this.ctx = ctx; this._path = []; return this.parse(f); } /** * Append to a path * @param {string} s path to append * @return {instance} for chaining */ append(s) { this._path.push(s); return this.ctx; } /** * Delete end of path * @param {integer} loc - segment of path to delete * @return {instance} for chaining */ delete(loc) { if (Array.isArray(loc)) { loc.reverse().forEach(l => this._path.splice(l, 1)); } else if (Number.isInteger(loc)) { this._path.splice(loc, 1); } else { this._path.pop(); } return this.ctx; } /** * Get the path * @return {array} path as array */ get() { return this._path; } /** * Parse the path part of a URl * @param {string} f - string path * @return {instance} for chaining */ parse(f = '') { let path = decodeURIComponent(f); let split = path.split('/'); if (Array.isArray(split)) { if(path.match(/^\//)) split.shift(); if (split[0] === '') split.shift(); if (split.length > 1 && path.match(/\/$/)) split.pop(); this._path = split; } return this; } /** * Replace part of a path * @param {string} f - path replacement * @param {integer} loc - location to replace * @return {instance} for chaining */ replace(f, loc) { if (loc === 'file') { this._path.splice(this._path.length - 1, 1, f); return this.ctx; } else if (Number.isInteger(loc)) { this._path.splice(loc, 1, f); return this.ctx; } this.parse(f); return this.ctx; } /** * Get string representatio of the path or the uri * @param {boolen} uri - if true return string represention of uri * @return {string} path or uri as string */ toString(uri) { if (uri) return this.ctx.toString(); return Array.isArray(this._path) ? this._path.join('/') : ''; } } /** * Class to manage query part of URL */ class Query { /** * @param {string} f - query string * @param {object} ctx - context of uri instance * @return {instance} for chaining */ constructor (f, ctx = {}) { Object.assign(this, ctx); this.ctx = ctx; this.set(f); return this } /** * Add a query string * @param {object} obj {name: 'value'} * @return {instance} for chaining */ add (obj = {}) { this._query = this._convert(obj, this._query[0], this._query[1]); return this.ctx } /** * Remove the query string * @return {instance} for chaining */ clear () { this._query = [[], []]; return this.ctx } _convert (obj, p = [], q = []) { for (const key in obj) { if (Array.isArray(obj[key])) { for (let i = 0; i < obj[key].length; i++) { const val = obj[key][i]; p.push(key); q.push(val); } } else if (obj[key]) { p.push(key); q.push(obj[key]); } } return [p, q] } /** * Get the query string or get the value of a single query parameter * @param {string} name representing single query string * @returns {array} or {string} representing the query string the value of a single query parameter */ get (name) { const dict = {}; const obj = this._query; for (let i = 0; i < obj[0].length; i++) { const k = obj[0][i]; const v = obj[1][i]; if (dict[k]) { dict[k].push(v); } else { dict[k] = [v]; } } if (name) return dict[name] && dict[name].length ? dict[name][0] : null return dict } getUrlTemplateQuery () { return this._urlTemplateQueryString } /** * Merge with the query string - replaces query string values if they exist * @param {object} obj {name: 'value'} * @return {instance} for chaining */ merge (obj) { const p = this._query[0]; const q = this._query[1]; for (const key in obj) { let kset = false; for (let i = 0; i < p.length; i++) { const xKey = p[i]; if (key === xKey) { if (kset) { p.splice(i, 1); q.splice(i, 1); continue } if (Array.isArray(obj[key])) { q[i] = obj[key].shift(); } else if (typeof obj[key] === 'undefined' || obj[key] === null) { p.splice(i, 1); q.splice(i, 1); delete obj[key]; } else { q[i] = obj[key]; delete obj[key]; } kset = true; } } } this._query = this._convert(obj, this._query[0], this._query[1]); return this.ctx } _parse (q = '') { const struct = [[], []]; const pairs = q.split(/&|;/); for (let j = 0; j < pairs.length; j++) { const pair = pairs[j]; const nPair = pair.match(this.qRegEx); if (nPair && typeof nPair[nPair.length - 1] !== 'undefined') { nPair.shift(); for (let i = 0; i < nPair.length; i++) { const p = nPair[i]; struct[i].push(decodeURIComponent(p.replace('+', ' ', 'g'))); } } } return struct } /** * Set with the query string - replaces existing query string * @param {obj} or {string} ...q * @return {instance} for chaining */ set (...q) { const args = [...q]; if (args.length === 1) { if (typeof args[0] === 'object') { this._query = this._convert(args[0]); } else { this._query = this._parse(args[0]); } } else if (args.length === 0) { this.clear(); } else { const obj = {}; obj[args[0]] = args[1]; this.merge(obj); } return this.ctx } /** * Set the url template query string vale * @param {string} s url-template query string * @return {instance} for chaining */ setUrlTemplateQuery (s) { this._urlTemplateQueryString = s; } /** * Get string representation of the path or the uri * @param {boolean} uri - if true return string representation of uri * @return {string} query or uri as string */ toString (uri) { if (uri) return this.ctx.toString() const pairs = []; const n = this._query[0]; const v = this._query[1]; for (let i = 0; i < n.length; i++) { pairs.push(encodeURIComponent(n[i]) + '=' + encodeURIComponent(v[i])); } return pairs.join('&') } } /** * Class to make it easier to build strings */ class StringBuilder { /** * @param {string} string - starting string (optional) * @return {instance} for chaining */ constructor(string) { if (!string || typeof string === 'undefined') this.string = String(""); else this.string = String(string); } /** * Return full string * @return {string} assembled string */ toString() { return this.string; } /** * Append a string to an existing string * @param {string} val - string to be appended * @return {instance} for chaining */ append(val) { this.string += val; return this; } /** * Insert a string to an existing string * @param {integer} pos - position at which to insert value * @param {string} val - string to be inserted * @return {instance} for chaining */ insert(pos, val) { this.string.length; let left = this.string.slice(0, pos); let right = this.string.slice(pos); this.string = left + val + right; return this; } } /** * Uri - manipulate URLs */ class TinyUri { /** * @param {string} uri - a URI string * @return {instance} - return Uri instance for chaining */ constructor(uri) { this.uriRegEx = /^(([^:/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; this.authRegEx = /^([^@]+)@/; this.portRegEx = /:(\d+)$/; this.qRegEx = /^([^=]+)(?:=(.*))?$/; this.urlTempQueryRegEx = /\{\?(.*?)\}/; return this.parse(uri); } /** * @param {string} authority - username password part of URL * @return {instance} - returns Uri instance for chaining */ authority(authority = '') { if (authority !== '') { let auth = authority.match(this.authRegEx); this._authority = authority; if (auth) { authority = authority.replace(this.authRegEx, ''); this.userInfo(auth[1]); } let port = authority.match(this.portRegEx); if(port) { authority = authority.replace(this.portRegEx, ''); this.port(port[1]); } this.host(authority.replace('{', '')); return this; } let userinfo = this.userInfo(); if (userinfo) authority = userinfo + '@'; authority = authority + this.host(); let port = this.port(); if (port) authority = authority + (':' + port); return authority; } /** * @param {string} f - string representation of fragment * @return {instance} - returns Uri instance for chaining */ fragment(f = '') { return this.gs(f, '_fragment'); } gs(val, tar, fn) { if (typeof val !== 'undefined') { this[tar] = val; return this; } return fn ? fn(this[tar]) : this[tar] ? this[tar] : ''; } /** * @param {string} f - string representation of host * @return {instance} - returns Uri instance for chaining */ host(f) { return this.gs(f, '_host'); } /** * @param {string} uri - URL * @return {instance} - returns Uri instance for chaining */ parse(uri) { let f = uri ? uri.match(this.uriRegEx) : []; let t = uri ? uri.match(this.urlTempQueryRegEx) : []; this.scheme(f[2]); this.authority(f[4]); this.path = new Path(f[5] ? f[5].replace(/{$/, '') : '', this); this.fragment(f[9]); this.query = new Query(f[7] ? f[7] : '', this); if (t) this.query.setUrlTemplateQuery(t[1]); return this; } /** * @param {string} f - port part of URL * @return {instance} - returns Uri instance for chaining */ port(f) { return this.gs(f, '_port'); } /** * @param {string} f - protocol part of URL * @return {instance} - returns Uri instance for chaining */ protocol(f) { return (this._scheme || '').toLowerCase(); } /** * @param {string} f - protocol scheme * @return {instance} - returns Uri instance for chaining */ scheme(f) { return this.gs(f, '_scheme'); } /** * @param {string} f - user info part of URL * @return {instance} - returns Uri instance for chaining */ userInfo(f) { return this.gs(f, '_userinfo', (r) => { return r ? encodeURI(r) : r; }); } /** * @return {string} - returns string URL */ toString() { let q = this.query.toString(); let p = this.path.toString(); this.fragment(); let s = this.scheme(); let str = new StringBuilder(); let retStr = str.append(s ? s + '://' : "") .append(this.authority()) .append('/').append(p) .append(q !== '' ? '?' : '') .append(q) .toString() .replace('/?', '?') .replace(/\/$/, ''); return retStr; } static clone(uri) { return new TinyUri(uri.toString()); } } module.exports = TinyUri;