aboutsummaryrefslogtreecommitdiff
path: root/src/URITemplate.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/URITemplate.js')
-rw-r--r--src/URITemplate.js513
1 files changed, 513 insertions, 0 deletions
diff --git a/src/URITemplate.js b/src/URITemplate.js
new file mode 100644
index 000000000..b739eb4fd
--- /dev/null
+++ b/src/URITemplate.js
@@ -0,0 +1,513 @@
+/*!
+ * URI.js - Mutating URLs
+ * URI Template Support - http://tools.ietf.org/html/rfc6570
+ *
+ * Version: 1.18.2
+ *
+ * Author: Rodney Rehm
+ * Web: http://medialize.github.io/URI.js/
+ *
+ * Licensed under
+ * MIT License http://www.opensource.org/licenses/mit-license
+ *
+ */
+(function (root, factory) {
+ 'use strict';
+ // 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';
+ // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
+ /*jshint camelcase: false */
+
+ // 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%_.]/;
+ // pattern to verify literal integrity
+ URITemplate.LITERAL_PATTERN = /[<>{}'"`^| \\]/;
+
+ // 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;
+
+ 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;
+ }
+
+ if (d.type > 1 && variable.maxlength) {
+ // composite variable cannot specify maxlength
+ throw new Error('Invalid expression: Prefix modifier not applicable to variable "' + variable.name + '"');
+ }
+
+ // 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) {
+ // 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++) {
+ /*jshint laxbreak: true */
+ result += typeof this.parts[i] === 'string'
+ // literal string
+ ? this.parts[i]
+ // expression
+ : URITemplate.expand(this.parts[i], data);
+ /*jshint laxbreak: false */
+ }
+
+ 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;
+ var lPattern = URITemplate.LITERAL_PATTERN;
+ // token result buffer
+ var parts = [];
+ // position within source template
+ var pos = 0;
+ var variables, eMatch, vMatch;
+
+ var checkLiteral = function(literal) {
+ if (literal.match(lPattern)) {
+ throw new Error('Invalid Literal "' + literal + '"');
+ }
+ return literal;
+ };
+
+ // 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(checkLiteral(expression.substring(pos)));
+ break;
+ } else {
+ // push leading literal
+ parts.push(checkLiteral(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(checkLiteral(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;
+}));