| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- 'use strict';
- const {Transform} = require('stream');
- const noCommaAfter = {startObject: 1, startArray: 1, endKey: 1, keyValue: 1},
- noSpaceAfter = {endObject: 1, endArray: 1, '': 1},
- noSpaceBefore = {startObject: 1, startArray: 1},
- depthIncrement = {startObject: 1, startArray: 1},
- depthDecrement = {endObject: 1, endArray: 1},
- values = {startKey: 'keyValue', startString: 'stringValue', startNumber: 'numberValue'},
- stopNames = {startKey: 'endKey', startString: 'endString', startNumber: 'endNumber'},
- symbols = {
- startObject: '{',
- endObject: '}',
- startArray: '[',
- endArray: ']',
- startKey: '"',
- endKey: '":',
- startString: '"',
- endString: '"',
- startNumber: '',
- endNumber: '',
- nullValue: 'null',
- trueValue: 'true',
- falseValue: 'false'
- };
- const skipValue = endName =>
- function (chunk, encoding, callback) {
- if (chunk.name === endName) {
- this._transform = this._prev_transform;
- }
- callback(null);
- };
- const replaceSymbols = {'\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', '"': '\\"', '\\': '\\\\'};
- const sanitizeString = value =>
- value.replace(/[\b\f\n\r\t\"\\\u0000-\u001F\u007F-\u009F]/g, match =>
- replaceSymbols.hasOwnProperty(match) ? replaceSymbols[match] : '\\u' + ('0000' + match.charCodeAt(0).toString(16)).slice(-4)
- );
- const doNothing = () => {};
- class Stringer extends Transform {
- static make(options) {
- return new Stringer(options);
- }
- constructor(options) {
- super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: false}));
- this._values = {};
- if (options) {
- 'useValues' in options && (this._values.keyValue = this._values.stringValue = this._values.numberValue = options.useValues);
- 'useKeyValues' in options && (this._values.keyValue = options.useKeyValues);
- 'useStringValues' in options && (this._values.stringValue = options.useStringValues);
- 'useNumberValues' in options && (this._values.numberValue = options.useNumberValues);
- this._makeArray = options.makeArray;
- }
- this._prev = '';
- this._depth = 0;
- if (this._makeArray) {
- this._transform = this._arrayTransform;
- this._flush = this._arrayFlush;
- }
- }
- _arrayTransform(chunk, encoding, callback) {
- // it runs once
- delete this._transform;
- this._transform({name: 'startArray'}, encoding, doNothing);
- this._transform(chunk, encoding, callback);
- }
- _arrayFlush(callback) {
- if (this._transform === this._arrayTransform) {
- delete this._transform;
- this._transform({name: 'startArray'}, null, doNothing);
- }
- this._transform({name: 'endArray'}, null, callback);
- }
- _transform(chunk, _, callback) {
- if (this._values[chunk.name]) {
- if (this._depth && noCommaAfter[this._prev] !== 1) this.push(',');
- switch (chunk.name) {
- case 'keyValue':
- this.push('"' + sanitizeString(chunk.value) + '":');
- break;
- case 'stringValue':
- this.push('"' + sanitizeString(chunk.value) + '"');
- break;
- case 'numberValue':
- this.push(chunk.value);
- break;
- }
- } else {
- // filter out values
- switch (chunk.name) {
- case 'endObject':
- case 'endArray':
- case 'endKey':
- case 'endString':
- case 'endNumber':
- this.push(symbols[chunk.name]);
- break;
- case 'stringChunk':
- this.push(sanitizeString(chunk.value));
- break;
- case 'numberChunk':
- this.push(chunk.value);
- break;
- case 'keyValue':
- case 'stringValue':
- case 'numberValue':
- // skip completely
- break;
- case 'startKey':
- case 'startString':
- case 'startNumber':
- if (this._values[values[chunk.name]]) {
- this._prev_transform = this._transform;
- this._transform = skipValue(stopNames[chunk.name]);
- return callback(null);
- }
- // intentional fall down
- default:
- // case 'startObject': case 'startArray': case 'startKey': case 'startString':
- // case 'startNumber': case 'nullValue': case 'trueValue': case 'falseValue':
- if (this._depth) {
- if (noCommaAfter[this._prev] !== 1) this.push(',');
- } else {
- if (noSpaceAfter[this._prev] !== 1 && noSpaceBefore[chunk.name] !== 1) this.push(' ');
- }
- this.push(symbols[chunk.name]);
- break;
- }
- if (depthIncrement[chunk.name]) {
- ++this._depth;
- } else if (depthDecrement[chunk.name]) {
- --this._depth;
- }
- }
- this._prev = chunk.name;
- callback(null);
- }
- }
- Stringer.stringer = Stringer.make;
- Stringer.make.Constructor = Stringer;
- module.exports = Stringer;
|