UNPKG

diffusion

Version:

Diffusion JavaScript client

339 lines (272 loc) 8.04 kB
/*eslint valid-jsdoc: "off"*/ /** * JSON Pointer implementation that allows incremental building of pointers. * * @param nodes - Segments from parent context * @constructor * @since 5.9 */ function JSONPointer(nodes) { /** * The path segments * @property */ this.segments = nodes; } /** * @returns {JSONPointer} a new instance equal to this instance with key appended */ JSONPointer.prototype.withKey = function (key) { return this.withSegment(new KeySegment(key)); }; /** * @returns {JSONPointer} a new instance equal to this instance with index appended */ JSONPointer.prototype.withIndex = function (index) { return this.withSegment(new IndexSegment(index)); }; /** * @returns {JSONPointer} a new instance equal to this instance with segment appended */ JSONPointer.prototype.withSegment = function (newNode) { var newNodes = this.segments.concat(newNode); return new JSONPointer(newNodes); }; /** * Returns whether this JSONPointer is equal to another JSONPointer, * ignoring the values of array index segments. * <p> * This _implements an equivalence relation between JSONPointers that * considers two values equal if they have the same number of segments, * corresponding segments are of the same type, and corresponding key * segments are equal. It is used by the structural delta code to identify * potential matches between two pointers to different values. * * @param {JSONPointer} other - The other pointer to compare to * @returns {boolean} whether the other pointer is equal (ignoring indexes) */ JSONPointer.prototype.equalIgnoringIndexes = function (other) { if (other === this) { return true; } var length = this.segments.length; if (length !== other.segments.length) { return false; } for (var i = 0; i < length; ++i) { var s = this.segments[i]; var o = other.segments[i]; if (s instanceof IndexSegment) { if (o instanceof KeySegment) { return false; } } else if (!s.equals(o)) { return false; } } return true; }; /** * @returns {string} The JSONPointer expression */ JSONPointer.prototype.toString = function () { var parts = []; for (var i = 0; i < this.segments.length; ++i) { parts.push(this.segments[i].toString()); } return parts.join(''); }; /** * Non-equal JSONPointer instances can have the same expression. * E.g. ROOT.withIndex(1) is not equal to ROOT.withKey("1"), but both have the same expression "/1" * * @param {JSONPointer} other - The other pointer instance to compare to * @returns {boolean} - whether the two instances are equal */ JSONPointer.prototype.equals = function (other) { if (other === this) { return true; } if (!other || !(other instanceof JSONPointer)) { return false; } if (this.segments.length !== other.segments.length) { return false; } var length = this.segments.length; for (var i = 0; i < length; ++i) { var s1 = this.segments[i]; var s2 = other.segments[i]; if (!s1.equals(s2)) { return false; } } return true; }; /** * JSONPointer representing the root * @type {JSONPointer} */ JSONPointer.ROOT = new JSONPointer([]); /** * Parse a JSON expression. * <P> * A segment that has only decimal digits will be interpreted as an index unless it has leading zeros * (RFC 6901, section4), or its value is larger than Integer.MAX_VALUE * * @param expression * @returns {JSONPointer} */ JSONPointer.parse = function (expression) { if (expression instanceof JSONPointer) { return expression; } var result = JSONPointer.ROOT; var segmentParser = new Parser(expression); for (;;) { var s = segmentParser.next(); if (s === null) { return result; } result = result.withSegment(s); } }; module.exports = JSONPointer; function escape(key) { for (var i = 0; i < key.length; ++i) { var c = key.charAt(i); if (c === '/' || c === '~') { var length = key.length; var parts = []; parts.push(key.substring(0, i)); for (var j = i; j < length; ++j) { var c1 = key.charAt(j); if (c1 === '/') { parts.push('~1'); } else if (c1 === '~') { parts.push('~0'); } else { parts.push(c1); } } return parts.join(''); } } return key; } var INDEX0 = new IndexSegment(0); /** * JSONPointer segment representing an index * * @param index * @constructor */ function IndexSegment(index) { this.index = index; } IndexSegment.prototype.equals = function (other) { if (other === this) { return true; } if (!other || !(other instanceof IndexSegment)) { return false; } return other.index === this.index; }; IndexSegment.prototype.toString = function () { return '/' + this.index; }; /** * JSONPointer segment representing a key * @param key * @constructor */ function KeySegment(key) { this.expression = '/' + escape(key); } KeySegment.prototype.equals = function (other) { if (other === this) { return true; } if (!other || !(other instanceof KeySegment)) { return false; } return other.expression === this.expression; }; KeySegment.prototype.toString = function () { return this.expression; }; function Parser(expression) { // Each iteration leaves p at the next separator var p = 0; if (expression !== "" && expression.charAt(0) !== '/') { throw new Error("JSON Pointer expression must be empty or start with '/' : " + expression); } function nextSegmentString() { var start = p; for (; p < expression.length; ++p) { var c = expression[p]; if (c === '/') { break; } else if (c === '~') { var parts = [expression.substring(start, p)]; var length = expression.length; for (; p < length; ++p) { var c1 = expression.charAt(p); if (c1 === '/') { break; } else if (c1 === '~' && p !== length - 1) { ++p; var c2 = expression.charAt(p); if (c2 === '0') { parts.push('~'); } else if (c2 === '1') { parts.push('/'); } else { parts.push('~'); parts.push(c2); } } else { parts.push(c1); } } return parts.join(''); } } return expression.substring(start, p); } function maybeIndex(s) { var digits = s.length; if (digits === 0 || digits > 10) { return null; } for (var i = 0; i < digits; ++i) { var c = s.charAt(i); if (c < '0' || c > '9') { return null; } if (c === '0' && i === 0) { // No leading zeroes allowed return digits === 1 ? INDEX0 : null; } } var index = parseInt(s, 10); // Cap at max int32 if (index > 2147483647) { return null; } return new IndexSegment(index); } this.next = function () { if (p === expression.length) { return null; } ++p; var s = nextSegmentString(); var is = maybeIndex(s); if (is !== null) { return is; } return new KeySegment(s); }; }