Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
import Singleton from '../abstracts/Singleton';
|
|
|
|
/**
|
|
* JSON Parser utility.
|
|
*
|
|
* This utility parses JSON-like data that does not strictly meet the JSON specifications in order to simplify development.
|
|
* It is a safe replacement for JSON.parse(JSON.stringify(eval("({" + value + "})"))) that does not require the use of eval()
|
|
*
|
|
* @author Ayumi Hamasaki
|
|
* @author Ben Thomson <git@alfreido.com>
|
|
* @see https://github.com/octobercms/october/pull/4527
|
|
*/
|
|
export default class JsonParser extends Singleton {
|
|
construct() {
|
|
// Add to global function for backwards compatibility
|
|
window.wnJSON = (json) => this.parse(json);
|
|
window.ocJSON = window.wnJSON;
|
|
}
|
|
|
|
parse(str) {
|
|
const jsonString = this.parseString(str);
|
|
return JSON.parse(jsonString);
|
|
}
|
|
|
|
parseString(value) {
|
|
let str = value.trim();
|
|
|
|
if (!str.length) {
|
|
throw new Error('Broken JSON object.');
|
|
}
|
|
|
|
let result = '';
|
|
let type = null;
|
|
let key = null;
|
|
let body = '';
|
|
|
|
/*
|
|
* the mistake ','
|
|
*/
|
|
while (str && str[0] === ',') {
|
|
str = str.substr(1);
|
|
}
|
|
|
|
/*
|
|
* string
|
|
*/
|
|
if (str[0] === '"' || str[0] === '\'') {
|
|
if (str[str.length - 1] !== str[0]) {
|
|
throw new Error('Invalid string JSON object.');
|
|
}
|
|
|
|
body = '"';
|
|
for (let i = 1; i < str.length; i += 1) {
|
|
if (str[i] === '\\') {
|
|
if (str[i + 1] === '\'') {
|
|
body += str[i + 1];
|
|
} else {
|
|
body += str[i];
|
|
body += str[i + 1];
|
|
}
|
|
i += 1;
|
|
} else if (str[i] === str[0]) {
|
|
body += '"';
|
|
return body;
|
|
} else if (str[i] === '"') {
|
|
body += '\\"';
|
|
} else {
|
|
body += str[i];
|
|
}
|
|
}
|
|
|
|
throw new Error('Invalid string JSON object.');
|
|
}
|
|
|
|
/*
|
|
* boolean
|
|
*/
|
|
if (str === 'true' || str === 'false') {
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* null
|
|
*/
|
|
if (str === 'null') {
|
|
return 'null';
|
|
}
|
|
|
|
/*
|
|
* number
|
|
*/
|
|
const num = Number(str);
|
|
if (!Number.isNaN(num)) {
|
|
return num.toString();
|
|
}
|
|
|
|
/*
|
|
* object
|
|
*/
|
|
if (str[0] === '{') {
|
|
type = 'needKey';
|
|
key = null;
|
|
result = '{';
|
|
|
|
for (let i = 1; i < str.length; i += 1) {
|
|
if (this.isBlankChar(str[i])) {
|
|
/* eslint-disable-next-line */
|
|
continue;
|
|
}
|
|
if (type === 'needKey' && (str[i] === '"' || str[i] === '\'')) {
|
|
key = this.parseKey(str, i + 1, str[i]);
|
|
result += `"${key}"`;
|
|
i += key.length;
|
|
i += 1;
|
|
type = 'afterKey';
|
|
} else if (type === 'needKey' && this.canBeKeyHead(str[i])) {
|
|
key = this.parseKey(str, i);
|
|
result += '"';
|
|
result += key;
|
|
result += '"';
|
|
i += key.length - 1;
|
|
type = 'afterKey';
|
|
} else if (type === 'afterKey' && str[i] === ':') {
|
|
result += ':';
|
|
type = ':';
|
|
} else if (type === ':') {
|
|
body = this.getBody(str, i);
|
|
|
|
i = i + body.originLength - 1;
|
|
result += this.parseString(body.body);
|
|
|
|
type = 'afterBody';
|
|
} else if (type === 'afterBody' || type === 'needKey') {
|
|
let last = i;
|
|
while (str[last] === ',' || this.isBlankChar(str[last])) {
|
|
last += 1;
|
|
}
|
|
if (str[last] === '}' && last === str.length - 1) {
|
|
while (result[result.length - 1] === ',') {
|
|
result = result.substr(0, result.length - 1);
|
|
}
|
|
result += '}';
|
|
return result;
|
|
}
|
|
if (last !== i && result !== '{') {
|
|
result += ',';
|
|
type = 'needKey';
|
|
i = last - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON object near ${result}`);
|
|
}
|
|
|
|
/*
|
|
* array
|
|
*/
|
|
if (str[0] === '[') {
|
|
result = '[';
|
|
type = 'needBody';
|
|
for (let i = 1; i < str.length; i += 1) {
|
|
if (str[i] === ' ' || str[i] === '\n' || str[i] === '\t') {
|
|
/* eslint-disable-next-line */
|
|
continue;
|
|
} else if (type === 'needBody') {
|
|
if (str[i] === ',') {
|
|
result += 'null,';
|
|
/* eslint-disable-next-line */
|
|
continue;
|
|
}
|
|
if (str[i] === ']' && i === str.length - 1) {
|
|
if (result[result.length - 1] === ',') {
|
|
result = result.substr(0, result.length - 1);
|
|
}
|
|
result += ']';
|
|
return result;
|
|
}
|
|
|
|
body = this.getBody(str, i);
|
|
|
|
i = i + body.originLength - 1;
|
|
result += this.parseString(body.body);
|
|
|
|
type = 'afterBody';
|
|
} else if (type === 'afterBody') {
|
|
if (str[i] === ',') {
|
|
result += ',';
|
|
type = 'needBody';
|
|
|
|
// deal with mistake ","
|
|
while (str[i + 1] === ',' || this.isBlankChar(str[i + 1])) {
|
|
if (str[i + 1] === ',') {
|
|
result += 'null,';
|
|
}
|
|
i += 1;
|
|
}
|
|
} else if (str[i] === ']' && i === str.length - 1) {
|
|
result += ']';
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON array near ${result}`);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
getBody(str, pos) {
|
|
let body = '';
|
|
|
|
// parse string body
|
|
if (str[pos] === '"' || str[pos] === '\'') {
|
|
body = str[pos];
|
|
|
|
for (let i = pos + 1; i < str.length; i += 1) {
|
|
if (str[i] === '\\') {
|
|
body += str[i];
|
|
if (i + 1 < str.length) {
|
|
body += str[i + 1];
|
|
}
|
|
i += 1;
|
|
} else if (str[i] === str[pos]) {
|
|
body += str[pos];
|
|
return {
|
|
originLength: body.length,
|
|
body,
|
|
};
|
|
} else {
|
|
body += str[i];
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON string body near ${body}`);
|
|
}
|
|
|
|
// parse true / false
|
|
if (str[pos] === 't') {
|
|
if (str.indexOf('true', pos) === pos) {
|
|
return {
|
|
originLength: 'true'.length,
|
|
body: 'true',
|
|
};
|
|
}
|
|
|
|
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
|
}
|
|
if (str[pos] === 'f') {
|
|
if (str.indexOf('f', pos) === pos) {
|
|
return {
|
|
originLength: 'false'.length,
|
|
body: 'false',
|
|
};
|
|
}
|
|
|
|
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
|
}
|
|
|
|
// parse null
|
|
if (str[pos] === 'n') {
|
|
if (str.indexOf('null', pos) === pos) {
|
|
return {
|
|
originLength: 'null'.length,
|
|
body: 'null',
|
|
};
|
|
}
|
|
|
|
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
|
}
|
|
|
|
// parse number
|
|
if (str[pos] === '-' || str[pos] === '+' || str[pos] === '.' || (str[pos] >= '0' && str[pos] <= '9')) {
|
|
body = '';
|
|
|
|
for (let i = pos; i < str.length; i += 1) {
|
|
if (str[i] === '-' || str[i] === '+' || str[i] === '.' || (str[i] >= '0' && str[i] <= '9')) {
|
|
body += str[i];
|
|
} else {
|
|
return {
|
|
originLength: body.length,
|
|
body,
|
|
};
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON number body near ${body}`);
|
|
}
|
|
|
|
// parse object
|
|
if (str[pos] === '{' || str[pos] === '[') {
|
|
const stack = [
|
|
str[pos],
|
|
];
|
|
body = str[pos];
|
|
|
|
for (let i = pos + 1; i < str.length; i += 1) {
|
|
body += str[i];
|
|
if (str[i] === '\\') {
|
|
if (i + 1 < str.length) {
|
|
body += str[i + 1];
|
|
}
|
|
i += 1;
|
|
} else if (str[i] === '"') {
|
|
if (stack[stack.length - 1] === '"') {
|
|
stack.pop();
|
|
} else if (stack[stack.length - 1] !== '\'') {
|
|
stack.push(str[i]);
|
|
}
|
|
} else if (str[i] === '\'') {
|
|
if (stack[stack.length - 1] === '\'') {
|
|
stack.pop();
|
|
} else if (stack[stack.length - 1] !== '"') {
|
|
stack.push(str[i]);
|
|
}
|
|
} else if (stack[stack.length - 1] !== '"' && stack[stack.length - 1] !== '\'') {
|
|
if (str[i] === '{') {
|
|
stack.push('{');
|
|
} else if (str[i] === '}') {
|
|
if (stack[stack.length - 1] === '{') {
|
|
stack.pop();
|
|
} else {
|
|
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
|
}
|
|
} else if (str[i] === '[') {
|
|
stack.push('[');
|
|
} else if (str[i] === ']') {
|
|
if (stack[stack.length - 1] === '[') {
|
|
stack.pop();
|
|
} else {
|
|
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
|
}
|
|
}
|
|
}
|
|
if (!stack.length) {
|
|
return {
|
|
originLength: i - pos,
|
|
body,
|
|
};
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
|
}
|
|
|
|
throw new Error(`Broken JSON body near ${str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50)}`);
|
|
}
|
|
|
|
parseKey(str, pos, quote) {
|
|
let key = '';
|
|
|
|
for (let i = pos; i < str.length; i += 1) {
|
|
if (quote && quote === str[i]) {
|
|
return key;
|
|
}
|
|
if (!quote && (str[i] === ' ' || str[i] === ':')) {
|
|
return key;
|
|
}
|
|
|
|
key += str[i];
|
|
|
|
if (str[i] === '\\' && i + 1 < str.length) {
|
|
key += str[i + 1];
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
throw new Error(`Broken JSON syntax near ${key}`);
|
|
}
|
|
|
|
canBeKeyHead(ch) {
|
|
if (ch[0] === '\\') {
|
|
return false;
|
|
}
|
|
if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') {
|
|
return true;
|
|
}
|
|
if (ch[0] >= '0' && ch[0] <= '9') {
|
|
return true;
|
|
}
|
|
if (ch[0] === '$') {
|
|
return true;
|
|
}
|
|
if (ch.charCodeAt(0) > 255) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
isBlankChar(ch) {
|
|
return ch === ' ' || ch === '\n' || ch === '\t';
|
|
}
|
|
}
|