123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- 'use strict';
- var escapedChars = {
- 'b': '\b',
- 'f': '\f',
- 'n': '\n',
- 'r': '\r',
- 't': '\t',
- '"': '"',
- '/': '/',
- '\\': '\\'
- };
- var A_CODE = 'a'.charCodeAt();
- function parse(source) {
- var pointers = {};
- var line = 0;
- var column = 0;
- var pos = 0;
- return {
- data: _parse('', true),
- pointers: pointers
- };
- function _parse(ptr, topLevel) {
- whitespace();
- var data;
- map(ptr, 'value');
- var char = getChar();
- switch (char) {
- case 't':
- read('rue');
- data = true;
- break;
- case 'f':
- read('alse');
- data = false;
- break;
- case 'n':
- read('ull');
- data = null;
- break;
- case '"':
- data = parseString();
- break;
- case '[':
- data = parseArray(ptr);
- break;
- case '{':
- data = parseObject(ptr);
- break;
- default:
- backChar();
- if ('-0123456789'.indexOf(char) >= 0)
- data = parseNumber();
- else
- unexpectedToken();
- }
- map(ptr, 'valueEnd');
- whitespace();
- if (topLevel && pos < source.length) unexpectedToken();
- return data;
- }
- function whitespace() {
- loop: while (pos < source.length) {
- switch (source[pos]) {
- case ' ':
- column++;
- break;
- case '\t':
- column += 4;
- break;
- case '\r':
- column = 0;
- break;
- case '\n':
- column = 0;
- line++;
- break;
- default:
- break loop;
- }
- pos++;
- }
- }
- function parseString() {
- var str = '';
- var char;
- while (true) {
- char = getChar();
- if (char == '"') {
- break;
- } else if (char == '\\') {
- char = getChar();
- if (char in escapedChars)
- str += escapedChars[char];
- else if (char == 'u')
- str += getCharCode();
- else
- wasUnexpectedToken();
- } else {
- str += char;
- }
- }
- return str;
- }
- function parseNumber() {
- var numStr = '';
- if (source[pos] == '-') numStr += getChar();
- numStr += source[pos] == '0' ? getChar() : getDigits();
- if (source[pos] == '.')
- numStr += getChar() + getDigits();
- if (source[pos] == 'e' || source[pos] == 'E') {
- numStr += getChar();
- if (source[pos] == '+' || source[pos] == '-') numStr += getChar();
- numStr += getDigits();
- }
- return +numStr;
- }
- function parseArray(ptr) {
- whitespace();
- var arr = [];
- var i = 0;
- if (getChar() == ']') return arr;
- backChar();
- while (true) {
- var itemPtr = ptr + '/' + i;
- arr.push(_parse(itemPtr));
- whitespace();
- var char = getChar();
- if (char == ']') break;
- if (char != ',') wasUnexpectedToken();
- whitespace();
- i++;
- }
- return arr;
- }
- function parseObject(ptr) {
- whitespace();
- var obj = {};
- if (getChar() == '}') return obj;
- backChar();
- while (true) {
- var loc = getLoc();
- if (getChar() != '"') wasUnexpectedToken();
- var key = parseString();
- var propPtr = ptr + '/' + escapeJsonPointer(key);
- mapLoc(propPtr, 'key', loc);
- map(propPtr, 'keyEnd');
- whitespace();
- if (getChar() != ':') wasUnexpectedToken();
- whitespace();
- obj[key] = _parse(propPtr);
- whitespace();
- var char = getChar();
- if (char == '}') break;
- if (char != ',') wasUnexpectedToken();
- whitespace();
- }
- return obj;
- }
- function read(str) {
- for (var i = 0; i < str.length; i++)
- if (getChar() !== str[i]) wasUnexpectedToken();
- }
- function getChar() {
- checkUnexpectedEnd();
- var char = source[pos];
- pos++;
- column++; // new line?
- return char;
- }
- function backChar() {
- pos--;
- column--;
- }
- function getCharCode() {
- var count = 4;
- var code = 0;
- while (count--) {
- code <<= 4;
- var char = getChar().toLowerCase();
- if (char >= 'a' && char <= 'f')
- code += char.charCodeAt() - A_CODE + 10;
- else if (char >= '0' && char <= '9')
- code += +char;
- else
- wasUnexpectedToken();
- }
- return String.fromCharCode(code);
- }
- function getDigits() {
- var digits = '';
- while (source[pos] >= '0' && source[pos] <= '9')
- digits += getChar();
- if (digits.length) return digits;
- checkUnexpectedEnd();
- unexpectedToken();
- }
- function map(ptr, prop) {
- mapLoc(ptr, prop, getLoc());
- }
- function mapLoc(ptr, prop, loc) {
- pointers[ptr] = pointers[ptr] || {};
- pointers[ptr][prop] = loc;
- }
- function getLoc() {
- return {
- line: line,
- column: column,
- pos: pos
- };
- }
- function unexpectedToken() {
- throw new SyntaxError('Unexpected token ' + source[pos] + ' in JSON at position ' + pos);
- }
- function wasUnexpectedToken() {
- backChar();
- unexpectedToken();
- }
- function checkUnexpectedEnd() {
- if (pos >= source.length)
- throw new SyntaxError('Unexpected end of JSON input');
- }
- };
- function stringify(data, _, whitespace) {
- if (!validType(data)) return;
- var wsLine = 0;
- var wsPos, wsColumn;
- switch (typeof whitespace) {
- case 'number':
- var len = whitespace > 10 ? 10 : whitespace < 0 ? 0 : Math.floor(whitespace);
- whitespace = len && repeat(len, ' ');
- wsPos = len;
- wsColumn = len;
- break;
- case 'string':
- whitespace = whitespace.slice(0, 10);
- wsPos = 0;
- wsColumn = 0;
- for (var j = 0; j < whitespace.length; j++) {
- var char = whitespace[j];
- switch (char) {
- case ' ':
- wsColumn++;
- break;
- case '\t':
- wsColumn += 4;
- break;
- case '\r':
- wsColumn = 0;
- break;
- case '\n':
- wsColumn = 0;
- wsLine++;
- break;
- default:
- throw new Error('whitespace characters not allowed in JSON');
- }
- wsPos++;
- }
- break;
- default:
- whitespace = undefined;
- }
- var json = '';
- var pointers = {};
- var line = 0;
- var column = 0;
- var pos = 0;
- _stringify(data, 0, '');
- return {
- json: json,
- pointers: pointers
- };
- function _stringify(_data, lvl, ptr) {
- map(ptr, 'value');
- switch (typeof _data) {
- case 'number':
- case 'boolean':
- out('' + _data);
- break;
- case 'string':
- out(quoted(_data));
- break;
- case 'object':
- if (_data === null)
- out('null');
- else if (typeof _data.toJSON == 'function')
- out(quoted(_data.toJSON()));
- else if (Array.isArray(_data))
- stringifyArray();
- else
- stringifyObject();
- }
- map(ptr, 'valueEnd');
- function stringifyArray() {
- if (_data.length) {
- out('[');
- var itemLvl = lvl + 1;
- for (var i = 0; i < _data.length; i++) {
- if (i) out(',');
- indent(itemLvl);
- var item = validType(_data[i]) ? _data[i] : null;
- var itemPtr = ptr + '/' + i;
- _stringify(item, itemLvl, itemPtr);
- }
- indent(lvl);
- out(']');
- } else {
- out('[]');
- }
- }
- function stringifyObject() {
- var keys = Object.keys(_data);
- if (keys.length) {
- out('{');
- var propLvl = lvl + 1;
- for (var i = 0; i < keys.length; i++) {
- var key = keys[i];
- var value = _data[key];
- if (validType(value)) {
- if (i) out(',');
- var propPtr = ptr + '/' + escapeJsonPointer(key);
- indent(propLvl);
- map(propPtr, 'key');
- out(quoted(key));
- map(propPtr, 'keyEnd');
- out(':');
- if (whitespace) out(' ');
- _stringify(value, propLvl, propPtr);
- }
- }
- indent(lvl);
- out('}');
- } else {
- out('{}');
- }
- }
- }
- function out(str) {
- column += str.length;
- pos += str.length;
- json += str;
- }
- function indent(lvl) {
- if (whitespace) {
- json += '\n' + repeat(lvl, whitespace);
- line++;
- column = 0;
- while (lvl--) {
- if (wsLine) {
- line += wsLine;
- column = wsColumn;
- } else {
- column += wsColumn;
- }
- pos += wsPos;
- }
- pos += 1; // \n character
- }
- }
- function map(ptr, prop) {
- pointers[ptr] = pointers[ptr] || {};
- pointers[ptr][prop] = {
- line: line,
- column: column,
- pos: pos
- };
- }
- function repeat(n, str) {
- return Array(n + 1).join(str);
- }
- };
- var VALID_TYPES = ['number', 'boolean', 'string', 'object'];
- function validType(data) {
- return VALID_TYPES.indexOf(typeof data) >= 0;
- }
- var ESC_QUOTE = /"|\\/g;
- var ESC_B = /[\b]/g;
- var ESC_F = /\f/g;
- var ESC_N = /\n/g;
- var ESC_R = /\r/g;
- var ESC_T = /\t/g;
- function quoted(str) {
- str = str.replace(ESC_QUOTE, '\\$&')
- .replace(ESC_F, '\\f')
- .replace(ESC_B, '\\b')
- .replace(ESC_N, '\\n')
- .replace(ESC_R, '\\r')
- .replace(ESC_T, '\\t');
- return '"' + str + '"';
- }
- var ESC_0 = /~/g;
- var ESC_1 = /\//g;
- function escapeJsonPointer(str) {
- return str.replace(ESC_0, '~0')
- .replace(ESC_1, '~1');
- }
|