mirror of https://github.com/jkjoy/sunpeiwen.git
203 lines
4.5 KiB
JavaScript
203 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const through2 = require('through2');
|
|
const Parser = require('jsonparse');
|
|
|
|
/**
|
|
* Check whether a x and y are equal, or x matches y, or x(y) is truthy.
|
|
* @param {boolean | string | RegExp | (args: any[]) => boolean} x
|
|
* @param {*} y
|
|
* @returns {boolean}
|
|
*/
|
|
const check = (x, y) => {
|
|
if (typeof x === 'string') {
|
|
return y === x;
|
|
}
|
|
|
|
if (x && typeof x.exec === 'function') {
|
|
return x.exec(y);
|
|
}
|
|
|
|
if (typeof x === 'boolean' || typeof x === 'object') {
|
|
return x;
|
|
}
|
|
|
|
if (typeof x === 'function') {
|
|
return x(y);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
module.exports.parse = function(path, map) {
|
|
let header, footer;
|
|
|
|
const parser = new Parser();
|
|
const stream = through2.obj(
|
|
(chunk, enc, cb) => {
|
|
if (typeof chunk === 'string') {
|
|
chunk = Buffer.from(chunk);
|
|
}
|
|
parser.write(chunk);
|
|
cb();
|
|
},
|
|
cb => {
|
|
if (header) {
|
|
stream.emit('header', header);
|
|
}
|
|
if (footer) {
|
|
stream.emit('footer', footer);
|
|
}
|
|
|
|
if (parser.tState !== Parser.C.START || parser.stack.length > 0) {
|
|
cb(new Error('Incomplete JSON'));
|
|
return;
|
|
}
|
|
|
|
cb();
|
|
}
|
|
);
|
|
|
|
if (typeof path === 'string') {
|
|
path = path.split('.').map(e => {
|
|
if (e === '$*') {
|
|
return { emitKey: true };
|
|
}
|
|
if (e === '*') {
|
|
return true;
|
|
}
|
|
if (e === '') {
|
|
// '..'.split('.') returns an empty string
|
|
return { recurse: true };
|
|
}
|
|
return e;
|
|
});
|
|
}
|
|
|
|
|
|
if (!path || !path.length) {
|
|
path = null;
|
|
}
|
|
|
|
parser.onValue = function(value) {
|
|
if (!this.root) { stream.root = value; }
|
|
|
|
if (!path) return;
|
|
|
|
let i = 0; // iterates on path
|
|
let j = 0; // iterates on stack
|
|
let emitKey = false;
|
|
let emitPath = false;
|
|
while (i < path.length) {
|
|
const key = path[i];
|
|
let c;
|
|
j++;
|
|
|
|
if (key && !key.recurse) {
|
|
c = j === this.stack.length ? this : this.stack[j];
|
|
if (!c) return;
|
|
if (!check(key, c.key)) {
|
|
setHeaderFooter(c.key, value);
|
|
return;
|
|
}
|
|
emitKey = !!key.emitKey;
|
|
emitPath = !!key.emitPath;
|
|
i++;
|
|
} else {
|
|
i++;
|
|
const nextKey = path[i];
|
|
if (!nextKey) return;
|
|
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
c = j === this.stack.length ? this : this.stack[j];
|
|
if (!c) return;
|
|
if (check(nextKey, c.key)) {
|
|
i++;
|
|
if (!Object.isFrozen(this.stack[j])) {
|
|
this.stack[j].value = null;
|
|
}
|
|
break;
|
|
} else {
|
|
setHeaderFooter(c.key, value);
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// emit header
|
|
if (header) {
|
|
stream.emit('header', header);
|
|
header = false;
|
|
}
|
|
if (j !== this.stack.length) return;
|
|
|
|
const actualPath = this.stack.slice(1).map(element => element.key);
|
|
actualPath.push(this.key);
|
|
|
|
let data = this.value[this.key];
|
|
if (data != null) {
|
|
if ((data = map ? map(data, actualPath) : data) != null) {
|
|
if (emitKey || emitPath) {
|
|
data = {
|
|
value: data
|
|
};
|
|
if (emitKey) {
|
|
data.key = this.key;
|
|
}
|
|
if (emitPath) {
|
|
data.path = actualPath;
|
|
}
|
|
}
|
|
|
|
stream.push(data);
|
|
}
|
|
}
|
|
|
|
delete this.value[this.key];
|
|
|
|
for (const k in this.stack) {
|
|
if (!Object.isFrozen(this.stack[k])) {
|
|
this.stack[k].value = null;
|
|
}
|
|
}
|
|
};
|
|
parser._onToken = parser.onToken;
|
|
|
|
parser.onToken = function(token, value) {
|
|
parser._onToken(token, value);
|
|
if (this.stack.length === 0) {
|
|
if (stream.root) {
|
|
if (!path) { stream.push(stream.root); }
|
|
|
|
stream.root = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
parser.onError = function(err) {
|
|
if (err.message.includes('at position')) {
|
|
err.message = 'Invalid JSON (' + err.message + ')';
|
|
}
|
|
stream.destroy(err);
|
|
};
|
|
|
|
return stream;
|
|
|
|
function setHeaderFooter(key, value) {
|
|
// header has not been emitted yet
|
|
if (header !== false) {
|
|
header = header || {};
|
|
header[key] = value;
|
|
}
|
|
|
|
// footer has not been emitted yet but header has
|
|
if (footer !== false && header === false) {
|
|
footer = footer || {};
|
|
footer[key] = value;
|
|
}
|
|
}
|
|
};
|