diffusion
Version:
Diffusion JavaScript client
339 lines (272 loc) • 8.04 kB
JavaScript
/*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);
};
}