osmosis
Version:
Web scraper for NodeJS
340 lines (266 loc) • 6.79 kB
JavaScript
/*jslint node: true */
'use strict';
/**
* @constructor Data
* @param {object} [data] - Data object value
* @param {object} [parent] - Parent Data object
* @param {number} [index] - Index in the parent object
* @param {number} [sortIndex] - Sort order of object if coerced into array
* @param {bool} [isArray] - Is the object an array?
* @property {number} refs - Number of references
* @property {number} clones - Number of clones
* @property {object} object - Key/value data storage
* @property {Data} parent - Parent Data object
* @property {string} index - Key to set in the parent object
* @private
*/
function Data(parent) {
this.stack = { count: 0 };
if (parent) {
this.parent = parent;
}
return this;
}
/**
* Create an empty child Data object for the parent.
*
*/
Data.prototype.child = function () {
return new Data(this);
};
/**
* Clone a Data object.
*
*/
Data.prototype.clone = function () {
var clone = this.next();
clone.object = this.copy();
return clone;
};
/**
* Call callback when `Data.stack.count` === 0.
*/
Data.prototype.done = function (cb) {
this.stack.done = cb;
return this;
};
/**
* Get the raw data object.
*
*/
Data.prototype.getObject = function () {
if (this.object === undefined) {
if (this.isArray() === true) {
this.toArray();
} else {
this.setObject({});
}
}
return this.object;
};
/**
* Set the raw data object.
*
*/
Data.prototype.setObject = function (object) {
this.object = object;
return this;
};
/**
* Create a new Data object to pass to the next Command.
*
*/
Data.prototype.next = function () {
var clone = new Data(this.parent)
.setSortIndex(this.getSortIndex())
.setIndex(this.getIndex())
.isArray(this.isArray());
clone.stack = this.stack;
clone.object = this.object;
return clone;
};
/**
* Increase the reference count on all ancestors.
*
*/
Data.prototype.ref = function () {
this.stack.count++;
return this;
};
/**
* Decrease the reference count on all ancestors.
*
*/
Data.prototype.unref = function () {
if (--this.stack.count === 0) {
if (this.stack.done !== undefined) {
this.stack.done.call(this);
}
}
};
/**
* Set a key/value in {@link Data.object}.
*
* @param {string|object} key - A key or { key: val } object
* @param {any} val - A value
*/
Data.prototype.set = function (key, val) {
var object, currentVal, sortKey;
if (val === undefined) {
return this;
}
if (this.isArray() === true) {
return this.push(val);
}
object = this.getObject();
currentVal = object[key];
if (currentVal !== undefined) {
// If the key being set already has a value,
// then convert it to an Array.
if (currentVal instanceof Array) {
currentVal.push(val);
} else {
object[key] = [currentVal, val];
}
} else {
object[key] = val;
}
return this;
};
/**
* Push a value onto {@link Data.object} array.
*/
Data.prototype.push = function (val) {
var object = this.toArray();
if (val === undefined) {
return this;
}
object.push(val);
return this;
};
Data.prototype.copy = function () {
var obj = this.object,
data, i, keys, key;
if (this.isArray()) {
data = obj.slice(0);
} else if (obj instanceof Object) {
data = {};
for (i = 0, keys = Object.keys(obj); i < keys.length; i++) {
key = keys[i];
data[key] = obj[key];
}
} else {
data = obj;
}
return data;
};
Data.prototype.isArray = function (val) {
if (val !== undefined) {
this._isArray = val === true;
return this;
}
return (this._isArray === true || this.object instanceof Array);
};
Data.prototype.isEmpty = function () {
return (this.object === undefined ||
(this.object instanceof Object &&
Object.keys(this.object).length === 0)
);
};
Data.prototype.getIndex = function () {
return this._index;
};
Data.prototype.setIndex = function (index) {
if (this.isArray() !== true) {
this._index = index;
}
return this;
};
Data.prototype.setSortIndex = function (index) {
if (index !== undefined) {
this.sortIndex = index;
}
return this;
};
Data.prototype.getSortIndex = function () {
return this.sortIndex;
}
Data.prototype.sortKey = function (key, sortIndex) {
var object = this.getObject(),
currentVal = object[key],
sortArray;
if (!this.sortArray) {
this.sortArray = {};
}
sortArray = this.sortArray[key];
if (sortArray === undefined) {
if (currentVal instanceof Array && currentVal.length > 0) {
sortArray = new Array(currentVal.length);
} else {
sortArray = [sortIndex];
}
this.sortArray[key] = sortArray;
}
if (currentVal instanceof Array) {
var diff = currentVal.length - sortArray.length;
while (diff > 0) {
sortArray.push(sortIndex + (--diff));
}
object[key] = sortArray.map(function (v, i) {
return {
value: v,
index: i
};
}).sort(function (a, b) {
return a.value - b.value;
}).map(function (v, i) {
sortArray[i] = v.value;
return currentVal[v.index];
});
}
}
Data.prototype.merge = function (child) {
var object = child.object,
index = child.getIndex(),
sortIndex = child.getSortIndex();
if (object === undefined) {
return;
}
if (this.isArray() === true) {
this.push(object);
} else if (index !== undefined) {
this.set(index, object);
} else if (object instanceof Object) {
this.extend(object);
}
if (sortIndex !== undefined) {
this.sortKey(index, sortIndex);
}
};
Data.prototype.toArray = function () {
var object = this.object;
if (object instanceof Array) {
return object;
}
if (this.isEmpty()) {
this.setObject([]);
} else {
this.setObject([ object ]);
}
return this.getObject();
};
Data.prototype.extend = function (object) {
var key, keys = Object.keys(object),
isArray = this.isArray(),
i = keys.length;
while (i--) {
key = keys[i];
if (isArray) {
this.push(object[key]);
} else {
this.set(key, object[key]);
}
}
return object;
};
module.exports = Data;