JAL-1641 biojson documentation with docson
[jalview.git] / examples / biojson-doc / lib / jsonpointer.js
diff --git a/examples/biojson-doc/lib/jsonpointer.js b/examples/biojson-doc/lib/jsonpointer.js
new file mode 100755 (executable)
index 0000000..3a662e7
--- /dev/null
@@ -0,0 +1,343 @@
+/**
+ * @author Alexey Kuzmin <alex.s.kuzmin@gmail.com>
+ * @fileoverview JavaScript implementation of JSON Pointer.
+ * @see http://tools.ietf.org/html/rfc6901
+ */
+
+
+
+;(function() {
+  'use strict';
+
+  /**
+   * List of special characters and their escape sequences.
+   * Special characters will be unescaped in order they are listed.
+   * Section 3 of spec.
+   * @type {Array.<Array.<string>>}
+   * @const
+   */
+  var SPECIAL_CHARACTERS = [
+    ['/', '~1'],
+    ['~', '~0']
+  ];
+
+
+  /**
+   * Tokens' separator in JSON pointer string.
+   * Section 3 of spec.
+   * @type {string}
+   * @const
+   */
+  var TOKENS_SEPARATOR = '/';
+
+
+  /**
+   * Prefix for error messages.
+   * @type {string}
+   * @const
+   */
+  var ERROR_MESSAGE_PREFIX = 'JSON Pointer: ';
+
+
+  /**
+   * Validates non-empty pointer string.
+   * @type {RegExp}
+   * @const
+   */
+  var NON_EMPTY_POINTER_REGEXP = /(\/[^\/]*)+/;
+
+
+  /**
+   * List of error messages.
+   * Please keep it in alphabetical order.
+   * @enum {string}
+   */
+  var ErrorMessage = {
+    HYPHEN_IS_NOT_SUPPORTED_IN_ARRAY_CONTEXT:
+        'Implementation does not support "-" token for arrays.',
+    INVALID_DOCUMENT: 'JSON document is not valid.',
+    INVALID_DOCUMENT_TYPE: 'JSON document must be a string or object.',
+    INVALID_POINTER: 'Pointer is not valid.',
+    NON_NUMBER_TOKEN_IN_ARRAY_CONTEXT:
+        'Non-number tokens cannot be used in array context.',
+    TOKEN_WITH_LEADING_ZERO_IN_ARRAY_CONTEXT:
+        'Token with leading zero cannot be used in array context.'
+  };
+
+
+  /**
+   * Returns |target| object's value pointed by |opt_pointer|, returns undefined
+   * if |opt_pointer| points to non-existing value.
+   * If pointer is not provided, validates first argument and returns
+   * evaluator function that takes pointer as argument.
+   * @param {(string|Object|Array)} target Evaluation target.
+   * @param {string=} opt_pointer JSON Pointer string.
+   * @returns {*} Some value.
+   */
+  function getPointedValue(target, opt_pointer) {
+    // .get() method implementation.
+
+    // First argument must be either string or object.
+    if (isString(target)) {
+
+      // If string it must be valid JSON document.
+      try {
+        // Let's try to parse it as JSON.
+        target = JSON.parse(target);
+      }
+      catch (e) {
+        // If parsing failed, an exception will be thrown.
+        throw getError(ErrorMessage.INVALID_DOCUMENT);
+      }
+    }
+    else if (!isObject(target)) {
+      // If not object or string, an exception will be thrown.
+      throw getError(ErrorMessage.INVALID_DOCUMENT_TYPE);
+    }
+
+    // |target| is already parsed, let's create evaluator function for it.
+    var evaluator = createPointerEvaluator(target);
+
+    if (isUndefined(opt_pointer)) {
+      // If pointer was not provided, return evaluator function.
+      return evaluator;
+    }
+    else {
+      // If pointer is provided, return evaluation result.
+      return evaluator(opt_pointer);
+    }
+  }
+
+
+  /**
+   * Returns function that takes JSON Pointer as single argument
+   * and evaluates it in given |target| context.
+   * Returned function throws an exception if pointer is not valid
+   * or any error occurs during evaluation.
+   * @param {*} target Evaluation target.
+   * @returns {Function}
+   */
+  function createPointerEvaluator(target) {
+
+    // Use cache to store already received values.
+    var cache = {};
+
+    return function(pointer) {
+
+      if (!isValidJSONPointer(pointer)) {
+        // If it's not, an exception will be thrown.
+        throw getError(ErrorMessage.INVALID_POINTER);
+      }
+
+      // First, look up in the cache.
+      if (cache.hasOwnProperty(pointer)) {
+        // If cache entry exists, return it's value.
+        return cache[pointer];
+      }
+
+      // Now, when all arguments are valid, we can start evaluation.
+      // First of all, let's convert JSON pointer string to tokens list.
+      var tokensList = parsePointer(pointer);
+      var token;
+      var value = target;
+
+      // Evaluation will be continued till tokens list is not empty
+      // and returned value is not an undefined.
+      while (!isUndefined(value) && !isUndefined(token = tokensList.pop())) {
+        // Let's evaluate token in current context.
+        // `getValue()` might throw an exception, but we won't handle it.
+        value = getValue(value, token);
+      }
+
+      // Pointer evaluation is done, save value in the cache and return it.
+      cache[pointer] = value;
+      return value;
+    };
+  }
+
+
+  /**
+   * Returns true if given |pointer| is valid, returns false otherwise.
+   * @param {!string} pointer
+   * @returns {boolean} Whether pointer is valid.
+   */
+  function isValidJSONPointer(pointer) {
+    // Validates JSON pointer string.
+
+    if (!isString(pointer)) {
+      // If it's not a string, it obviously is not valid.
+      return false;
+    }
+
+    if ('' === pointer) {
+      // If it is string and is an empty string, it's valid.
+      return true;
+    }
+
+    // If it is non-empty string, it must match spec defined format.
+    // Check Section 3 of specification for concrete syntax.
+    return NON_EMPTY_POINTER_REGEXP.test(pointer);
+  }
+
+
+  /**
+   * Returns tokens list for given |pointer|. List is reversed, e.g.
+   *     '/simple/path' -> ['path', 'simple']
+   * @param {!string} pointer JSON pointer string.
+   * @returns {Array} List of tokens.
+   */
+  function parsePointer(pointer) {
+    // Converts JSON pointer string into tokens list.
+
+    // Let's split pointer string by tokens' separator character.
+    // Also we will reverse resulting array to simplify it's further usage.
+    var tokens = pointer.split(TOKENS_SEPARATOR).reverse();
+
+    // Last item in resulting array is always an empty string,
+    // we don't need it, let's remove it.
+    tokens.pop();
+
+    // Now tokens' array is ready to use, let's return it.
+    return tokens;
+  }
+
+
+  /**
+   * Decodes all escape sequences in given |rawReferenceToken|.
+   * @param {!string} rawReferenceToken
+   * @returns {string} Unescaped reference token.
+   */
+  function unescapeReferenceToken(rawReferenceToken) {
+    // Unescapes reference token. See Section 3 of specification.
+
+    var referenceToken = rawReferenceToken;
+    var character;
+    var escapeSequence;
+    var replaceRegExp;
+
+    // Order of unescaping does matter.
+    // That's why an array is used here and not hash.
+    SPECIAL_CHARACTERS.forEach(function(pair) {
+      character = pair[0];
+      escapeSequence = pair[1];
+      replaceRegExp = new RegExp(escapeSequence, 'g');
+      referenceToken = referenceToken.replace(replaceRegExp, character);
+    });
+
+    return referenceToken;
+  }
+
+
+  /**
+   * Returns value pointed by |token| in evaluation |context|.
+   * Throws an exception if any error occurs.
+   * @param {*} context Current evaluation context.
+   * @param {!string} token Unescaped reference token.
+   * @returns {*} Some value or undefined if value if not found.
+   */
+  function getValue(context, token) {
+    // Reference token evaluation. See Section 4 of spec.
+
+    // First of all we should unescape all special characters in token.
+    token = unescapeReferenceToken(token);
+
+    // Further actions depend of context of evaluation.
+
+    if (isArray(context)) {
+      // In array context there are more strict requirements
+      // for token value.
+
+      if ('-' === token) {
+        // Token cannot be a "-" character,
+        // it has no sense in current implementation.
+        throw getError(ErrorMessage.HYPHEN_IS_NOT_SUPPORTED_IN_ARRAY_CONTEXT);
+      }
+      if (!isNumber(token)) {
+        // Token cannot be non-number.
+        throw getError(ErrorMessage.NON_NUMBER_TOKEN_IN_ARRAY_CONTEXT);
+      }
+      if (token.length > 1 && '0' === token[0]) {
+        // Token cannot be non-zero number with leading zero.
+        throw getError(ErrorMessage.TOKEN_WITH_LEADING_ZERO_IN_ARRAY_CONTEXT);
+      }
+      // If all conditions are met, simply return element
+      // with token's value index.
+      // It might be undefined, but it's ok.
+      return context[token];
+    }
+
+    if (isObject(context)) {
+      // In object context we can simply return element w/ key equal to token.
+      // It might be undefined, but it's ok.
+      return context[token];
+    }
+
+    // If context is not an array or an object,
+    // token evaluation is not possible.
+    // This is the expected situation and so we won't throw an error,
+    // undefined value is perfectly suitable here.
+    return;
+  }
+
+
+  /**
+   * Returns Error instance for throwing.
+   * @param {string} message Error message.
+   * @returns {Error}
+   */
+  function getError(message) {
+    return new Error(ERROR_MESSAGE_PREFIX + message);
+  }
+
+
+  function isObject(o) {
+    return 'object' === typeof o && null !== o;
+  }
+
+
+  function isArray(a) {
+    return Array.isArray(a);
+  }
+
+
+  function isNumber(n) {
+    return !isNaN(Number(n));
+  }
+
+
+  function isString(s) {
+    return 'string' === typeof s || s instanceof String;
+  }
+
+
+  function isUndefined(v) {
+    return 'undefined' === typeof v;
+  }
+
+
+  // Let's expose API to the world.
+
+  var jsonpointer = {
+    get: getPointedValue
+  };
+
+  if ('object' === typeof exports) {
+    // If `exports` is an object, we are in Node.js context.
+    // We are supposed to act as Node.js package.
+    module.exports = jsonpointer;
+  } else if ('function' === typeof define && define.amd) {
+    // If there is global function `define()` and `define.amd` is defined,
+    // we are supposed to act as AMD module.
+    define(function() {
+      return jsonpointer;
+    });
+  } else {
+    // Last resort.
+    // Let's create global `jsonpointer` object.
+    this.jsonpointer = jsonpointer;
+  }
+
+}).call((function() {
+  'use strict';
+  return (typeof window !== 'undefined' ? window : global);
+})());