202 lines
5.5 KiB
Plaintext
202 lines
5.5 KiB
Plaintext
'use strict';
|
|
|
|
const {Transform} = require('stream');
|
|
|
|
class FilterBase extends Transform {
|
|
static stringFilter(string, separator) {
|
|
return stack => {
|
|
const path = stack.join(separator);
|
|
return (
|
|
(path.length === string.length && path === string) ||
|
|
(path.length > string.length && path.substr(0, string.length) === string && path.substr(string.length, separator.length) === separator)
|
|
);
|
|
};
|
|
}
|
|
|
|
static regExpFilter(regExp, separator) {
|
|
return stack => regExp.test(stack.join(separator));
|
|
}
|
|
|
|
static arrayReplacement(array) {
|
|
return () => array;
|
|
}
|
|
|
|
constructor(options) {
|
|
super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
|
|
this._transform = this._check;
|
|
this._stack = [];
|
|
|
|
const filter = options && options.filter,
|
|
separator = (options && options.pathSeparator) || '.';
|
|
if (typeof filter == 'string') {
|
|
this._filter = FilterBase.stringFilter(filter, separator);
|
|
} else if (typeof filter == 'function') {
|
|
this._filter = filter;
|
|
} else if (filter instanceof RegExp) {
|
|
this._filter = FilterBase.regExpFilter(filter, separator);
|
|
}
|
|
|
|
const replacement = options && options.replacement;
|
|
if (typeof replacement == 'function') {
|
|
this._replacement = replacement;
|
|
} else {
|
|
this._replacement = FilterBase.arrayReplacement(replacement || FilterBase.defaultReplacement);
|
|
}
|
|
this._allowEmptyReplacement = options && options.allowEmptyReplacement;
|
|
|
|
this._streamKeys = true;
|
|
if (options) {
|
|
'streamValues' in options && (this._streamKeys = options.streamValues);
|
|
'streamKeys' in options && (this._streamKeys = options.streamKeys);
|
|
}
|
|
|
|
this._once = options && options.once;
|
|
this._previousToken = '';
|
|
}
|
|
|
|
_check(chunk, _, callback) {
|
|
// update the last stack key
|
|
switch (chunk.name) {
|
|
case 'startObject':
|
|
case 'startArray':
|
|
case 'startString':
|
|
case 'startNumber':
|
|
case 'nullValue':
|
|
case 'trueValue':
|
|
case 'falseValue':
|
|
if (typeof this._stack[this._stack.length - 1] == 'number') {
|
|
// array
|
|
++this._stack[this._stack.length - 1];
|
|
}
|
|
break;
|
|
case 'keyValue':
|
|
this._stack[this._stack.length - 1] = chunk.value;
|
|
break;
|
|
case 'numberValue':
|
|
if (this._previousToken !== 'endNumber' && typeof this._stack[this._stack.length - 1] == 'number') {
|
|
// array
|
|
++this._stack[this._stack.length - 1];
|
|
}
|
|
break;
|
|
case 'stringValue':
|
|
if (this._previousToken !== 'endString' && typeof this._stack[this._stack.length - 1] == 'number') {
|
|
// array
|
|
++this._stack[this._stack.length - 1];
|
|
}
|
|
break;
|
|
}
|
|
this._previousToken = chunk.name;
|
|
// check, if we allow a chunk
|
|
if (this._checkChunk(chunk)) {
|
|
return callback(null);
|
|
}
|
|
// update the stack
|
|
switch (chunk.name) {
|
|
case 'startObject':
|
|
this._stack.push(null);
|
|
break;
|
|
case 'startArray':
|
|
this._stack.push(-1);
|
|
break;
|
|
case 'endObject':
|
|
case 'endArray':
|
|
this._stack.pop();
|
|
break;
|
|
}
|
|
callback(null);
|
|
}
|
|
|
|
_passObject(chunk, _, callback) {
|
|
this.push(chunk);
|
|
switch (chunk.name) {
|
|
case 'startObject':
|
|
case 'startArray':
|
|
++this._depth;
|
|
break;
|
|
case 'endObject':
|
|
case 'endArray':
|
|
--this._depth;
|
|
break;
|
|
}
|
|
if (!this._depth) {
|
|
this._transform = this._once ? this._skip : this._check;
|
|
}
|
|
callback(null);
|
|
}
|
|
|
|
_pass(chunk, _, callback) {
|
|
this.push(chunk);
|
|
callback(null);
|
|
}
|
|
|
|
_skipObject(chunk, _, callback) {
|
|
switch (chunk.name) {
|
|
case 'startObject':
|
|
case 'startArray':
|
|
++this._depth;
|
|
break;
|
|
case 'endObject':
|
|
case 'endArray':
|
|
--this._depth;
|
|
break;
|
|
}
|
|
if (!this._depth) {
|
|
this._transform = this._once ? this._pass : this._check;
|
|
}
|
|
callback(null);
|
|
}
|
|
|
|
_skip(chunk, _, callback) {
|
|
callback(null);
|
|
}
|
|
}
|
|
|
|
FilterBase.defaultReplacement = [{name: 'nullValue', value: null}];
|
|
|
|
const passValue = (last, post) =>
|
|
function(chunk, _, callback) {
|
|
if (this._expected) {
|
|
const expected = this._expected;
|
|
this._expected = '';
|
|
this._transform = this._once ? this._skip : this._check;
|
|
if (expected === chunk.name) {
|
|
this.push(chunk);
|
|
} else {
|
|
return this._transform(chunk, _, callback);
|
|
}
|
|
} else {
|
|
this.push(chunk);
|
|
if (chunk.name === last) {
|
|
this._expected = post;
|
|
}
|
|
}
|
|
callback(null);
|
|
};
|
|
|
|
FilterBase.prototype._passNumber = passValue('endNumber', 'numberValue');
|
|
FilterBase.prototype._passString = passValue('endString', 'stringValue');
|
|
FilterBase.prototype._passKey = passValue('endKey', 'keyValue');
|
|
|
|
const skipValue = (last, post) =>
|
|
function(chunk, _, callback) {
|
|
if (this._expected) {
|
|
const expected = this._expected;
|
|
this._expected = '';
|
|
this._transform = this._once ? this._pass : this._check;
|
|
if (expected !== chunk.name) {
|
|
return this._transform(chunk, _, callback);
|
|
}
|
|
} else {
|
|
if (chunk.name === last) {
|
|
this._expected = post;
|
|
}
|
|
}
|
|
callback(null);
|
|
};
|
|
|
|
FilterBase.prototype._skipNumber = skipValue('endNumber', 'numberValue');
|
|
FilterBase.prototype._skipString = skipValue('endString', 'stringValue');
|
|
FilterBase.prototype._skipKey = skipValue('endKey', 'keyValue');
|
|
|
|
module.exports = FilterBase;
|