var equal = require('deep-equal'); var extend = require('extend'); var lib = { attributes: { compose: function (a, b, keepNull) { if (typeof a !== 'object') a = {}; if (typeof b !== 'object') b = {}; var attributes = extend(true, {}, b); if (!keepNull) { attributes = Object.keys(attributes).reduce(function (copy, key) { if (attributes[key] != null) { copy[key] = attributes[key]; } return copy; }, {}); } for (var key in a) { if (a[key] !== undefined && b[key] === undefined) { attributes[key] = a[key]; } } return Object.keys(attributes).length > 0 ? attributes : undefined; }, diff: function(a, b) { if (typeof a !== 'object') a = {}; if (typeof b !== 'object') b = {}; var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) { if (!equal(a[key], b[key])) { attributes[key] = b[key] === undefined ? null : b[key]; } return attributes; }, {}); return Object.keys(attributes).length > 0 ? attributes : undefined; }, transform: function (a, b, priority) { if (typeof a !== 'object') return b; if (typeof b !== 'object') return undefined; if (!priority) return b; // b simply overwrites us without priority var attributes = Object.keys(b).reduce(function (attributes, key) { if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value return attributes; }, {}); return Object.keys(attributes).length > 0 ? attributes : undefined; } }, iterator: function (ops) { return new Iterator(ops); }, length: function (op) { if (typeof op['delete'] === 'number') { return op['delete']; } else if (typeof op.retain === 'number') { return op.retain; } else { return typeof op.insert === 'string' ? op.insert.length : 1; } } }; function Iterator(ops) { this.ops = ops; this.index = 0; this.offset = 0; }; Iterator.prototype.hasNext = function () { return this.peekLength() < Infinity; }; Iterator.prototype.next = function (length) { if (!length) length = Infinity; var nextOp = this.ops[this.index]; if (nextOp) { var offset = this.offset; var opLength = lib.length(nextOp) if (length >= opLength - offset) { length = opLength - offset; this.index += 1; this.offset = 0; } else { this.offset += length; } if (typeof nextOp['delete'] === 'number') { return { 'delete': length }; } else { var retOp = {}; if (nextOp.attributes) { retOp.attributes = nextOp.attributes; } if (typeof nextOp.retain === 'number') { retOp.retain = length; } else if (typeof nextOp.insert === 'string') { retOp.insert = nextOp.insert.substr(offset, length); } else { // offset should === 0, length should === 1 retOp.insert = nextOp.insert; } return retOp; } } else { return { retain: Infinity }; } }; Iterator.prototype.peek = function () { return this.ops[this.index]; }; Iterator.prototype.peekLength = function () { if (this.ops[this.index]) { // Should never return 0 if our index is being managed correctly return lib.length(this.ops[this.index]) - this.offset; } else { return Infinity; } }; Iterator.prototype.peekType = function () { if (this.ops[this.index]) { if (typeof this.ops[this.index]['delete'] === 'number') { return 'delete'; } else if (typeof this.ops[this.index].retain === 'number') { return 'retain'; } else { return 'insert'; } } return 'retain'; }; Iterator.prototype.rest = function () { if (!this.hasNext()) { return []; } else if (this.offset === 0) { return this.ops.slice(this.index); } else { var offset = this.offset; var index = this.index; var next = this.next(); var rest = this.ops.slice(this.index); this.offset = offset; this.index = index; return [next].concat(rest); } }; module.exports = lib;