495 lines
15 KiB
JavaScript
Executable File
495 lines
15 KiB
JavaScript
Executable File
/*!
|
|
* URI.js - Mutating URLs
|
|
* URI Template Support - http://tools.ietf.org/html/rfc6570
|
|
*
|
|
* Version: 1.11.2
|
|
*
|
|
* Author: Rodney Rehm
|
|
* Web: http://medialize.github.com/URI.js/
|
|
*
|
|
* Licensed under
|
|
* MIT License http://www.opensource.org/licenses/mit-license
|
|
* GPL v3 http://opensource.org/licenses/GPL-3.0
|
|
*
|
|
*/
|
|
(function (root, factory) {
|
|
// https://github.com/umdjs/umd/blob/master/returnExports.js
|
|
if (typeof exports === 'object') {
|
|
// Node
|
|
module.exports = factory(require('./URI'));
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define(['./URI'], factory);
|
|
} else {
|
|
// Browser globals (root is window)
|
|
root.URITemplate = factory(root.URI, root);
|
|
}
|
|
}(this, function (URI, root) {
|
|
"use strict";
|
|
|
|
// save current URITemplate variable, if any
|
|
var _URITemplate = root && root.URITemplate;
|
|
|
|
var hasOwn = Object.prototype.hasOwnProperty;
|
|
function URITemplate(expression) {
|
|
// serve from cache where possible
|
|
if (URITemplate._cache[expression]) {
|
|
return URITemplate._cache[expression];
|
|
}
|
|
|
|
// Allow instantiation without the 'new' keyword
|
|
if (!(this instanceof URITemplate)) {
|
|
return new URITemplate(expression);
|
|
}
|
|
|
|
this.expression = expression;
|
|
URITemplate._cache[expression] = this;
|
|
return this;
|
|
}
|
|
|
|
function Data(data) {
|
|
this.data = data;
|
|
this.cache = {};
|
|
}
|
|
|
|
var p = URITemplate.prototype;
|
|
// list of operators and their defined options
|
|
var operators = {
|
|
// Simple string expansion
|
|
'' : {
|
|
prefix: "",
|
|
separator: ",",
|
|
named: false,
|
|
empty_name_separator: false,
|
|
encode : "encode"
|
|
},
|
|
// Reserved character strings
|
|
'+' : {
|
|
prefix: "",
|
|
separator: ",",
|
|
named: false,
|
|
empty_name_separator: false,
|
|
encode : "encodeReserved"
|
|
},
|
|
// Fragment identifiers prefixed by "#"
|
|
'#' : {
|
|
prefix: "#",
|
|
separator: ",",
|
|
named: false,
|
|
empty_name_separator: false,
|
|
encode : "encodeReserved"
|
|
},
|
|
// Name labels or extensions prefixed by "."
|
|
'.' : {
|
|
prefix: ".",
|
|
separator: ".",
|
|
named: false,
|
|
empty_name_separator: false,
|
|
encode : "encode"
|
|
},
|
|
// Path segments prefixed by "/"
|
|
'/' : {
|
|
prefix: "/",
|
|
separator: "/",
|
|
named: false,
|
|
empty_name_separator: false,
|
|
encode : "encode"
|
|
},
|
|
// Path parameter name or name=value pairs prefixed by ";"
|
|
';' : {
|
|
prefix: ";",
|
|
separator: ";",
|
|
named: true,
|
|
empty_name_separator: false,
|
|
encode : "encode"
|
|
},
|
|
// Query component beginning with "?" and consisting
|
|
// of name=value pairs separated by "&"; an
|
|
'?' : {
|
|
prefix: "?",
|
|
separator: "&",
|
|
named: true,
|
|
empty_name_separator: true,
|
|
encode : "encode"
|
|
},
|
|
// Continuation of query-style &name=value pairs
|
|
// within a literal query component.
|
|
'&' : {
|
|
prefix: "&",
|
|
separator: "&",
|
|
named: true,
|
|
empty_name_separator: true,
|
|
encode : "encode"
|
|
}
|
|
|
|
// The operator characters equals ("="), comma (","), exclamation ("!"),
|
|
// at sign ("@"), and pipe ("|") are reserved for future extensions.
|
|
};
|
|
|
|
// storage for already parsed templates
|
|
URITemplate._cache = {};
|
|
// pattern to identify expressions [operator, variable-list] in template
|
|
URITemplate.EXPRESSION_PATTERN = /\{([^a-zA-Z0-9%_]?)([^\}]+)(\}|$)/g;
|
|
// pattern to identify variables [name, explode, maxlength] in variable-list
|
|
URITemplate.VARIABLE_PATTERN = /^([^*:]+)((\*)|:(\d+))?$/;
|
|
// pattern to verify variable name integrity
|
|
URITemplate.VARIABLE_NAME_PATTERN = /[^a-zA-Z0-9%_]/;
|
|
|
|
// expand parsed expression (expression, not template!)
|
|
URITemplate.expand = function(expression, data) {
|
|
// container for defined options for the given operator
|
|
var options = operators[expression.operator];
|
|
// expansion type (include keys or not)
|
|
var type = options.named ? "Named" : "Unnamed";
|
|
// list of variables within the expression
|
|
var variables = expression.variables;
|
|
// result buffer for evaluating the expression
|
|
var buffer = [];
|
|
var d, variable, i, l, value;
|
|
|
|
for (i = 0; variable = variables[i]; i++) {
|
|
// fetch simplified data source
|
|
d = data.get(variable.name);
|
|
if (!d.val.length) {
|
|
if (d.type) {
|
|
// empty variables (empty string)
|
|
// still lead to a separator being appended!
|
|
buffer.push("");
|
|
}
|
|
// no data, no action
|
|
continue;
|
|
}
|
|
|
|
// expand the given variable
|
|
buffer.push(URITemplate["expand" + type](
|
|
d,
|
|
options,
|
|
variable.explode,
|
|
variable.explode && options.separator || ",",
|
|
variable.maxlength,
|
|
variable.name
|
|
));
|
|
}
|
|
|
|
if (buffer.length) {
|
|
return options.prefix + buffer.join(options.separator);
|
|
} else {
|
|
// prefix is not prepended for empty expressions
|
|
return "";
|
|
}
|
|
};
|
|
// expand a named variable
|
|
URITemplate.expandNamed = function(d, options, explode, separator, length, name) {
|
|
// variable result buffer
|
|
var result = "";
|
|
// peformance crap
|
|
var encode = options.encode;
|
|
var empty_name_separator = options.empty_name_separator;
|
|
// flag noting if values are already encoded
|
|
var _encode = !d[encode].length;
|
|
// key for named expansion
|
|
var _name = d.type === 2 ? '': URI[encode](name);
|
|
var _value, i, l;
|
|
|
|
// for each found value
|
|
for (i = 0, l = d.val.length; i < l; i++) {
|
|
if (length) {
|
|
// maxlength must be determined before encoding can happen
|
|
_value = URI[encode](d.val[i][1].substring(0, length));
|
|
if (d.type === 2) {
|
|
// apply maxlength to keys of objects as well
|
|
_name = URI[encode](d.val[i][0].substring(0, length));
|
|
}
|
|
} else if (_encode) {
|
|
// encode value
|
|
_value = URI[encode](d.val[i][1]);
|
|
if (d.type === 2) {
|
|
// encode name and cache encoded value
|
|
_name = URI[encode](d.val[i][0]);
|
|
d[encode].push([_name, _value]);
|
|
} else {
|
|
// cache encoded value
|
|
d[encode].push([undefined, _value]);
|
|
}
|
|
} else {
|
|
// values are already encoded and can be pulled from cache
|
|
_value = d[encode][i][1];
|
|
if (d.type === 2) {
|
|
_name = d[encode][i][0];
|
|
}
|
|
}
|
|
|
|
if (result) {
|
|
// unless we're the first value, prepend the separator
|
|
result += separator;
|
|
}
|
|
|
|
if (!explode) {
|
|
if (!i) {
|
|
// first element, so prepend variable name
|
|
result += URI[encode](name) + (empty_name_separator || _value ? "=" : "");
|
|
}
|
|
|
|
if (d.type === 2) {
|
|
// without explode-modifier, keys of objects are returned comma-separated
|
|
result += _name + ",";
|
|
}
|
|
|
|
result += _value;
|
|
} else {
|
|
// only add the = if it is either default (?&) or there actually is a value (;)
|
|
result += _name + (empty_name_separator || _value ? "=" : "") + _value;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
// expand an unnamed variable
|
|
URITemplate.expandUnnamed = function(d, options, explode, separator, length, name) {
|
|
// variable result buffer
|
|
var result = "";
|
|
// performance crap
|
|
var encode = options.encode;
|
|
var empty_name_separator = options.empty_name_separator;
|
|
// flag noting if values are already encoded
|
|
var _encode = !d[encode].length;
|
|
var _name, _value, i, l;
|
|
|
|
// for each found value
|
|
for (i = 0, l = d.val.length; i < l; i++) {
|
|
if (length) {
|
|
// maxlength must be determined before encoding can happen
|
|
_value = URI[encode](d.val[i][1].substring(0, length));
|
|
} else if (_encode) {
|
|
// encode and cache value
|
|
_value = URI[encode](d.val[i][1]);
|
|
d[encode].push([
|
|
d.type === 2 ? URI[encode](d.val[i][0]) : undefined,
|
|
_value
|
|
]);
|
|
} else {
|
|
// value already encoded, pull from cache
|
|
_value = d[encode][i][1];
|
|
}
|
|
|
|
if (result) {
|
|
// unless we're the first value, prepend the separator
|
|
result += separator;
|
|
}
|
|
|
|
if (d.type === 2) {
|
|
if (length) {
|
|
// maxlength also applies to keys of objects
|
|
_name = URI[encode](d.val[i][0].substring(0, length));
|
|
} else {
|
|
// at this point the name must already be encoded
|
|
_name = d[encode][i][0];
|
|
}
|
|
|
|
result += _name;
|
|
if (explode) {
|
|
// explode-modifier separates name and value by "="
|
|
result += (empty_name_separator || _value ? "=" : "");
|
|
} else {
|
|
// no explode-modifier separates name and value by ","
|
|
result += ",";
|
|
}
|
|
}
|
|
|
|
result += _value;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
URITemplate.noConflict = function() {
|
|
if (root.URITemplate === URITemplate) {
|
|
root.URITemplate = _URITemplate;
|
|
}
|
|
|
|
return URITemplate;
|
|
};
|
|
|
|
// expand template through given data map
|
|
p.expand = function(data) {
|
|
var result = "";
|
|
|
|
if (!this.parts || !this.parts.length) {
|
|
// lazilyy parse the template
|
|
this.parse();
|
|
}
|
|
|
|
if (!(data instanceof Data)) {
|
|
// make given data available through the
|
|
// optimized data handling thingie
|
|
data = new Data(data);
|
|
}
|
|
|
|
for (var i = 0, l = this.parts.length; i < l; i++) {
|
|
result += typeof this.parts[i] === "string"
|
|
// literal string
|
|
? this.parts[i]
|
|
// expression
|
|
: URITemplate.expand(this.parts[i], data);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
// parse template into action tokens
|
|
p.parse = function() {
|
|
// performance crap
|
|
var expression = this.expression;
|
|
var ePattern = URITemplate.EXPRESSION_PATTERN;
|
|
var vPattern = URITemplate.VARIABLE_PATTERN;
|
|
var nPattern = URITemplate.VARIABLE_NAME_PATTERN;
|
|
// token result buffer
|
|
var parts = [];
|
|
// position within source template
|
|
var pos = 0;
|
|
var variables, eMatch, vMatch;
|
|
|
|
// RegExp is shared accross all templates,
|
|
// which requires a manual reset
|
|
ePattern.lastIndex = 0;
|
|
// I don't like while(foo = bar()) loops,
|
|
// to make things simpler I go while(true) and break when required
|
|
while (true) {
|
|
eMatch = ePattern.exec(expression);
|
|
if (eMatch === null) {
|
|
// push trailing literal
|
|
parts.push(expression.substring(pos));
|
|
break;
|
|
} else {
|
|
// push leading literal
|
|
parts.push(expression.substring(pos, eMatch.index));
|
|
pos = eMatch.index + eMatch[0].length;
|
|
}
|
|
|
|
if (!operators[eMatch[1]]) {
|
|
throw new Error('Unknown Operator "' + eMatch[1] + '" in "' + eMatch[0] + '"');
|
|
} else if (!eMatch[3]) {
|
|
throw new Error('Unclosed Expression "' + eMatch[0] + '"');
|
|
}
|
|
|
|
// parse variable-list
|
|
variables = eMatch[2].split(',');
|
|
for (var i = 0, l = variables.length; i < l; i++) {
|
|
vMatch = variables[i].match(vPattern);
|
|
if (vMatch === null) {
|
|
throw new Error('Invalid Variable "' + variables[i] + '" in "' + eMatch[0] + '"');
|
|
} else if (vMatch[1].match(nPattern)) {
|
|
throw new Error('Invalid Variable Name "' + vMatch[1] + '" in "' + eMatch[0] + '"');
|
|
}
|
|
|
|
variables[i] = {
|
|
name: vMatch[1],
|
|
explode: !!vMatch[3],
|
|
maxlength: vMatch[4] && parseInt(vMatch[4], 10)
|
|
};
|
|
}
|
|
|
|
if (!variables.length) {
|
|
throw new Error('Expression Missing Variable(s) "' + eMatch[0] + '"');
|
|
}
|
|
|
|
parts.push({
|
|
expression: eMatch[0],
|
|
operator: eMatch[1],
|
|
variables: variables
|
|
});
|
|
}
|
|
|
|
if (!parts.length) {
|
|
// template doesn't contain any expressions
|
|
// so it is a simple literal string
|
|
// this probably should fire a warning or something?
|
|
parts.push(expression);
|
|
}
|
|
|
|
this.parts = parts;
|
|
return this;
|
|
};
|
|
|
|
// simplify data structures
|
|
Data.prototype.get = function(key) {
|
|
// performance crap
|
|
var data = this.data;
|
|
// cache for processed data-point
|
|
var d = {
|
|
// type of data 0: undefined/null, 1: string, 2: object, 3: array
|
|
type: 0,
|
|
// original values (except undefined/null)
|
|
val: [],
|
|
// cache for encoded values (only for non-maxlength expansion)
|
|
encode: [],
|
|
encodeReserved: []
|
|
};
|
|
var i, l, value;
|
|
|
|
if (this.cache[key] !== undefined) {
|
|
// we've already processed this key
|
|
return this.cache[key];
|
|
}
|
|
|
|
this.cache[key] = d;
|
|
|
|
if (String(Object.prototype.toString.call(data)) === "[object Function]") {
|
|
// data itself is a callback (global callback)
|
|
value = data(key);
|
|
} else if (String(Object.prototype.toString.call(data[key])) === "[object Function]") {
|
|
// data is a map of callbacks (local callback)
|
|
value = data[key](key);
|
|
} else {
|
|
// data is a map of data
|
|
value = data[key];
|
|
}
|
|
|
|
// generalize input into [ [name1, value1], [name2, value2], … ]
|
|
// so expansion has to deal with a single data structure only
|
|
if (value === undefined || value === null) {
|
|
// undefined and null values are to be ignored completely
|
|
return d;
|
|
} else if (String(Object.prototype.toString.call(value)) === "[object Array]") {
|
|
for (i = 0, l = value.length; i < l; i++) {
|
|
if (value[i] !== undefined && value[i] !== null) {
|
|
// arrays don't have names
|
|
d.val.push([undefined, String(value[i])]);
|
|
}
|
|
}
|
|
|
|
if (d.val.length) {
|
|
// only treat non-empty arrays as arrays
|
|
d.type = 3; // array
|
|
}
|
|
} else if (String(Object.prototype.toString.call(value)) === "[object Object]") {
|
|
for (i in value) {
|
|
if (hasOwn.call(value, i) && value[i] !== undefined && value[i] !== null) {
|
|
// objects have keys, remember them for named expansion
|
|
d.val.push([i, String(value[i])]);
|
|
}
|
|
}
|
|
|
|
if (d.val.length) {
|
|
// only treat non-empty objects as objects
|
|
d.type = 2; // object
|
|
}
|
|
} else {
|
|
d.type = 1; // primitive string (could've been string, number, boolean and objects with a toString())
|
|
// arrays don't have names
|
|
d.val.push([undefined, String(value)]);
|
|
}
|
|
|
|
return d;
|
|
};
|
|
|
|
// hook into URI for fluid access
|
|
URI.expand = function(expression, data) {
|
|
var template = new URITemplate(expression);
|
|
var expansion = template.expand(data);
|
|
|
|
return new URI(expansion);
|
|
};
|
|
|
|
return URITemplate;
|
|
}));
|