Initial commit: n8n Strike API node
- Add Strike API credentials configuration - Implement Strike node with 9 resources (Account, Balance, Currency Exchange, Deposit, Invoice, Payment, Payment Method, Payout, Rates) - Add comprehensive operation descriptions for all resources - Include CLAUDE.MD documentation - Set up build configuration with TypeScript, ESLint, and Prettier 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
7
node_modules/jsonrepair/LICENSE.md
generated
vendored
Normal file
7
node_modules/jsonrepair/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2020-2025 by Jos de Jong
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
228
node_modules/jsonrepair/README.md
generated
vendored
Normal file
228
node_modules/jsonrepair/README.md
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
# jsonrepair
|
||||
|
||||
Repair invalid JSON documents.
|
||||
|
||||
Try it out in a minimal demo: https://josdejong.github.io/jsonrepair/
|
||||
|
||||
Use it in a full-fledged application: https://jsoneditoronline.org
|
||||
|
||||
Read the background article ["How to fix JSON and validate it with ease"](https://jsoneditoronline.org/indepth/parse/fix-json/)
|
||||
|
||||
The following issues can be fixed:
|
||||
|
||||
- Add missing quotes around keys
|
||||
- Add missing escape characters
|
||||
- Add missing commas
|
||||
- Add missing closing brackets
|
||||
- Repair truncated JSON
|
||||
- Replace single quotes with double quotes
|
||||
- Replace special quote characters like `“...”` with regular double quotes
|
||||
- Replace special white space characters with regular spaces
|
||||
- Replace Python constants `None`, `True`, and `False` with `null`, `true`, and `false`
|
||||
- Strip trailing commas
|
||||
- Strip comments like `/* ... */` and `// ...`
|
||||
- Strip fenced code blocks like `` ```json`` and `` ``` ``
|
||||
- Strip ellipsis in arrays and objects like `[1, 2, 3, ...]`
|
||||
- Strip JSONP notation like `callback({ ... })`
|
||||
- Strip escape characters from an escaped string like `{\"stringified\": \"content\"}`
|
||||
- Strip MongoDB data types like `NumberLong(2)` and `ISODate("2012-12-19T06:01:17.171Z")`
|
||||
- Concatenate strings like `"long text" + "more text on next line"`
|
||||
- Turn newline delimited JSON into a valid JSON array, for example:
|
||||
```
|
||||
{ "id": 1, "name": "John" }
|
||||
{ "id": 2, "name": "Sarah" }
|
||||
```
|
||||
|
||||
The `jsonrepair` library has streaming support and can handle infinitely large documents.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install jsonrepair
|
||||
```
|
||||
|
||||
Note that in the `lib` folder, there are builds for ESM, UMD, and CommonJs.
|
||||
|
||||
|
||||
## Use
|
||||
|
||||
### ES module
|
||||
|
||||
Use the `jsonrepair` function using an ES modules import:
|
||||
|
||||
```js
|
||||
import { jsonrepair } from 'jsonrepair'
|
||||
|
||||
try {
|
||||
// The following is invalid JSON: is consists of JSON contents copied from
|
||||
// a JavaScript code base, where the keys are missing double quotes,
|
||||
// and strings are using single quotes:
|
||||
const json = "{name: 'John'}"
|
||||
|
||||
const repaired = jsonrepair(json)
|
||||
|
||||
console.log(repaired) // '{"name": "John"}'
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming API
|
||||
|
||||
Use the streaming API in Node.js:
|
||||
|
||||
```js
|
||||
import { createReadStream, createWriteStream } from 'node:fs'
|
||||
import { pipeline } from 'node:stream'
|
||||
import { jsonrepairTransform } from 'jsonrepair/stream'
|
||||
|
||||
const inputStream = createReadStream('./data/broken.json')
|
||||
const outputStream = createWriteStream('./data/repaired.json')
|
||||
|
||||
pipeline(inputStream, jsonrepairTransform(), outputStream, (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
} else {
|
||||
console.log('done')
|
||||
}
|
||||
})
|
||||
|
||||
// or using .pipe() instead of pipeline():
|
||||
// inputStream
|
||||
// .pipe(jsonrepairTransform())
|
||||
// .pipe(outputStream)
|
||||
// .on('error', (err) => console.error(err))
|
||||
// .on('finish', () => console.log('done'))
|
||||
```
|
||||
|
||||
### CommonJS
|
||||
|
||||
Use in CommonJS (not recommended):
|
||||
|
||||
```js
|
||||
const { jsonrepair } = require('jsonrepair')
|
||||
const json = "{name: 'John'}"
|
||||
console.log(jsonrepair(json)) // '{"name": "John"}'
|
||||
```
|
||||
|
||||
### UMD
|
||||
|
||||
Use with UMD in the browser (not recommended):
|
||||
|
||||
```html
|
||||
<script src="/node_modules/jsonrepair/lib/umd/jsonrepair.js"></script>
|
||||
<script>
|
||||
const { jsonrepair } = JSONRepair
|
||||
const json = "{name: 'John'}"
|
||||
console.log(jsonrepair(json)) // '{"name": "John"}'
|
||||
</script>
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
Use in Python via [`PythonMonkey`](https://github.com/Distributive-Network/PythonMonkey#pythonmonkey).
|
||||
|
||||
1. Install `jsonrepair` via `npm install jsonrepair`
|
||||
2. Install `PythonMonkey` via `pip install pythonmonkey`
|
||||
3. Use the libraries in a Python script:
|
||||
|
||||
```python
|
||||
import pythonmonkey
|
||||
|
||||
jsonrepair = pythonmonkey.require('jsonrepair').jsonrepair
|
||||
|
||||
json = "[1,2,3,"
|
||||
repaired = jsonrepair(json)
|
||||
print(repaired)
|
||||
# [1,2,3]
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Regular API
|
||||
|
||||
You can use `jsonrepair` as a function or as a streaming transform. Broken JSON is passed to the function, and the function either returns the repaired JSON, or throws an `JSONRepairError` exception when an issue is encountered which could not be solved.
|
||||
|
||||
```ts
|
||||
// @throws JSONRepairError
|
||||
jsonrepair(json: string) : string
|
||||
```
|
||||
|
||||
### Streaming API
|
||||
|
||||
The streaming API is availabe in `jsonrepair/stream` and can be used in a [Node.js stream](https://nodejs.org/api/stream.html). It consists of a transform function that can be used in a stream pipeline.
|
||||
|
||||
```ts
|
||||
jsonrepairTransform(options?: { chunkSize?: number, bufferSize?: number }) : Transform
|
||||
```
|
||||
|
||||
The option `chunkSize` determines the size of the chunks that the transform outputs, and is `65536` bytes by default. Changing `chunkSize` can influcence the performance.
|
||||
|
||||
The option `bufferSize` determines how many bytes of the input and output stream are kept in memory and is also `65536` bytes by default. This buffer is used as a "moving window" on the input and output. This is necessary because `jsonrepair` must look ahead or look back to see what to fix, and it must sometimes walk back the generated output to insert a missing comma for example. The `bufferSize` must be larger than the length of the largest string and whitespace in the JSON data, otherwise, and error is thrown when processing the data. Making `bufferSize` very large will result in more memory usage and less performance.
|
||||
|
||||
## Command Line Interface (CLI)
|
||||
|
||||
When `jsonrepair` is installed globally using npm, it can be used on the command line. To install `jsonrepair` globally:
|
||||
|
||||
```bash
|
||||
$ npm install -g jsonrepair
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
$ jsonrepair [filename] {OPTIONS}
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
```
|
||||
--version, -v Show application version
|
||||
--help, -h Show this message
|
||||
--output, -o Output file
|
||||
--overwrite Overwrite the input file
|
||||
--buffer Buffer size in bytes, for example 64K (default) or 1M
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
$ jsonrepair broken.json # Repair a file, output to console
|
||||
$ jsonrepair broken.json > repaired.json # Repair a file, output to file
|
||||
$ jsonrepair broken.json --output repaired.json # Repair a file, output to file
|
||||
$ jsonrepair broken.json --overwrite # Repair a file, replace the file itself
|
||||
$ cat broken.json | jsonrepair # Repair data from an input stream
|
||||
$ cat broken.json | jsonrepair > repaired.json # Repair data from an input stream, output to file
|
||||
```
|
||||
|
||||
## Alternatives:
|
||||
|
||||
Similar libraries:
|
||||
|
||||
- https://github.com/RyanMarcus/dirty-json
|
||||
|
||||
## Develop
|
||||
|
||||
When implementing a fix or a new feature, it important to know that there are currently two implementations:
|
||||
|
||||
- `src/regular` This is a non-streaming implementation. The code is small and works for files up to 512MB, ideal for usage in the browser.
|
||||
- `src/streaming` A streaming implementation that can be used in Node.js. The code is larger and more complex, and the implementation uses a configurable `bufferSize` and `chunkSize`. When the parsed document contains a string or number that is longer than the configured `bufferSize`, the library will throw an "Index out of range" error since it cannot hold the full string in the buffer. When configured with an infinite buffer size, the streaming implementation works the same as the regular implementation. In that case this out of range error cannot occur, but it makes the performance worse and the application can run out of memory when repairing large documents.
|
||||
|
||||
Both implementations are tested against the same suite of unit tests in `src/index.test.ts`.
|
||||
|
||||
Scripts:
|
||||
|
||||
Script | Description
|
||||
---------- | -----------
|
||||
`npm install` | Install the dependencies once
|
||||
`npm run build` | Build the library (ESM, CommonJs, and UMD output in the folder `lib`)
|
||||
`npm test` | Run the unit tests
|
||||
`npm run lint` | Run the linter (eslint)
|
||||
`npm run format` | Automatically fix linter issues
|
||||
`npm run build-and-test` | Run the linter, build all, and run unit tests and integration tests
|
||||
`npm run release` | Release a new version. This will lint, test, build, increment the version number, push the changes to git, add a git version tag, and publish the npm package.
|
||||
`npm run release-dry-run` | Run all release steps and see the change list without actually publishing:
|
||||
|
||||
## License
|
||||
|
||||
Released under the [ISC license](LICENSE.md).
|
||||
179
node_modules/jsonrepair/bin/cli.js
generated
vendored
Executable file
179
node_modules/jsonrepair/bin/cli.js
generated
vendored
Executable file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env node
|
||||
import { createReadStream, createWriteStream, readFileSync, renameSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { pipeline as pipelineCallback } from 'node:stream'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import { jsonrepairTransform } from '../lib/esm/stream.js'
|
||||
|
||||
const pipeline = promisify(pipelineCallback)
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
function processArgs(args) {
|
||||
const options = {
|
||||
version: false,
|
||||
help: false,
|
||||
overwrite: false,
|
||||
bufferSize: undefined,
|
||||
inputFile: null,
|
||||
outputFile: null
|
||||
}
|
||||
|
||||
// we skip the first two args, since they contain node and the script path
|
||||
let i = 2
|
||||
while (i < args.length) {
|
||||
const arg = args[i]
|
||||
|
||||
switch (arg) {
|
||||
case '-v':
|
||||
case '--version':
|
||||
options.version = true
|
||||
break
|
||||
|
||||
case '-h':
|
||||
case '--help':
|
||||
options.help = true
|
||||
break
|
||||
|
||||
case '--overwrite':
|
||||
options.overwrite = true
|
||||
break
|
||||
|
||||
case '--buffer':
|
||||
i++
|
||||
options.bufferSize = parseSize(args[i])
|
||||
break
|
||||
|
||||
case '-o':
|
||||
case '--output':
|
||||
i++
|
||||
options.outputFile = args[i]
|
||||
break
|
||||
|
||||
default:
|
||||
if (options.inputFile == null) {
|
||||
options.inputFile = arg
|
||||
} else {
|
||||
throw new Error(`Unexpected argument "${arg}"`)
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
async function run(options) {
|
||||
if (options.version) {
|
||||
outputVersion()
|
||||
return
|
||||
}
|
||||
|
||||
if (options.help) {
|
||||
outputHelp()
|
||||
return
|
||||
}
|
||||
|
||||
if (options.overwrite) {
|
||||
if (!options.inputFile) {
|
||||
console.error('Error: cannot use --overwrite: no input file provided')
|
||||
process.exit(1)
|
||||
}
|
||||
if (options.outputFile) {
|
||||
console.error('Error: cannot use --overwrite: there is also an --output provided')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const dateStr = new Date().toISOString().replace(/\W/g, '-')
|
||||
const tempFileSuffix = `.repair-${dateStr}.json`
|
||||
const tempFile = options.inputFile + tempFileSuffix
|
||||
|
||||
try {
|
||||
const readStream = createReadStream(options.inputFile)
|
||||
const writeStream = createWriteStream(tempFile)
|
||||
await pipeline(
|
||||
readStream,
|
||||
jsonrepairTransform({ bufferSize: options.bufferSize }),
|
||||
writeStream
|
||||
)
|
||||
renameSync(tempFile, options.inputFile)
|
||||
} catch (err) {
|
||||
process.stderr.write(err.toString())
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const readStream = options.inputFile ? createReadStream(options.inputFile) : process.stdin
|
||||
const writeStream = options.outputFile ? createWriteStream(options.outputFile) : process.stdout
|
||||
await pipeline(readStream, jsonrepairTransform({ bufferSize: options.bufferSize }), writeStream)
|
||||
} catch (err) {
|
||||
process.stderr.write(err.toString())
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function outputVersion() {
|
||||
const file = join(__dirname, '../package.json')
|
||||
const pkg = JSON.parse(String(readFileSync(file, 'utf-8')))
|
||||
|
||||
console.log(pkg.version)
|
||||
}
|
||||
|
||||
function parseSize(size) {
|
||||
// match
|
||||
const match = size.match(/^(\d+)([KMG]?)$/)
|
||||
if (!match) {
|
||||
throw new Error(`Buffer size "${size}" not recognized. Examples: 65536, 512K, 2M`)
|
||||
}
|
||||
|
||||
const num = Number.parseInt(match[1], 10)
|
||||
const suffix = match[2] // K, M, or G
|
||||
|
||||
switch (suffix) {
|
||||
case 'K':
|
||||
return num * 1024
|
||||
case 'M':
|
||||
return num * 1024 * 1024
|
||||
case 'G':
|
||||
return num * 1024 * 1024 * 1024
|
||||
default:
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
const help = `
|
||||
jsonrepair
|
||||
https://github.com/josdejong/jsonrepair
|
||||
|
||||
Repair invalid JSON documents. When a document could not be repaired, the output will be left unchanged.
|
||||
|
||||
Usage:
|
||||
jsonrepair [filename] {OPTIONS}
|
||||
|
||||
Options:
|
||||
--version, -v Show application version
|
||||
--help, -h Show this message
|
||||
--output, -o Output file
|
||||
--overwrite Overwrite the input file
|
||||
--buffer Buffer size in bytes, for example 64K (default) or 1M
|
||||
|
||||
Example usage:
|
||||
jsonrepair broken.json # Repair a file, output to console
|
||||
jsonrepair broken.json > repaired.json # Repair a file, output to file
|
||||
jsonrepair broken.json --output repaired.json # Repair a file, output to file
|
||||
jsonrepair broken.json --overwrite # Repair a file, replace the file itself
|
||||
cat broken.json | jsonrepair # Repair data from an input stream
|
||||
cat broken.json | jsonrepair > repaired.json # Repair data from an input stream, output to file
|
||||
`
|
||||
|
||||
function outputHelp() {
|
||||
console.log(help)
|
||||
}
|
||||
|
||||
const options = processArgs(process.argv)
|
||||
await run(options)
|
||||
20
node_modules/jsonrepair/lib/cjs/index.js
generated
vendored
Normal file
20
node_modules/jsonrepair/lib/cjs/index.js
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "JSONRepairError", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _JSONRepairError.JSONRepairError;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "jsonrepair", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _jsonrepair.jsonrepair;
|
||||
}
|
||||
});
|
||||
var _jsonrepair = require("./regular/jsonrepair.js");
|
||||
var _JSONRepairError = require("./utils/JSONRepairError.js");
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/index.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/index.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":["_jsonrepair","require","_JSONRepairError"],"sources":["../../src/index.ts"],"sourcesContent":["// Cross-platform, non-streaming JavaScript API\nexport { jsonrepair } from './regular/jsonrepair.js'\nexport { JSONRepairError } from './utils/JSONRepairError.js'\n"],"mappings":";;;;;;;;;;;;;;;;;AACA,IAAAA,WAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA","ignoreList":[]}
|
||||
3
node_modules/jsonrepair/lib/cjs/package.json
generated
vendored
Normal file
3
node_modules/jsonrepair/lib/cjs/package.json
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
746
node_modules/jsonrepair/lib/cjs/regular/jsonrepair.js
generated
vendored
Normal file
746
node_modules/jsonrepair/lib/cjs/regular/jsonrepair.js
generated
vendored
Normal file
@ -0,0 +1,746 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.jsonrepair = jsonrepair;
|
||||
var _JSONRepairError = require("../utils/JSONRepairError.js");
|
||||
var _stringUtils = require("../utils/stringUtils.js");
|
||||
const controlCharacters = {
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
};
|
||||
|
||||
// map with all escape characters
|
||||
const escapeCharacters = {
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
b: '\b',
|
||||
f: '\f',
|
||||
n: '\n',
|
||||
r: '\r',
|
||||
t: '\t'
|
||||
// note that \u is handled separately in parseString()
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair a string containing an invalid JSON document.
|
||||
* For example changes JavaScript notation into JSON notation.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* try {
|
||||
* const json = "{name: 'John'}"
|
||||
* const repaired = jsonrepair(json)
|
||||
* console.log(repaired)
|
||||
* // '{"name": "John"}'
|
||||
* } catch (err) {
|
||||
* console.error(err)
|
||||
* }
|
||||
*
|
||||
*/
|
||||
function jsonrepair(text) {
|
||||
let i = 0; // current index in text
|
||||
let output = ''; // generated output
|
||||
|
||||
parseMarkdownCodeBlock(['```', '[```', '{```']);
|
||||
const processed = parseValue();
|
||||
if (!processed) {
|
||||
throwUnexpectedEnd();
|
||||
}
|
||||
parseMarkdownCodeBlock(['```', '```]', '```}']);
|
||||
const processedComma = parseCharacter(',');
|
||||
if (processedComma) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if ((0, _stringUtils.isStartOfValue)(text[i]) && (0, _stringUtils.endsWithCommaOrNewline)(output)) {
|
||||
// start of a new value after end of the root level object: looks like
|
||||
// newline delimited JSON -> turn into a root level array
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ',');
|
||||
}
|
||||
parseNewlineDelimitedJSON();
|
||||
} else if (processedComma) {
|
||||
// repair: remove trailing comma
|
||||
output = (0, _stringUtils.stripLastOccurrence)(output, ',');
|
||||
}
|
||||
|
||||
// repair redundant end quotes
|
||||
while (text[i] === '}' || text[i] === ']') {
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (i >= text.length) {
|
||||
// reached the end of the document properly
|
||||
return output;
|
||||
}
|
||||
throwUnexpectedCharacter();
|
||||
function parseValue() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processed = parseObject() || parseArray() || parseString() || parseNumber() || parseKeywords() || parseUnquotedString(false) || parseRegex();
|
||||
parseWhitespaceAndSkipComments();
|
||||
return processed;
|
||||
}
|
||||
function parseWhitespaceAndSkipComments() {
|
||||
let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
const start = i;
|
||||
let changed = parseWhitespace(skipNewline);
|
||||
do {
|
||||
changed = parseComment();
|
||||
if (changed) {
|
||||
changed = parseWhitespace(skipNewline);
|
||||
}
|
||||
} while (changed);
|
||||
return i > start;
|
||||
}
|
||||
function parseWhitespace(skipNewline) {
|
||||
const _isWhiteSpace = skipNewline ? _stringUtils.isWhitespace : _stringUtils.isWhitespaceExceptNewline;
|
||||
let whitespace = '';
|
||||
while (true) {
|
||||
if (_isWhiteSpace(text, i)) {
|
||||
whitespace += text[i];
|
||||
i++;
|
||||
} else if ((0, _stringUtils.isSpecialWhitespace)(text, i)) {
|
||||
// repair special whitespace
|
||||
whitespace += ' ';
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace.length > 0) {
|
||||
output += whitespace;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseComment() {
|
||||
// find a block comment '/* ... */'
|
||||
if (text[i] === '/' && text[i + 1] === '*') {
|
||||
// repair block comment by skipping it
|
||||
while (i < text.length && !atEndOfBlockComment(text, i)) {
|
||||
i++;
|
||||
}
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find a line comment '// ...'
|
||||
if (text[i] === '/' && text[i + 1] === '/') {
|
||||
// repair line comment by skipping it
|
||||
while (i < text.length && text[i] !== '\n') {
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseMarkdownCodeBlock(blocks) {
|
||||
// find and skip over a Markdown fenced code block:
|
||||
// ``` ... ```
|
||||
// or
|
||||
// ```json ... ```
|
||||
if (skipMarkdownCodeBlock(blocks)) {
|
||||
if ((0, _stringUtils.isFunctionNameCharStart)(text[i])) {
|
||||
// strip the optional language specifier like "json"
|
||||
while (i < text.length && (0, _stringUtils.isFunctionNameChar)(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipMarkdownCodeBlock(blocks) {
|
||||
parseWhitespace(true);
|
||||
for (const block of blocks) {
|
||||
const end = i + block.length;
|
||||
if (text.slice(i, end) === block) {
|
||||
i = end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
output += text[i];
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipEscapeCharacter() {
|
||||
return skipCharacter('\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ellipsis like "[1,2,3,...]" or "[1,2,3,...,9]" or "[...,7,8,9]"
|
||||
* or a similar construct in objects.
|
||||
*/
|
||||
function skipEllipsis() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (text[i] === '.' && text[i + 1] === '.' && text[i + 2] === '.') {
|
||||
// repair: remove the ellipsis (three dots) and optionally a comma
|
||||
i += 3;
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipCharacter(',');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an object like '{"key": "value"}'
|
||||
*/
|
||||
function parseObject() {
|
||||
if (text[i] === '{') {
|
||||
output += '{';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in {, message: "hi"}
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== '}') {
|
||||
let processedComma;
|
||||
if (!initial) {
|
||||
processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ',');
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
} else {
|
||||
processedComma = true;
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedKey = parseString() || parseUnquotedString(true);
|
||||
if (!processedKey) {
|
||||
if (text[i] === '}' || text[i] === '{' || text[i] === ']' || text[i] === '[' || text[i] === undefined) {
|
||||
// repair trailing comma
|
||||
output = (0, _stringUtils.stripLastOccurrence)(output, ',');
|
||||
} else {
|
||||
throwObjectKeyExpected();
|
||||
}
|
||||
break;
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processedColon = parseCharacter(':');
|
||||
const truncatedText = i >= text.length;
|
||||
if (!processedColon) {
|
||||
if ((0, _stringUtils.isStartOfValue)(text[i]) || truncatedText) {
|
||||
// repair missing colon
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ':');
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
if (processedColon || truncatedText) {
|
||||
// repair missing object value
|
||||
output += 'null';
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text[i] === '}') {
|
||||
output += '}';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing end bracket
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, '}');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array like '["item1", "item2", ...]'
|
||||
*/
|
||||
function parseArray() {
|
||||
if (text[i] === '[') {
|
||||
output += '[';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in [,1,2,3]
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== ']') {
|
||||
if (!initial) {
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
// repair trailing comma
|
||||
output = (0, _stringUtils.stripLastOccurrence)(output, ',');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text[i] === ']') {
|
||||
output += ']';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing closing array bracket
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ']');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and repair Newline Delimited JSON (NDJSON):
|
||||
* multiple JSON objects separated by a newline character
|
||||
*/
|
||||
function parseNewlineDelimitedJSON() {
|
||||
// repair NDJSON
|
||||
let initial = true;
|
||||
let processedValue = true;
|
||||
while (processedValue) {
|
||||
if (!initial) {
|
||||
// parse optional comma, insert when missing
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair: add missing comma
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
processedValue = parseValue();
|
||||
}
|
||||
if (!processedValue) {
|
||||
// repair: remove trailing comma
|
||||
output = (0, _stringUtils.stripLastOccurrence)(output, ',');
|
||||
}
|
||||
|
||||
// repair: wrap the output inside array brackets
|
||||
output = `[\n${output}\n]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string enclosed by double quotes "...". Can contain escaped quotes
|
||||
* Repair strings enclosed in single quotes or special quotes
|
||||
* Repair an escaped string
|
||||
*
|
||||
* The function can run in two stages:
|
||||
* - First, it assumes the string has a valid end quote
|
||||
* - If it turns out that the string does not have a valid end quote followed
|
||||
* by a delimiter (which should be the case), the function runs again in a
|
||||
* more conservative way, stopping the string at the first next delimiter
|
||||
* and fixing the string by inserting a quote there, or stopping at a
|
||||
* stop index detected in the first iteration.
|
||||
*/
|
||||
function parseString() {
|
||||
let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
|
||||
let skipEscapeChars = text[i] === '\\';
|
||||
if (skipEscapeChars) {
|
||||
// repair: remove the first escape character
|
||||
i++;
|
||||
skipEscapeChars = true;
|
||||
}
|
||||
if ((0, _stringUtils.isQuote)(text[i])) {
|
||||
// double quotes are correct JSON,
|
||||
// single quotes come from JavaScript for example, we assume it will have a correct single end quote too
|
||||
// otherwise, we will match any double-quote-like start with a double-quote-like end,
|
||||
// or any single-quote-like start with a single-quote-like end
|
||||
const isEndQuote = (0, _stringUtils.isDoubleQuote)(text[i]) ? _stringUtils.isDoubleQuote : (0, _stringUtils.isSingleQuote)(text[i]) ? _stringUtils.isSingleQuote : (0, _stringUtils.isSingleQuoteLike)(text[i]) ? _stringUtils.isSingleQuoteLike : _stringUtils.isDoubleQuoteLike;
|
||||
const iBefore = i;
|
||||
const oBefore = output.length;
|
||||
let str = '"';
|
||||
i++;
|
||||
while (true) {
|
||||
if (i >= text.length) {
|
||||
// end of text, we are missing an end quote
|
||||
|
||||
const iPrev = prevNonWhitespaceIndex(i - 1);
|
||||
if (!stopAtDelimiter && (0, _stringUtils.isDelimiter)(text.charAt(iPrev))) {
|
||||
// if the text ends with a delimiter, like ["hello],
|
||||
// so the missing end quote should be inserted before this delimiter
|
||||
// retry parsing the string, stopping at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = (0, _stringUtils.insertBeforeLastWhitespace)(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (i === stopAtIndex) {
|
||||
// use the stop index detected in the first iteration, and repair end quote
|
||||
str = (0, _stringUtils.insertBeforeLastWhitespace)(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (isEndQuote(text[i])) {
|
||||
// end quote
|
||||
// let us check what is before and after the quote to verify whether this is a legit end quote
|
||||
const iQuote = i;
|
||||
const oQuote = str.length;
|
||||
str += '"';
|
||||
i++;
|
||||
output += str;
|
||||
parseWhitespaceAndSkipComments(false);
|
||||
if (stopAtDelimiter || i >= text.length || (0, _stringUtils.isDelimiter)(text[i]) || (0, _stringUtils.isQuote)(text[i]) || (0, _stringUtils.isDigit)(text[i])) {
|
||||
// The quote is followed by the end of the text, a delimiter,
|
||||
// or a next value. So the quote is indeed the end of the string.
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
}
|
||||
const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);
|
||||
const prevChar = text.charAt(iPrevChar);
|
||||
if (prevChar === ',') {
|
||||
// A comma followed by a quote, like '{"a":"b,c,"d":"e"}'.
|
||||
// We assume that the quote is a start quote, and that the end quote
|
||||
// should have been located right before the comma but is missing.
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(false, iPrevChar);
|
||||
}
|
||||
if ((0, _stringUtils.isDelimiter)(prevChar)) {
|
||||
// This is not the right end quote: it is preceded by a delimiter,
|
||||
// and NOT followed by a delimiter. So, there is an end quote missing
|
||||
// parse the string again and then stop at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// revert to right after the quote but before any whitespace, and continue parsing the string
|
||||
output = output.substring(0, oBefore);
|
||||
i = iQuote + 1;
|
||||
|
||||
// repair unescaped quote
|
||||
str = `${str.substring(0, oQuote)}\\${str.substring(oQuote)}`;
|
||||
} else if (stopAtDelimiter && (0, _stringUtils.isUnquotedStringDelimiter)(text[i])) {
|
||||
// we're in the mode to stop the string at the first delimiter
|
||||
// because there is an end quote missing
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && _stringUtils.regexUrlStart.test(text.substring(iBefore + 1, i + 2))) {
|
||||
while (i < text.length && _stringUtils.regexUrlChar.test(text[i])) {
|
||||
str += text[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = (0, _stringUtils.insertBeforeLastWhitespace)(str, '"');
|
||||
output += str;
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
} else if (text[i] === '\\') {
|
||||
// handle escaped content like \n or \u2605
|
||||
const char = text.charAt(i + 1);
|
||||
const escapeChar = escapeCharacters[char];
|
||||
if (escapeChar !== undefined) {
|
||||
str += text.slice(i, i + 2);
|
||||
i += 2;
|
||||
} else if (char === 'u') {
|
||||
let j = 2;
|
||||
while (j < 6 && (0, _stringUtils.isHex)(text[i + j])) {
|
||||
j++;
|
||||
}
|
||||
if (j === 6) {
|
||||
str += text.slice(i, i + 6);
|
||||
i += 6;
|
||||
} else if (i + j >= text.length) {
|
||||
// repair invalid or truncated unicode char at the end of the text
|
||||
// by removing the unicode char and ending the string here
|
||||
i = text.length;
|
||||
} else {
|
||||
throwInvalidUnicodeCharacter();
|
||||
}
|
||||
} else {
|
||||
// repair invalid escape character: remove it
|
||||
str += char;
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
// handle regular characters
|
||||
const char = text.charAt(i);
|
||||
if (char === '"' && text[i - 1] !== '\\') {
|
||||
// repair unescaped double quote
|
||||
str += `\\${char}`;
|
||||
i++;
|
||||
} else if ((0, _stringUtils.isControlCharacter)(char)) {
|
||||
// unescaped control character
|
||||
str += controlCharacters[char];
|
||||
i++;
|
||||
} else {
|
||||
if (!(0, _stringUtils.isValidStringCharacter)(char)) {
|
||||
throwInvalidCharacter(char);
|
||||
}
|
||||
str += char;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (skipEscapeChars) {
|
||||
// repair: skipped escape character (nothing to do)
|
||||
skipEscapeCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair concatenated strings like "hello" + "world", change this into "helloworld"
|
||||
*/
|
||||
function parseConcatenatedString() {
|
||||
let processed = false;
|
||||
parseWhitespaceAndSkipComments();
|
||||
while (text[i] === '+') {
|
||||
processed = true;
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: remove the end quote of the first string
|
||||
output = (0, _stringUtils.stripLastOccurrence)(output, '"', true);
|
||||
const start = output.length;
|
||||
const parsedStr = parseString();
|
||||
if (parsedStr) {
|
||||
// repair: remove the start quote of the second string
|
||||
output = (0, _stringUtils.removeAtIndex)(output, start, 1);
|
||||
} else {
|
||||
// repair: remove the + because it is not followed by a string
|
||||
output = (0, _stringUtils.insertBeforeLastWhitespace)(output, '"');
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number like 2.4 or 2.4e6
|
||||
*/
|
||||
function parseNumber() {
|
||||
const start = i;
|
||||
if (text[i] === '-') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in JSON leading zeros like "00789" are not allowed.
|
||||
// We will allow all leading zeros here though and at the end of parseNumber
|
||||
// check against trailing zeros and repair that if needed.
|
||||
// Leading zeros can have meaning, so we should not clear them.
|
||||
while ((0, _stringUtils.isDigit)(text[i])) {
|
||||
i++;
|
||||
}
|
||||
if (text[i] === '.') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while ((0, _stringUtils.isDigit)(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (text[i] === 'e' || text[i] === 'E') {
|
||||
i++;
|
||||
if (text[i] === '-' || text[i] === '+') {
|
||||
i++;
|
||||
}
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while ((0, _stringUtils.isDigit)(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not at the end of the number by this point, allow this to be parsed as another type
|
||||
if (!atEndOfNumber()) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
if (i > start) {
|
||||
// repair a number with leading zeros like "00789"
|
||||
const num = text.slice(start, i);
|
||||
const hasInvalidLeadingZero = /^0\d/.test(num);
|
||||
output += hasInvalidLeadingZero ? `"${num}"` : num;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keywords true, false, null
|
||||
* Repair Python keywords True, False, None
|
||||
*/
|
||||
function parseKeywords() {
|
||||
return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||
|
||||
// repair Python keywords True, False, None
|
||||
parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');
|
||||
}
|
||||
function parseKeyword(name, value) {
|
||||
if (text.slice(i, i + name.length) === name) {
|
||||
output += value;
|
||||
i += name.length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair an unquoted string by adding quotes around it
|
||||
* Repair a MongoDB function call like NumberLong("2")
|
||||
* Repair a JSONP function call like callback({...});
|
||||
*/
|
||||
function parseUnquotedString(isKey) {
|
||||
// note that the symbol can end with whitespaces: we stop at the next delimiter
|
||||
// also, note that we allow strings to contain a slash / in order to support repairing regular expressions
|
||||
const start = i;
|
||||
if ((0, _stringUtils.isFunctionNameCharStart)(text[i])) {
|
||||
while (i < text.length && (0, _stringUtils.isFunctionNameChar)(text[i])) {
|
||||
i++;
|
||||
}
|
||||
let j = i;
|
||||
while ((0, _stringUtils.isWhitespace)(text, j)) {
|
||||
j++;
|
||||
}
|
||||
if (text[j] === '(') {
|
||||
// repair a MongoDB function call like NumberLong("2")
|
||||
// repair a JSONP function call like callback({...});
|
||||
i = j + 1;
|
||||
parseValue();
|
||||
if (text[i] === ')') {
|
||||
// repair: skip close bracket of function call
|
||||
i++;
|
||||
if (text[i] === ';') {
|
||||
// repair: skip semicolon after JSONP call
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (i < text.length && !(0, _stringUtils.isUnquotedStringDelimiter)(text[i]) && !(0, _stringUtils.isQuote)(text[i]) && (!isKey || text[i] !== ':')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && _stringUtils.regexUrlStart.test(text.substring(start, i + 2))) {
|
||||
while (i < text.length && _stringUtils.regexUrlChar.test(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i > start) {
|
||||
// repair unquoted string
|
||||
// also, repair undefined into null
|
||||
|
||||
// first, go back to prevent getting trailing whitespaces in the string
|
||||
while ((0, _stringUtils.isWhitespace)(text, i - 1) && i > 0) {
|
||||
i--;
|
||||
}
|
||||
const symbol = text.slice(start, i);
|
||||
output += symbol === 'undefined' ? 'null' : JSON.stringify(symbol);
|
||||
if (text[i] === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function parseRegex() {
|
||||
if (text[i] === '/') {
|
||||
const start = i;
|
||||
i++;
|
||||
while (i < text.length && (text[i] !== '/' || text[i - 1] === '\\')) {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
output += `"${text.substring(start, i)}"`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function prevNonWhitespaceIndex(start) {
|
||||
let prev = start;
|
||||
while (prev > 0 && (0, _stringUtils.isWhitespace)(text, prev)) {
|
||||
prev--;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
function atEndOfNumber() {
|
||||
return i >= text.length || (0, _stringUtils.isDelimiter)(text[i]) || (0, _stringUtils.isWhitespace)(text, i);
|
||||
}
|
||||
function repairNumberEndingWithNumericSymbol(start) {
|
||||
// repair numbers cut off at the end
|
||||
// this will only be called when we end after a '.', '-', or 'e' and does not
|
||||
// change the number more than it needs to make it valid JSON
|
||||
output += `${text.slice(start, i)}0`;
|
||||
}
|
||||
function throwInvalidCharacter(char) {
|
||||
throw new _JSONRepairError.JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);
|
||||
}
|
||||
function throwUnexpectedCharacter() {
|
||||
throw new _JSONRepairError.JSONRepairError(`Unexpected character ${JSON.stringify(text[i])}`, i);
|
||||
}
|
||||
function throwUnexpectedEnd() {
|
||||
throw new _JSONRepairError.JSONRepairError('Unexpected end of json string', text.length);
|
||||
}
|
||||
function throwObjectKeyExpected() {
|
||||
throw new _JSONRepairError.JSONRepairError('Object key expected', i);
|
||||
}
|
||||
function throwColonExpected() {
|
||||
throw new _JSONRepairError.JSONRepairError('Colon expected', i);
|
||||
}
|
||||
function throwInvalidUnicodeCharacter() {
|
||||
const chars = text.slice(i, i + 6);
|
||||
throw new _JSONRepairError.JSONRepairError(`Invalid unicode character "${chars}"`, i);
|
||||
}
|
||||
}
|
||||
function atEndOfBlockComment(text, i) {
|
||||
return text[i] === '*' && text[i + 1] === '/';
|
||||
}
|
||||
//# sourceMappingURL=jsonrepair.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/regular/jsonrepair.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/regular/jsonrepair.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
13
node_modules/jsonrepair/lib/cjs/stream.js
generated
vendored
Normal file
13
node_modules/jsonrepair/lib/cjs/stream.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "jsonrepairTransform", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _stream.jsonrepairTransform;
|
||||
}
|
||||
});
|
||||
var _stream = require("./streaming/stream.js");
|
||||
//# sourceMappingURL=stream.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/stream.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/stream.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.js","names":["_stream","require"],"sources":["../../src/stream.ts"],"sourcesContent":["// Node.js streaming API\nexport { type JsonRepairTransformOptions, jsonrepairTransform } from './streaming/stream.js'\n"],"mappings":";;;;;;;;;;;AACA,IAAAA,OAAA,GAAAC,OAAA","ignoreList":[]}
|
||||
75
node_modules/jsonrepair/lib/cjs/streaming/buffer/InputBuffer.js
generated
vendored
Normal file
75
node_modules/jsonrepair/lib/cjs/streaming/buffer/InputBuffer.js
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.createInputBuffer = createInputBuffer;
|
||||
function createInputBuffer() {
|
||||
let buffer = '';
|
||||
let offset = 0;
|
||||
let currentLength = 0;
|
||||
let closed = false;
|
||||
function ensure(index) {
|
||||
if (index < offset) {
|
||||
throw new Error(`${indexOutOfRangeMessage} (index: ${index}, offset: ${offset})`);
|
||||
}
|
||||
if (index >= currentLength) {
|
||||
if (!closed) {
|
||||
throw new Error(`${indexOutOfRangeMessage} (index: ${index})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function push(chunk) {
|
||||
buffer += chunk;
|
||||
currentLength += chunk.length;
|
||||
}
|
||||
function flush(position) {
|
||||
if (position > currentLength) {
|
||||
return;
|
||||
}
|
||||
buffer = buffer.substring(position - offset);
|
||||
offset = position;
|
||||
}
|
||||
function charAt(index) {
|
||||
ensure(index);
|
||||
return buffer.charAt(index - offset);
|
||||
}
|
||||
function charCodeAt(index) {
|
||||
ensure(index);
|
||||
return buffer.charCodeAt(index - offset);
|
||||
}
|
||||
function substring(start, end) {
|
||||
ensure(end - 1); // -1 because end is excluded
|
||||
ensure(start);
|
||||
return buffer.slice(start - offset, end - offset);
|
||||
}
|
||||
function length() {
|
||||
if (!closed) {
|
||||
throw new Error('Cannot get length: input is not yet closed');
|
||||
}
|
||||
return currentLength;
|
||||
}
|
||||
function isEnd(index) {
|
||||
if (!closed) {
|
||||
ensure(index);
|
||||
}
|
||||
return index >= currentLength;
|
||||
}
|
||||
function close() {
|
||||
closed = true;
|
||||
}
|
||||
return {
|
||||
push,
|
||||
flush,
|
||||
charAt,
|
||||
charCodeAt,
|
||||
substring,
|
||||
length,
|
||||
currentLength: () => currentLength,
|
||||
currentBufferSize: () => buffer.length,
|
||||
isEnd,
|
||||
close
|
||||
};
|
||||
}
|
||||
const indexOutOfRangeMessage = 'Index out of range, please configure a larger buffer size';
|
||||
//# sourceMappingURL=InputBuffer.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/streaming/buffer/InputBuffer.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/streaming/buffer/InputBuffer.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"InputBuffer.js","names":["createInputBuffer","buffer","offset","currentLength","closed","ensure","index","Error","indexOutOfRangeMessage","push","chunk","length","flush","position","substring","charAt","charCodeAt","start","end","slice","isEnd","close","currentBufferSize"],"sources":["../../../../src/streaming/buffer/InputBuffer.ts"],"sourcesContent":["export interface InputBuffer {\n push: (chunk: string) => void\n flush: (position: number) => void\n charAt: (index: number) => string\n charCodeAt: (index: number) => number\n substring: (start: number, end: number) => string\n length: () => number\n currentLength: () => number\n currentBufferSize: () => number\n isEnd: (index: number) => boolean\n close: () => void\n}\n\nexport function createInputBuffer(): InputBuffer {\n let buffer = ''\n let offset = 0\n let currentLength = 0\n let closed = false\n\n function ensure(index: number) {\n if (index < offset) {\n throw new Error(`${indexOutOfRangeMessage} (index: ${index}, offset: ${offset})`)\n }\n\n if (index >= currentLength) {\n if (!closed) {\n throw new Error(`${indexOutOfRangeMessage} (index: ${index})`)\n }\n }\n }\n\n function push(chunk: string) {\n buffer += chunk\n currentLength += chunk.length\n }\n\n function flush(position: number) {\n if (position > currentLength) {\n return\n }\n\n buffer = buffer.substring(position - offset)\n offset = position\n }\n\n function charAt(index: number): string {\n ensure(index)\n\n return buffer.charAt(index - offset)\n }\n\n function charCodeAt(index: number): number {\n ensure(index)\n\n return buffer.charCodeAt(index - offset)\n }\n\n function substring(start: number, end: number): string {\n ensure(end - 1) // -1 because end is excluded\n ensure(start)\n\n return buffer.slice(start - offset, end - offset)\n }\n\n function length(): number {\n if (!closed) {\n throw new Error('Cannot get length: input is not yet closed')\n }\n\n return currentLength\n }\n\n function isEnd(index: number): boolean {\n if (!closed) {\n ensure(index)\n }\n\n return index >= currentLength\n }\n\n function close() {\n closed = true\n }\n\n return {\n push,\n flush,\n charAt,\n charCodeAt,\n substring,\n length,\n currentLength: () => currentLength,\n currentBufferSize: () => buffer.length,\n isEnd,\n close\n }\n}\n\nconst indexOutOfRangeMessage = 'Index out of range, please configure a larger buffer size'\n"],"mappings":";;;;;;AAaO,SAASA,iBAAiBA,CAAA,EAAgB;EAC/C,IAAIC,MAAM,GAAG,EAAE;EACf,IAAIC,MAAM,GAAG,CAAC;EACd,IAAIC,aAAa,GAAG,CAAC;EACrB,IAAIC,MAAM,GAAG,KAAK;EAElB,SAASC,MAAMA,CAACC,KAAa,EAAE;IAC7B,IAAIA,KAAK,GAAGJ,MAAM,EAAE;MAClB,MAAM,IAAIK,KAAK,CAAC,GAAGC,sBAAsB,YAAYF,KAAK,aAAaJ,MAAM,GAAG,CAAC;IACnF;IAEA,IAAII,KAAK,IAAIH,aAAa,EAAE;MAC1B,IAAI,CAACC,MAAM,EAAE;QACX,MAAM,IAAIG,KAAK,CAAC,GAAGC,sBAAsB,YAAYF,KAAK,GAAG,CAAC;MAChE;IACF;EACF;EAEA,SAASG,IAAIA,CAACC,KAAa,EAAE;IAC3BT,MAAM,IAAIS,KAAK;IACfP,aAAa,IAAIO,KAAK,CAACC,MAAM;EAC/B;EAEA,SAASC,KAAKA,CAACC,QAAgB,EAAE;IAC/B,IAAIA,QAAQ,GAAGV,aAAa,EAAE;MAC5B;IACF;IAEAF,MAAM,GAAGA,MAAM,CAACa,SAAS,CAACD,QAAQ,GAAGX,MAAM,CAAC;IAC5CA,MAAM,GAAGW,QAAQ;EACnB;EAEA,SAASE,MAAMA,CAACT,KAAa,EAAU;IACrCD,MAAM,CAACC,KAAK,CAAC;IAEb,OAAOL,MAAM,CAACc,MAAM,CAACT,KAAK,GAAGJ,MAAM,CAAC;EACtC;EAEA,SAASc,UAAUA,CAACV,KAAa,EAAU;IACzCD,MAAM,CAACC,KAAK,CAAC;IAEb,OAAOL,MAAM,CAACe,UAAU,CAACV,KAAK,GAAGJ,MAAM,CAAC;EAC1C;EAEA,SAASY,SAASA,CAACG,KAAa,EAAEC,GAAW,EAAU;IACrDb,MAAM,CAACa,GAAG,GAAG,CAAC,CAAC,EAAC;IAChBb,MAAM,CAACY,KAAK,CAAC;IAEb,OAAOhB,MAAM,CAACkB,KAAK,CAACF,KAAK,GAAGf,MAAM,EAAEgB,GAAG,GAAGhB,MAAM,CAAC;EACnD;EAEA,SAASS,MAAMA,CAAA,EAAW;IACxB,IAAI,CAACP,MAAM,EAAE;MACX,MAAM,IAAIG,KAAK,CAAC,4CAA4C,CAAC;IAC/D;IAEA,OAAOJ,aAAa;EACtB;EAEA,SAASiB,KAAKA,CAACd,KAAa,EAAW;IACrC,IAAI,CAACF,MAAM,EAAE;MACXC,MAAM,CAACC,KAAK,CAAC;IACf;IAEA,OAAOA,KAAK,IAAIH,aAAa;EAC/B;EAEA,SAASkB,KAAKA,CAAA,EAAG;IACfjB,MAAM,GAAG,IAAI;EACf;EAEA,OAAO;IACLK,IAAI;IACJG,KAAK;IACLG,MAAM;IACNC,UAAU;IACVF,SAAS;IACTH,MAAM;IACNR,aAAa,EAAEA,CAAA,KAAMA,aAAa;IAClCmB,iBAAiB,EAAEA,CAAA,KAAMrB,MAAM,CAACU,MAAM;IACtCS,KAAK;IACLC;EACF,CAAC;AACH;AAEA,MAAMb,sBAAsB,GAAG,2DAA2D","ignoreList":[]}
|
||||
117
node_modules/jsonrepair/lib/cjs/streaming/buffer/OutputBuffer.js
generated
vendored
Normal file
117
node_modules/jsonrepair/lib/cjs/streaming/buffer/OutputBuffer.js
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.createOutputBuffer = createOutputBuffer;
|
||||
var _stringUtils = require("../../utils/stringUtils.js");
|
||||
function createOutputBuffer(_ref) {
|
||||
let {
|
||||
write,
|
||||
chunkSize,
|
||||
bufferSize
|
||||
} = _ref;
|
||||
let buffer = '';
|
||||
let offset = 0;
|
||||
function flushChunks() {
|
||||
let minSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : bufferSize;
|
||||
while (buffer.length >= minSize + chunkSize) {
|
||||
const chunk = buffer.substring(0, chunkSize);
|
||||
write(chunk);
|
||||
offset += chunkSize;
|
||||
buffer = buffer.substring(chunkSize);
|
||||
}
|
||||
}
|
||||
function flush() {
|
||||
flushChunks(0);
|
||||
if (buffer.length > 0) {
|
||||
write(buffer);
|
||||
offset += buffer.length;
|
||||
buffer = '';
|
||||
}
|
||||
}
|
||||
function push(text) {
|
||||
buffer += text;
|
||||
flushChunks();
|
||||
}
|
||||
function unshift(text) {
|
||||
if (offset > 0) {
|
||||
throw new Error(`Cannot unshift: ${flushedMessage}`);
|
||||
}
|
||||
buffer = text + buffer;
|
||||
flushChunks();
|
||||
}
|
||||
function remove(start, end) {
|
||||
if (start < offset) {
|
||||
throw new Error(`Cannot remove: ${flushedMessage}`);
|
||||
}
|
||||
if (end !== undefined) {
|
||||
buffer = buffer.substring(0, start - offset) + buffer.substring(end - offset);
|
||||
} else {
|
||||
buffer = buffer.substring(0, start - offset);
|
||||
}
|
||||
}
|
||||
function insertAt(index, text) {
|
||||
if (index < offset) {
|
||||
throw new Error(`Cannot insert: ${flushedMessage}`);
|
||||
}
|
||||
buffer = buffer.substring(0, index - offset) + text + buffer.substring(index - offset);
|
||||
}
|
||||
function length() {
|
||||
return offset + buffer.length;
|
||||
}
|
||||
function stripLastOccurrence(textToStrip) {
|
||||
let stripRemainingText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
const bufferIndex = buffer.lastIndexOf(textToStrip);
|
||||
if (bufferIndex !== -1) {
|
||||
if (stripRemainingText) {
|
||||
buffer = buffer.substring(0, bufferIndex);
|
||||
} else {
|
||||
buffer = buffer.substring(0, bufferIndex) + buffer.substring(bufferIndex + textToStrip.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
function insertBeforeLastWhitespace(textToInsert) {
|
||||
let bufferIndex = buffer.length; // index relative to the start of the buffer, not taking `offset` into account
|
||||
|
||||
if (!(0, _stringUtils.isWhitespace)(buffer, bufferIndex - 1)) {
|
||||
// no trailing whitespaces
|
||||
push(textToInsert);
|
||||
return;
|
||||
}
|
||||
while ((0, _stringUtils.isWhitespace)(buffer, bufferIndex - 1)) {
|
||||
bufferIndex--;
|
||||
}
|
||||
if (bufferIndex <= 0) {
|
||||
throw new Error(`Cannot insert: ${flushedMessage}`);
|
||||
}
|
||||
buffer = buffer.substring(0, bufferIndex) + textToInsert + buffer.substring(bufferIndex);
|
||||
flushChunks();
|
||||
}
|
||||
function endsWithIgnoringWhitespace(char) {
|
||||
let i = buffer.length - 1;
|
||||
while (i > 0) {
|
||||
if (char === buffer.charAt(i)) {
|
||||
return true;
|
||||
}
|
||||
if (!(0, _stringUtils.isWhitespace)(buffer, i)) {
|
||||
return false;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
push,
|
||||
unshift,
|
||||
remove,
|
||||
insertAt,
|
||||
length,
|
||||
flush,
|
||||
stripLastOccurrence,
|
||||
insertBeforeLastWhitespace,
|
||||
endsWithIgnoringWhitespace
|
||||
};
|
||||
}
|
||||
const flushedMessage = 'start of the output is already flushed from the buffer';
|
||||
//# sourceMappingURL=OutputBuffer.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/streaming/buffer/OutputBuffer.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/streaming/buffer/OutputBuffer.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
824
node_modules/jsonrepair/lib/cjs/streaming/core.js
generated
vendored
Normal file
824
node_modules/jsonrepair/lib/cjs/streaming/core.js
generated
vendored
Normal file
@ -0,0 +1,824 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.jsonrepairCore = jsonrepairCore;
|
||||
var _JSONRepairError = require("../utils/JSONRepairError.js");
|
||||
var _stringUtils = require("../utils/stringUtils.js");
|
||||
var _InputBuffer = require("./buffer/InputBuffer.js");
|
||||
var _OutputBuffer = require("./buffer/OutputBuffer.js");
|
||||
var _stack = require("./stack.js");
|
||||
const controlCharacters = {
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
};
|
||||
|
||||
// map with all escape characters
|
||||
const escapeCharacters = {
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
b: '\b',
|
||||
f: '\f',
|
||||
n: '\n',
|
||||
r: '\r',
|
||||
t: '\t'
|
||||
// note that \u is handled separately in parseString()
|
||||
};
|
||||
function jsonrepairCore(_ref) {
|
||||
let {
|
||||
onData,
|
||||
bufferSize = 65536,
|
||||
chunkSize = 65536
|
||||
} = _ref;
|
||||
const input = (0, _InputBuffer.createInputBuffer)();
|
||||
const output = (0, _OutputBuffer.createOutputBuffer)({
|
||||
write: onData,
|
||||
bufferSize,
|
||||
chunkSize
|
||||
});
|
||||
let i = 0;
|
||||
let iFlushed = 0;
|
||||
const stack = (0, _stack.createStack)();
|
||||
function flushInputBuffer() {
|
||||
while (iFlushed < i - bufferSize - chunkSize) {
|
||||
iFlushed += chunkSize;
|
||||
input.flush(iFlushed);
|
||||
}
|
||||
}
|
||||
function transform(chunk) {
|
||||
input.push(chunk);
|
||||
while (i < input.currentLength() - bufferSize && parse()) {
|
||||
// loop until there is nothing more to process
|
||||
}
|
||||
flushInputBuffer();
|
||||
}
|
||||
function flush() {
|
||||
input.close();
|
||||
while (parse()) {
|
||||
// loop until there is nothing more to process
|
||||
}
|
||||
output.flush();
|
||||
}
|
||||
function parse() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
switch (stack.type) {
|
||||
case _stack.StackType.object:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case _stack.Caret.beforeKey:
|
||||
return skipEllipsis() || parseObjectKey() || parseUnexpectedColon() || parseRepairTrailingComma() || parseRepairObjectEndOrComma();
|
||||
case _stack.Caret.beforeValue:
|
||||
return parseValue() || parseRepairMissingObjectValue();
|
||||
case _stack.Caret.afterValue:
|
||||
return parseObjectComma() || parseObjectEnd() || parseRepairObjectEndOrComma();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case _stack.StackType.array:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case _stack.Caret.beforeValue:
|
||||
return skipEllipsis() || parseValue() || parseRepairTrailingComma() || parseRepairArrayEnd();
|
||||
case _stack.Caret.afterValue:
|
||||
return parseArrayComma() || parseArrayEnd() || parseRepairMissingComma() || parseRepairArrayEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case _stack.StackType.ndJson:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case _stack.Caret.beforeValue:
|
||||
return parseValue() || parseRepairTrailingComma();
|
||||
case _stack.Caret.afterValue:
|
||||
return parseArrayComma() || parseRepairMissingComma() || parseRepairNdJsonEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case _stack.StackType.functionCall:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case _stack.Caret.beforeValue:
|
||||
return parseValue();
|
||||
case _stack.Caret.afterValue:
|
||||
return parseFunctionCallEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case _stack.StackType.root:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case _stack.Caret.beforeValue:
|
||||
return parseRootStart();
|
||||
case _stack.Caret.afterValue:
|
||||
return parseRootEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function parseValue() {
|
||||
return parseObjectStart() || parseArrayStart() || parseString() || parseNumber() || parseKeywords() || parseRepairUnquotedString() || parseRepairRegex();
|
||||
}
|
||||
function parseObjectStart() {
|
||||
if (parseCharacter('{')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipEllipsis();
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (parseCharacter('}')) {
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return stack.push(_stack.StackType.object, _stack.Caret.beforeKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseArrayStart() {
|
||||
if (parseCharacter('[')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipEllipsis();
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (parseCharacter(']')) {
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return stack.push(_stack.StackType.array, _stack.Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairUnquotedString() {
|
||||
let j = i;
|
||||
if ((0, _stringUtils.isFunctionNameCharStart)(input.charAt(j))) {
|
||||
while (!input.isEnd(j) && (0, _stringUtils.isFunctionNameChar)(input.charAt(j))) {
|
||||
j++;
|
||||
}
|
||||
let k = j;
|
||||
while ((0, _stringUtils.isWhitespace)(input, k)) {
|
||||
k++;
|
||||
}
|
||||
if (input.charAt(k) === '(') {
|
||||
// repair a MongoDB function call like NumberLong("2")
|
||||
// repair a JSONP function call like callback({...});
|
||||
k++;
|
||||
i = k;
|
||||
return stack.push(_stack.StackType.functionCall, _stack.Caret.beforeValue);
|
||||
}
|
||||
}
|
||||
j = findNextDelimiter(false, j);
|
||||
if (j !== null) {
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (input.charAt(j - 1) === ':' && _stringUtils.regexUrlStart.test(input.substring(i, j + 2))) {
|
||||
while (!input.isEnd(j) && _stringUtils.regexUrlChar.test(input.charAt(j))) {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
const symbol = input.substring(i, j);
|
||||
i = j;
|
||||
output.push(symbol === 'undefined' ? 'null' : JSON.stringify(symbol));
|
||||
if (input.charAt(i) === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairRegex() {
|
||||
if (input.charAt(i) === '/') {
|
||||
const start = i;
|
||||
i++;
|
||||
while (!input.isEnd(i) && (input.charAt(i) !== '/' || input.charAt(i - 1) === '\\')) {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
output.push(`"${input.substring(start, i)}"`);
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
}
|
||||
function parseRepairMissingObjectValue() {
|
||||
// repair missing object value
|
||||
output.push('null');
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
function parseRepairTrailingComma() {
|
||||
// repair trailing comma
|
||||
if (output.endsWithIgnoringWhitespace(',')) {
|
||||
output.stripLastOccurrence(',');
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnexpectedColon() {
|
||||
if (input.charAt(i) === ':') {
|
||||
throwObjectKeyExpected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnexpectedEnd() {
|
||||
if (input.isEnd(i)) {
|
||||
throwUnexpectedEnd();
|
||||
} else {
|
||||
throwUnexpectedCharacter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectKey() {
|
||||
const parsedKey = parseString() || parseUnquotedKey();
|
||||
if (parsedKey) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (parseCharacter(':')) {
|
||||
// expect a value after the :
|
||||
return stack.update(_stack.Caret.beforeValue);
|
||||
}
|
||||
const truncatedText = input.isEnd(i);
|
||||
if ((0, _stringUtils.isStartOfValue)(input.charAt(i)) || truncatedText) {
|
||||
// repair missing colon
|
||||
output.insertBeforeLastWhitespace(':');
|
||||
return stack.update(_stack.Caret.beforeValue);
|
||||
}
|
||||
throwColonExpected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectComma() {
|
||||
if (parseCharacter(',')) {
|
||||
return stack.update(_stack.Caret.beforeKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectEnd() {
|
||||
if (parseCharacter('}')) {
|
||||
return stack.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairObjectEndOrComma() {
|
||||
// repair missing object end and trailing comma
|
||||
if (input.charAt(i) === '{') {
|
||||
output.stripLastOccurrence(',');
|
||||
output.insertBeforeLastWhitespace('}');
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
// repair missing comma
|
||||
if (!input.isEnd(i) && (0, _stringUtils.isStartOfValue)(input.charAt(i))) {
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
return stack.update(_stack.Caret.beforeKey);
|
||||
}
|
||||
|
||||
// repair missing closing brace
|
||||
output.insertBeforeLastWhitespace('}');
|
||||
return stack.pop();
|
||||
}
|
||||
function parseArrayComma() {
|
||||
if (parseCharacter(',')) {
|
||||
return stack.update(_stack.Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseArrayEnd() {
|
||||
if (parseCharacter(']')) {
|
||||
return stack.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairMissingComma() {
|
||||
// repair missing comma
|
||||
if (!input.isEnd(i) && (0, _stringUtils.isStartOfValue)(input.charAt(i))) {
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
return stack.update(_stack.Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairArrayEnd() {
|
||||
// repair missing closing bracket
|
||||
output.insertBeforeLastWhitespace(']');
|
||||
return stack.pop();
|
||||
}
|
||||
function parseRepairNdJsonEnd() {
|
||||
if (input.isEnd(i)) {
|
||||
output.push('\n]');
|
||||
return stack.pop();
|
||||
}
|
||||
throwUnexpectedEnd();
|
||||
return false; // just to make TS happy
|
||||
}
|
||||
function parseFunctionCallEnd() {
|
||||
if (skipCharacter(')')) {
|
||||
skipCharacter(';');
|
||||
}
|
||||
return stack.pop();
|
||||
}
|
||||
function parseRootStart() {
|
||||
parseMarkdownCodeBlock(['```', '[```', '{```']);
|
||||
return parseValue() || parseUnexpectedEnd();
|
||||
}
|
||||
function parseRootEnd() {
|
||||
parseMarkdownCodeBlock(['```', '```]', '```}']);
|
||||
const parsedComma = parseCharacter(',');
|
||||
parseWhitespaceAndSkipComments();
|
||||
if ((0, _stringUtils.isStartOfValue)(input.charAt(i)) && (output.endsWithIgnoringWhitespace(',') || output.endsWithIgnoringWhitespace('\n'))) {
|
||||
// start of a new value after end of the root level object: looks like
|
||||
// newline delimited JSON -> turn into a root level array
|
||||
if (!parsedComma) {
|
||||
// repair missing comma
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
}
|
||||
output.unshift('[\n');
|
||||
return stack.push(_stack.StackType.ndJson, _stack.Caret.beforeValue);
|
||||
}
|
||||
if (parsedComma) {
|
||||
// repair: remove trailing comma
|
||||
output.stripLastOccurrence(',');
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
|
||||
// repair redundant end braces and brackets
|
||||
while (input.charAt(i) === '}' || input.charAt(i) === ']') {
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (!input.isEnd(i)) {
|
||||
throwUnexpectedCharacter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseWhitespaceAndSkipComments() {
|
||||
let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
const start = i;
|
||||
let changed = parseWhitespace(skipNewline);
|
||||
do {
|
||||
changed = parseComment();
|
||||
if (changed) {
|
||||
changed = parseWhitespace(skipNewline);
|
||||
}
|
||||
} while (changed);
|
||||
return i > start;
|
||||
}
|
||||
function parseWhitespace(skipNewline) {
|
||||
const _isWhiteSpace = skipNewline ? _stringUtils.isWhitespace : _stringUtils.isWhitespaceExceptNewline;
|
||||
let whitespace = '';
|
||||
while (true) {
|
||||
if (_isWhiteSpace(input, i)) {
|
||||
whitespace += input.charAt(i);
|
||||
i++;
|
||||
} else if ((0, _stringUtils.isSpecialWhitespace)(input, i)) {
|
||||
// repair special whitespace
|
||||
whitespace += ' ';
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace.length > 0) {
|
||||
output.push(whitespace);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseComment() {
|
||||
// find a block comment '/* ... */'
|
||||
if (input.charAt(i) === '/' && input.charAt(i + 1) === '*') {
|
||||
// repair block comment by skipping it
|
||||
while (!input.isEnd(i) && !atEndOfBlockComment(i)) {
|
||||
i++;
|
||||
}
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find a line comment '// ...'
|
||||
if (input.charAt(i) === '/' && input.charAt(i + 1) === '/') {
|
||||
// repair line comment by skipping it
|
||||
while (!input.isEnd(i) && input.charAt(i) !== '\n') {
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseMarkdownCodeBlock(blocks) {
|
||||
// find and skip over a Markdown fenced code block:
|
||||
// ``` ... ```
|
||||
// or
|
||||
// ```json ... ```
|
||||
if (skipMarkdownCodeBlock(blocks)) {
|
||||
if ((0, _stringUtils.isFunctionNameCharStart)(input.charAt(i))) {
|
||||
// strip the optional language specifier like "json"
|
||||
while (!input.isEnd(i) && (0, _stringUtils.isFunctionNameChar)(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipMarkdownCodeBlock(blocks) {
|
||||
for (const block of blocks) {
|
||||
const end = i + block.length;
|
||||
if (input.substring(i, end) === block) {
|
||||
i = end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseCharacter(char) {
|
||||
if (input.charAt(i) === char) {
|
||||
output.push(input.charAt(i));
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipCharacter(char) {
|
||||
if (input.charAt(i) === char) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipEscapeCharacter() {
|
||||
return skipCharacter('\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ellipsis like "[1,2,3,...]" or "[1,2,3,...,9]" or "[...,7,8,9]"
|
||||
* or a similar construct in objects.
|
||||
*/
|
||||
function skipEllipsis() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (input.charAt(i) === '.' && input.charAt(i + 1) === '.' && input.charAt(i + 2) === '.') {
|
||||
// repair: remove the ellipsis (three dots) and optionally a comma
|
||||
i += 3;
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipCharacter(',');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string enclosed by double quotes "...". Can contain escaped quotes
|
||||
* Repair strings enclosed in single quotes or special quotes
|
||||
* Repair an escaped string
|
||||
*
|
||||
* The function can run in two stages:
|
||||
* - First, it assumes the string has a valid end quote
|
||||
* - If it turns out that the string does not have a valid end quote followed
|
||||
* by a delimiter (which should be the case), the function runs again in a
|
||||
* more conservative way, stopping the string at the first next delimiter
|
||||
* and fixing the string by inserting a quote there, or stopping at a
|
||||
* stop index detected in the first iteration.
|
||||
*/
|
||||
function parseString() {
|
||||
let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
|
||||
let skipEscapeChars = input.charAt(i) === '\\';
|
||||
if (skipEscapeChars) {
|
||||
// repair: remove the first escape character
|
||||
i++;
|
||||
skipEscapeChars = true;
|
||||
}
|
||||
if ((0, _stringUtils.isQuote)(input.charAt(i))) {
|
||||
// double quotes are correct JSON,
|
||||
// single quotes come from JavaScript for example, we assume it will have a correct single end quote too
|
||||
// otherwise, we will match any double-quote-like start with a double-quote-like end,
|
||||
// or any single-quote-like start with a single-quote-like end
|
||||
const isEndQuote = (0, _stringUtils.isDoubleQuote)(input.charAt(i)) ? _stringUtils.isDoubleQuote : (0, _stringUtils.isSingleQuote)(input.charAt(i)) ? _stringUtils.isSingleQuote : (0, _stringUtils.isSingleQuoteLike)(input.charAt(i)) ? _stringUtils.isSingleQuoteLike : _stringUtils.isDoubleQuoteLike;
|
||||
const iBefore = i;
|
||||
const oBefore = output.length();
|
||||
output.push('"');
|
||||
i++;
|
||||
while (true) {
|
||||
if (input.isEnd(i)) {
|
||||
// end of text, we have a missing quote somewhere
|
||||
|
||||
const iPrev = prevNonWhitespaceIndex(i - 1);
|
||||
if (!stopAtDelimiter && (0, _stringUtils.isDelimiter)(input.charAt(iPrev))) {
|
||||
// if the text ends with a delimiter, like ["hello],
|
||||
// so the missing end quote should be inserted before this delimiter
|
||||
// retry parsing the string, stopping at the first next delimiter
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
if (i === stopAtIndex) {
|
||||
// use the stop index detected in the first iteration, and repair end quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
if (isEndQuote(input.charAt(i))) {
|
||||
// end quote
|
||||
// let us check what is before and after the quote to verify whether this is a legit end quote
|
||||
const iQuote = i;
|
||||
const oQuote = output.length();
|
||||
output.push('"');
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments(false);
|
||||
if (stopAtDelimiter || input.isEnd(i) || (0, _stringUtils.isDelimiter)(input.charAt(i)) || (0, _stringUtils.isQuote)(input.charAt(i)) || (0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
// The quote is followed by the end of the text, a delimiter, or a next value
|
||||
// so the quote is indeed the end of the string
|
||||
parseConcatenatedString();
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);
|
||||
const prevChar = input.charAt(iPrevChar);
|
||||
if (prevChar === ',') {
|
||||
// A comma followed by a quote, like '{"a":"b,c,"d":"e"}'.
|
||||
// We assume that the quote is a start quote, and that the end quote
|
||||
// should have been located right before the comma but is missing.
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(false, iPrevChar);
|
||||
}
|
||||
if ((0, _stringUtils.isDelimiter)(prevChar)) {
|
||||
// This is not the right end quote: it is preceded by a delimiter,
|
||||
// and NOT followed by a delimiter. So, there is an end quote missing
|
||||
// parse the string again and then stop at the first next delimiter
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// revert to right after the quote but before any whitespace, and continue parsing the string
|
||||
output.remove(oQuote + 1);
|
||||
i = iQuote + 1;
|
||||
|
||||
// repair unescaped quote
|
||||
output.insertAt(oQuote, '\\');
|
||||
} else if (stopAtDelimiter && (0, _stringUtils.isUnquotedStringDelimiter)(input.charAt(i))) {
|
||||
// we're in the mode to stop the string at the first delimiter
|
||||
// because there is an end quote missing
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (input.charAt(i - 1) === ':' && _stringUtils.regexUrlStart.test(input.substring(iBefore + 1, i + 2))) {
|
||||
while (!input.isEnd(i) && _stringUtils.regexUrlChar.test(input.charAt(i))) {
|
||||
output.push(input.charAt(i));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
parseConcatenatedString();
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
} else if (input.charAt(i) === '\\') {
|
||||
// handle escaped content like \n or \u2605
|
||||
const char = input.charAt(i + 1);
|
||||
const escapeChar = escapeCharacters[char];
|
||||
if (escapeChar !== undefined) {
|
||||
output.push(input.substring(i, i + 2));
|
||||
i += 2;
|
||||
} else if (char === 'u') {
|
||||
let j = 2;
|
||||
while (j < 6 && (0, _stringUtils.isHex)(input.charAt(i + j))) {
|
||||
j++;
|
||||
}
|
||||
if (j === 6) {
|
||||
output.push(input.substring(i, i + 6));
|
||||
i += 6;
|
||||
} else if (input.isEnd(i + j)) {
|
||||
// repair invalid or truncated unicode char at the end of the text
|
||||
// by removing the unicode char and ending the string here
|
||||
i += j;
|
||||
} else {
|
||||
throwInvalidUnicodeCharacter();
|
||||
}
|
||||
} else {
|
||||
// repair invalid escape character: remove it
|
||||
output.push(char);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
// handle regular characters
|
||||
const char = input.charAt(i);
|
||||
if (char === '"' && input.charAt(i - 1) !== '\\') {
|
||||
// repair unescaped double quote
|
||||
output.push(`\\${char}`);
|
||||
i++;
|
||||
} else if ((0, _stringUtils.isControlCharacter)(char)) {
|
||||
// unescaped control character
|
||||
output.push(controlCharacters[char]);
|
||||
i++;
|
||||
} else {
|
||||
if (!(0, _stringUtils.isValidStringCharacter)(char)) {
|
||||
throwInvalidCharacter(char);
|
||||
}
|
||||
output.push(char);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (skipEscapeChars) {
|
||||
// repair: skipped escape character (nothing to do)
|
||||
skipEscapeCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair concatenated strings like "hello" + "world", change this into "helloworld"
|
||||
*/
|
||||
function parseConcatenatedString() {
|
||||
let parsed = false;
|
||||
parseWhitespaceAndSkipComments();
|
||||
while (input.charAt(i) === '+') {
|
||||
parsed = true;
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: remove the end quote of the first string
|
||||
output.stripLastOccurrence('"', true);
|
||||
const start = output.length();
|
||||
const parsedStr = parseString();
|
||||
if (parsedStr) {
|
||||
// repair: remove the start quote of the second string
|
||||
output.remove(start, start + 1);
|
||||
} else {
|
||||
// repair: remove the + because it is not followed by a string
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number like 2.4 or 2.4e6
|
||||
*/
|
||||
function parseNumber() {
|
||||
const start = i;
|
||||
if (input.charAt(i) === '-') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in JSON leading zeros like "00789" are not allowed.
|
||||
// We will allow all leading zeros here though and at the end of parseNumber
|
||||
// check against trailing zeros and repair that if needed.
|
||||
// Leading zeros can have meaning, so we should not clear them.
|
||||
while ((0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
if (input.charAt(i) === '.') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while ((0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (input.charAt(i) === 'e' || input.charAt(i) === 'E') {
|
||||
i++;
|
||||
if (input.charAt(i) === '-' || input.charAt(i) === '+') {
|
||||
i++;
|
||||
}
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
if (!(0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while ((0, _stringUtils.isDigit)(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not at the end of the number by this point, allow this to be parsed as another type
|
||||
if (!atEndOfNumber()) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
if (i > start) {
|
||||
// repair a number with leading zeros like "00789"
|
||||
const num = input.substring(start, i);
|
||||
const hasInvalidLeadingZero = /^0\d/.test(num);
|
||||
output.push(hasInvalidLeadingZero ? `"${num}"` : num);
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keywords true, false, null
|
||||
* Repair Python keywords True, False, None
|
||||
*/
|
||||
function parseKeywords() {
|
||||
return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||
|
||||
// repair Python keywords True, False, None
|
||||
parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');
|
||||
}
|
||||
function parseKeyword(name, value) {
|
||||
if (input.substring(i, i + name.length) === name) {
|
||||
output.push(value);
|
||||
i += name.length;
|
||||
return stack.update(_stack.Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnquotedKey() {
|
||||
let end = findNextDelimiter(true, i);
|
||||
if (end !== null) {
|
||||
// first, go back to prevent getting trailing whitespaces in the string
|
||||
while ((0, _stringUtils.isWhitespace)(input, end - 1) && end > i) {
|
||||
end--;
|
||||
}
|
||||
const symbol = input.substring(i, end);
|
||||
output.push(JSON.stringify(symbol));
|
||||
i = end;
|
||||
if (input.charAt(i) === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return stack.update(_stack.Caret.afterValue); // we do not have a state Caret.afterKey, therefore we use afterValue here
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function findNextDelimiter(isKey, start) {
|
||||
// note that the symbol can end with whitespaces: we stop at the next delimiter
|
||||
// also, note that we allow strings to contain a slash / in order to support repairing regular expressions
|
||||
let j = start;
|
||||
while (!input.isEnd(j) && !(0, _stringUtils.isUnquotedStringDelimiter)(input.charAt(j)) && !(0, _stringUtils.isQuote)(input.charAt(j)) && (!isKey || input.charAt(j) !== ':')) {
|
||||
j++;
|
||||
}
|
||||
return j > i ? j : null;
|
||||
}
|
||||
function prevNonWhitespaceIndex(start) {
|
||||
let prev = start;
|
||||
while (prev > 0 && (0, _stringUtils.isWhitespace)(input, prev)) {
|
||||
prev--;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
function atEndOfNumber() {
|
||||
return input.isEnd(i) || (0, _stringUtils.isDelimiter)(input.charAt(i)) || (0, _stringUtils.isWhitespace)(input, i);
|
||||
}
|
||||
function repairNumberEndingWithNumericSymbol(start) {
|
||||
// repair numbers cut off at the end
|
||||
// this will only be called when we end after a '.', '-', or 'e' and does not
|
||||
// change the number more than it needs to make it valid JSON
|
||||
output.push(`${input.substring(start, i)}0`);
|
||||
}
|
||||
function throwInvalidCharacter(char) {
|
||||
throw new _JSONRepairError.JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);
|
||||
}
|
||||
function throwUnexpectedCharacter() {
|
||||
throw new _JSONRepairError.JSONRepairError(`Unexpected character ${JSON.stringify(input.charAt(i))}`, i);
|
||||
}
|
||||
function throwUnexpectedEnd() {
|
||||
throw new _JSONRepairError.JSONRepairError('Unexpected end of json string', i);
|
||||
}
|
||||
function throwObjectKeyExpected() {
|
||||
throw new _JSONRepairError.JSONRepairError('Object key expected', i);
|
||||
}
|
||||
function throwColonExpected() {
|
||||
throw new _JSONRepairError.JSONRepairError('Colon expected', i);
|
||||
}
|
||||
function throwInvalidUnicodeCharacter() {
|
||||
const chars = input.substring(i, i + 6);
|
||||
throw new _JSONRepairError.JSONRepairError(`Invalid unicode character "${chars}"`, i);
|
||||
}
|
||||
function atEndOfBlockComment(i) {
|
||||
return input.charAt(i) === '*' && input.charAt(i + 1) === '/';
|
||||
}
|
||||
return {
|
||||
transform,
|
||||
flush
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=core.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/streaming/core.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/streaming/core.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
51
node_modules/jsonrepair/lib/cjs/streaming/stack.js
generated
vendored
Normal file
51
node_modules/jsonrepair/lib/cjs/streaming/stack.js
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.StackType = exports.Caret = void 0;
|
||||
exports.createStack = createStack;
|
||||
let Caret = exports.Caret = /*#__PURE__*/function (Caret) {
|
||||
Caret["beforeValue"] = "beforeValue";
|
||||
Caret["afterValue"] = "afterValue";
|
||||
Caret["beforeKey"] = "beforeKey";
|
||||
return Caret;
|
||||
}({});
|
||||
let StackType = exports.StackType = /*#__PURE__*/function (StackType) {
|
||||
StackType["root"] = "root";
|
||||
StackType["object"] = "object";
|
||||
StackType["array"] = "array";
|
||||
StackType["ndJson"] = "ndJson";
|
||||
StackType["functionCall"] = "dataType";
|
||||
return StackType;
|
||||
}({});
|
||||
function createStack() {
|
||||
const stack = [StackType.root];
|
||||
let caret = Caret.beforeValue;
|
||||
return {
|
||||
get type() {
|
||||
return last(stack);
|
||||
},
|
||||
get caret() {
|
||||
return caret;
|
||||
},
|
||||
pop() {
|
||||
stack.pop();
|
||||
caret = Caret.afterValue;
|
||||
return true;
|
||||
},
|
||||
push(type, newCaret) {
|
||||
stack.push(type);
|
||||
caret = newCaret;
|
||||
return true;
|
||||
},
|
||||
update(newCaret) {
|
||||
caret = newCaret;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
function last(array) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
//# sourceMappingURL=stack.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/streaming/stack.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/streaming/stack.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stack.js","names":["Caret","exports","StackType","createStack","stack","root","caret","beforeValue","type","last","pop","afterValue","push","newCaret","update","array","length"],"sources":["../../../src/streaming/stack.ts"],"sourcesContent":["export enum Caret {\n beforeValue = 'beforeValue',\n afterValue = 'afterValue',\n beforeKey = 'beforeKey'\n}\n\nexport enum StackType {\n root = 'root',\n object = 'object',\n array = 'array',\n ndJson = 'ndJson',\n functionCall = 'dataType'\n}\n\nexport function createStack() {\n const stack: StackType[] = [StackType.root]\n let caret = Caret.beforeValue\n\n return {\n get type() {\n return last(stack)\n },\n\n get caret() {\n return caret\n },\n\n pop(): true {\n stack.pop()\n caret = Caret.afterValue\n\n return true\n },\n\n push(type: StackType, newCaret: Caret): true {\n stack.push(type)\n caret = newCaret\n\n return true\n },\n\n update(newCaret: Caret): true {\n caret = newCaret\n\n return true\n }\n }\n}\n\nfunction last<T>(array: T[]): T | undefined {\n return array[array.length - 1]\n}\n"],"mappings":";;;;;;;IAAYA,KAAK,GAAAC,OAAA,CAAAD,KAAA,0BAALA,KAAK;EAALA,KAAK;EAALA,KAAK;EAALA,KAAK;EAAA,OAALA,KAAK;AAAA;AAAA,IAMLE,SAAS,GAAAD,OAAA,CAAAC,SAAA,0BAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAAA,OAATA,SAAS;AAAA;AAQd,SAASC,WAAWA,CAAA,EAAG;EAC5B,MAAMC,KAAkB,GAAG,CAACF,SAAS,CAACG,IAAI,CAAC;EAC3C,IAAIC,KAAK,GAAGN,KAAK,CAACO,WAAW;EAE7B,OAAO;IACL,IAAIC,IAAIA,CAAA,EAAG;MACT,OAAOC,IAAI,CAACL,KAAK,CAAC;IACpB,CAAC;IAED,IAAIE,KAAKA,CAAA,EAAG;MACV,OAAOA,KAAK;IACd,CAAC;IAEDI,GAAGA,CAAA,EAAS;MACVN,KAAK,CAACM,GAAG,CAAC,CAAC;MACXJ,KAAK,GAAGN,KAAK,CAACW,UAAU;MAExB,OAAO,IAAI;IACb,CAAC;IAEDC,IAAIA,CAACJ,IAAe,EAAEK,QAAe,EAAQ;MAC3CT,KAAK,CAACQ,IAAI,CAACJ,IAAI,CAAC;MAChBF,KAAK,GAAGO,QAAQ;MAEhB,OAAO,IAAI;IACb,CAAC;IAEDC,MAAMA,CAACD,QAAe,EAAQ;MAC5BP,KAAK,GAAGO,QAAQ;MAEhB,OAAO,IAAI;IACb;EACF,CAAC;AACH;AAEA,SAASJ,IAAIA,CAAIM,KAAU,EAAiB;EAC1C,OAAOA,KAAK,CAACA,KAAK,CAACC,MAAM,GAAG,CAAC,CAAC;AAChC","ignoreList":[]}
|
||||
37
node_modules/jsonrepair/lib/cjs/streaming/stream.js
generated
vendored
Normal file
37
node_modules/jsonrepair/lib/cjs/streaming/stream.js
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.jsonrepairTransform = jsonrepairTransform;
|
||||
var _nodeStream = require("node:stream");
|
||||
var _core = require("./core.js");
|
||||
function jsonrepairTransform(options) {
|
||||
const repair = (0, _core.jsonrepairCore)({
|
||||
onData: chunk => transform.push(chunk),
|
||||
bufferSize: options?.bufferSize,
|
||||
chunkSize: options?.chunkSize
|
||||
});
|
||||
const transform = new _nodeStream.Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
try {
|
||||
repair.transform(chunk.toString());
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
} finally {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
flush(callback) {
|
||||
try {
|
||||
repair.flush();
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
} finally {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
return transform;
|
||||
}
|
||||
//# sourceMappingURL=stream.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/streaming/stream.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/streaming/stream.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.js","names":["_nodeStream","require","_core","jsonrepairTransform","options","repair","jsonrepairCore","onData","chunk","transform","push","bufferSize","chunkSize","Transform","_encoding","callback","toString","err","emit","flush"],"sources":["../../../src/streaming/stream.ts"],"sourcesContent":["import { Transform } from 'node:stream'\nimport { jsonrepairCore } from './core.js'\n\nexport interface JsonRepairTransformOptions {\n chunkSize?: number\n bufferSize?: number\n}\n\nexport function jsonrepairTransform(options?: JsonRepairTransformOptions): Transform {\n const repair = jsonrepairCore({\n onData: (chunk) => transform.push(chunk),\n bufferSize: options?.bufferSize,\n chunkSize: options?.chunkSize\n })\n\n const transform = new Transform({\n transform(chunk, _encoding, callback) {\n try {\n repair.transform(chunk.toString())\n } catch (err) {\n this.emit('error', err)\n } finally {\n callback()\n }\n },\n\n flush(callback) {\n try {\n repair.flush()\n } catch (err) {\n this.emit('error', err)\n } finally {\n callback()\n }\n }\n })\n\n return transform\n}\n"],"mappings":";;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AACA,IAAAC,KAAA,GAAAD,OAAA;AAOO,SAASE,mBAAmBA,CAACC,OAAoC,EAAa;EACnF,MAAMC,MAAM,GAAG,IAAAC,oBAAc,EAAC;IAC5BC,MAAM,EAAGC,KAAK,IAAKC,SAAS,CAACC,IAAI,CAACF,KAAK,CAAC;IACxCG,UAAU,EAAEP,OAAO,EAAEO,UAAU;IAC/BC,SAAS,EAAER,OAAO,EAAEQ;EACtB,CAAC,CAAC;EAEF,MAAMH,SAAS,GAAG,IAAII,qBAAS,CAAC;IAC9BJ,SAASA,CAACD,KAAK,EAAEM,SAAS,EAAEC,QAAQ,EAAE;MACpC,IAAI;QACFV,MAAM,CAACI,SAAS,CAACD,KAAK,CAACQ,QAAQ,CAAC,CAAC,CAAC;MACpC,CAAC,CAAC,OAAOC,GAAG,EAAE;QACZ,IAAI,CAACC,IAAI,CAAC,OAAO,EAAED,GAAG,CAAC;MACzB,CAAC,SAAS;QACRF,QAAQ,CAAC,CAAC;MACZ;IACF,CAAC;IAEDI,KAAKA,CAACJ,QAAQ,EAAE;MACd,IAAI;QACFV,MAAM,CAACc,KAAK,CAAC,CAAC;MAChB,CAAC,CAAC,OAAOF,GAAG,EAAE;QACZ,IAAI,CAACC,IAAI,CAAC,OAAO,EAAED,GAAG,CAAC;MACzB,CAAC,SAAS;QACRF,QAAQ,CAAC,CAAC;MACZ;IACF;EACF,CAAC,CAAC;EAEF,OAAON,SAAS;AAClB","ignoreList":[]}
|
||||
14
node_modules/jsonrepair/lib/cjs/utils/JSONRepairError.js
generated
vendored
Normal file
14
node_modules/jsonrepair/lib/cjs/utils/JSONRepairError.js
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.JSONRepairError = void 0;
|
||||
class JSONRepairError extends Error {
|
||||
constructor(message, position) {
|
||||
super(`${message} at position ${position}`);
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
exports.JSONRepairError = JSONRepairError;
|
||||
//# sourceMappingURL=JSONRepairError.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/utils/JSONRepairError.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/utils/JSONRepairError.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"JSONRepairError.js","names":["JSONRepairError","Error","constructor","message","position","exports"],"sources":["../../../src/utils/JSONRepairError.ts"],"sourcesContent":["export class JSONRepairError extends Error {\n position: number\n\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`)\n\n this.position = position\n }\n}\n"],"mappings":";;;;;;AAAO,MAAMA,eAAe,SAASC,KAAK,CAAC;EAGzCC,WAAWA,CAACC,OAAe,EAAEC,QAAgB,EAAE;IAC7C,KAAK,CAAC,GAAGD,OAAO,gBAAgBC,QAAQ,EAAE,CAAC;IAE3C,IAAI,CAACA,QAAQ,GAAGA,QAAQ;EAC1B;AACF;AAACC,OAAA,CAAAL,eAAA,GAAAA,eAAA","ignoreList":[]}
|
||||
174
node_modules/jsonrepair/lib/cjs/utils/stringUtils.js
generated
vendored
Normal file
174
node_modules/jsonrepair/lib/cjs/utils/stringUtils.js
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.endsWithCommaOrNewline = endsWithCommaOrNewline;
|
||||
exports.insertBeforeLastWhitespace = insertBeforeLastWhitespace;
|
||||
exports.isControlCharacter = isControlCharacter;
|
||||
exports.isDelimiter = isDelimiter;
|
||||
exports.isDigit = isDigit;
|
||||
exports.isDoubleQuote = isDoubleQuote;
|
||||
exports.isDoubleQuoteLike = isDoubleQuoteLike;
|
||||
exports.isFunctionNameChar = isFunctionNameChar;
|
||||
exports.isFunctionNameCharStart = isFunctionNameCharStart;
|
||||
exports.isHex = isHex;
|
||||
exports.isQuote = isQuote;
|
||||
exports.isSingleQuote = isSingleQuote;
|
||||
exports.isSingleQuoteLike = isSingleQuoteLike;
|
||||
exports.isSpecialWhitespace = isSpecialWhitespace;
|
||||
exports.isStartOfValue = isStartOfValue;
|
||||
exports.isUnquotedStringDelimiter = isUnquotedStringDelimiter;
|
||||
exports.isValidStringCharacter = isValidStringCharacter;
|
||||
exports.isWhitespace = isWhitespace;
|
||||
exports.isWhitespaceExceptNewline = isWhitespaceExceptNewline;
|
||||
exports.regexUrlStart = exports.regexUrlChar = void 0;
|
||||
exports.removeAtIndex = removeAtIndex;
|
||||
exports.stripLastOccurrence = stripLastOccurrence;
|
||||
const codeSpace = 0x20; // " "
|
||||
const codeNewline = 0xa; // "\n"
|
||||
const codeTab = 0x9; // "\t"
|
||||
const codeReturn = 0xd; // "\r"
|
||||
const codeNonBreakingSpace = 0xa0;
|
||||
const codeEnQuad = 0x2000;
|
||||
const codeHairSpace = 0x200a;
|
||||
const codeNarrowNoBreakSpace = 0x202f;
|
||||
const codeMediumMathematicalSpace = 0x205f;
|
||||
const codeIdeographicSpace = 0x3000;
|
||||
function isHex(char) {
|
||||
return /^[0-9A-Fa-f]$/.test(char);
|
||||
}
|
||||
function isDigit(char) {
|
||||
return char >= '0' && char <= '9';
|
||||
}
|
||||
function isValidStringCharacter(char) {
|
||||
// note that the valid range is between \u{0020} and \u{10ffff},
|
||||
// but in JavaScript it is not possible to create a code point larger than
|
||||
// \u{10ffff}, so there is no need to test for that here.
|
||||
return char >= '\u0020';
|
||||
}
|
||||
function isDelimiter(char) {
|
||||
return ',:[]/{}()\n+'.includes(char);
|
||||
}
|
||||
function isFunctionNameCharStart(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$';
|
||||
}
|
||||
function isFunctionNameChar(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$' || char >= '0' && char <= '9';
|
||||
}
|
||||
|
||||
// matches "https://" and other schemas
|
||||
const regexUrlStart = exports.regexUrlStart = /^(http|https|ftp|mailto|file|data|irc):\/\/$/;
|
||||
|
||||
// matches all valid URL characters EXCEPT "[", "]", and ",", since that are important JSON delimiters
|
||||
const regexUrlChar = exports.regexUrlChar = /^[A-Za-z0-9-._~:/?#@!$&'()*+;=]$/;
|
||||
function isUnquotedStringDelimiter(char) {
|
||||
return ',[]/{}\n+'.includes(char);
|
||||
}
|
||||
function isStartOfValue(char) {
|
||||
return isQuote(char) || regexStartOfValue.test(char);
|
||||
}
|
||||
|
||||
// alpha, number, minus, or opening bracket or brace
|
||||
const regexStartOfValue = /^[[{\w-]$/;
|
||||
function isControlCharacter(char) {
|
||||
return char === '\n' || char === '\r' || char === '\t' || char === '\b' || char === '\f';
|
||||
}
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space, tab, or
|
||||
* newline
|
||||
*/
|
||||
function isWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeNewline || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space or tab,
|
||||
* but NOT a newline
|
||||
*/
|
||||
function isWhitespaceExceptNewline(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a special whitespace character, some
|
||||
* unicode variant
|
||||
*/
|
||||
function isSpecialWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeNonBreakingSpace || code >= codeEnQuad && code <= codeHairSpace || code === codeNarrowNoBreakSpace || code === codeMediumMathematicalSpace || code === codeIdeographicSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a quote or double quote character.
|
||||
* Also tests for special variants of quotes.
|
||||
*/
|
||||
function isQuote(char) {
|
||||
// the first check double quotes, since that occurs most often
|
||||
return isDoubleQuoteLike(char) || isSingleQuoteLike(char);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Also tests for special variants of double quotes.
|
||||
*/
|
||||
function isDoubleQuoteLike(char) {
|
||||
return char === '"' || char === '\u201c' || char === '\u201d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Does NOT test for special variants of double quotes.
|
||||
*/
|
||||
function isDoubleQuote(char) {
|
||||
return char === '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Also tests for special variants of single quotes.
|
||||
*/
|
||||
function isSingleQuoteLike(char) {
|
||||
return char === "'" || char === '\u2018' || char === '\u2019' || char === '\u0060' || char === '\u00b4';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Does NOT test for special variants of single quotes.
|
||||
*/
|
||||
function isSingleQuote(char) {
|
||||
return char === "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip last occurrence of textToStrip from text
|
||||
*/
|
||||
function stripLastOccurrence(text, textToStrip) {
|
||||
let stripRemainingText = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||||
const index = text.lastIndexOf(textToStrip);
|
||||
return index !== -1 ? text.substring(0, index) + (stripRemainingText ? '' : text.substring(index + 1)) : text;
|
||||
}
|
||||
function insertBeforeLastWhitespace(text, textToInsert) {
|
||||
let index = text.length;
|
||||
if (!isWhitespace(text, index - 1)) {
|
||||
// no trailing whitespaces
|
||||
return text + textToInsert;
|
||||
}
|
||||
while (isWhitespace(text, index - 1)) {
|
||||
index--;
|
||||
}
|
||||
return text.substring(0, index) + textToInsert + text.substring(index);
|
||||
}
|
||||
function removeAtIndex(text, start, count) {
|
||||
return text.substring(0, start) + text.substring(start + count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a string ends with a newline or comma character and optional whitespace
|
||||
*/
|
||||
function endsWithCommaOrNewline(text) {
|
||||
return /[,\n][ \t\r]*$/.test(text);
|
||||
}
|
||||
//# sourceMappingURL=stringUtils.js.map
|
||||
1
node_modules/jsonrepair/lib/cjs/utils/stringUtils.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/cjs/utils/stringUtils.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
4
node_modules/jsonrepair/lib/esm/index.js
generated
vendored
Normal file
4
node_modules/jsonrepair/lib/esm/index.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Cross-platform, non-streaming JavaScript API
|
||||
export { jsonrepair } from './regular/jsonrepair.js';
|
||||
export { JSONRepairError } from './utils/JSONRepairError.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/index.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/index.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":["jsonrepair","JSONRepairError"],"sources":["../../src/index.ts"],"sourcesContent":["// Cross-platform, non-streaming JavaScript API\nexport { jsonrepair } from './regular/jsonrepair.js'\nexport { JSONRepairError } from './utils/JSONRepairError.js'\n"],"mappings":"AAAA;AACA,SAASA,UAAU,QAAQ,yBAAyB;AACpD,SAASC,eAAe,QAAQ,4BAA4B","ignoreList":[]}
|
||||
740
node_modules/jsonrepair/lib/esm/regular/jsonrepair.js
generated
vendored
Normal file
740
node_modules/jsonrepair/lib/esm/regular/jsonrepair.js
generated
vendored
Normal file
@ -0,0 +1,740 @@
|
||||
import { JSONRepairError } from '../utils/JSONRepairError.js';
|
||||
import { endsWithCommaOrNewline, insertBeforeLastWhitespace, isControlCharacter, isDelimiter, isDigit, isDoubleQuote, isDoubleQuoteLike, isFunctionNameChar, isFunctionNameCharStart, isHex, isQuote, isSingleQuote, isSingleQuoteLike, isSpecialWhitespace, isStartOfValue, isUnquotedStringDelimiter, isValidStringCharacter, isWhitespace, isWhitespaceExceptNewline, regexUrlChar, regexUrlStart, removeAtIndex, stripLastOccurrence } from '../utils/stringUtils.js';
|
||||
const controlCharacters = {
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
};
|
||||
|
||||
// map with all escape characters
|
||||
const escapeCharacters = {
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
b: '\b',
|
||||
f: '\f',
|
||||
n: '\n',
|
||||
r: '\r',
|
||||
t: '\t'
|
||||
// note that \u is handled separately in parseString()
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair a string containing an invalid JSON document.
|
||||
* For example changes JavaScript notation into JSON notation.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* try {
|
||||
* const json = "{name: 'John'}"
|
||||
* const repaired = jsonrepair(json)
|
||||
* console.log(repaired)
|
||||
* // '{"name": "John"}'
|
||||
* } catch (err) {
|
||||
* console.error(err)
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export function jsonrepair(text) {
|
||||
let i = 0; // current index in text
|
||||
let output = ''; // generated output
|
||||
|
||||
parseMarkdownCodeBlock(['```', '[```', '{```']);
|
||||
const processed = parseValue();
|
||||
if (!processed) {
|
||||
throwUnexpectedEnd();
|
||||
}
|
||||
parseMarkdownCodeBlock(['```', '```]', '```}']);
|
||||
const processedComma = parseCharacter(',');
|
||||
if (processedComma) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (isStartOfValue(text[i]) && endsWithCommaOrNewline(output)) {
|
||||
// start of a new value after end of the root level object: looks like
|
||||
// newline delimited JSON -> turn into a root level array
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
parseNewlineDelimitedJSON();
|
||||
} else if (processedComma) {
|
||||
// repair: remove trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
}
|
||||
|
||||
// repair redundant end quotes
|
||||
while (text[i] === '}' || text[i] === ']') {
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (i >= text.length) {
|
||||
// reached the end of the document properly
|
||||
return output;
|
||||
}
|
||||
throwUnexpectedCharacter();
|
||||
function parseValue() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processed = parseObject() || parseArray() || parseString() || parseNumber() || parseKeywords() || parseUnquotedString(false) || parseRegex();
|
||||
parseWhitespaceAndSkipComments();
|
||||
return processed;
|
||||
}
|
||||
function parseWhitespaceAndSkipComments() {
|
||||
let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
const start = i;
|
||||
let changed = parseWhitespace(skipNewline);
|
||||
do {
|
||||
changed = parseComment();
|
||||
if (changed) {
|
||||
changed = parseWhitespace(skipNewline);
|
||||
}
|
||||
} while (changed);
|
||||
return i > start;
|
||||
}
|
||||
function parseWhitespace(skipNewline) {
|
||||
const _isWhiteSpace = skipNewline ? isWhitespace : isWhitespaceExceptNewline;
|
||||
let whitespace = '';
|
||||
while (true) {
|
||||
if (_isWhiteSpace(text, i)) {
|
||||
whitespace += text[i];
|
||||
i++;
|
||||
} else if (isSpecialWhitespace(text, i)) {
|
||||
// repair special whitespace
|
||||
whitespace += ' ';
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace.length > 0) {
|
||||
output += whitespace;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseComment() {
|
||||
// find a block comment '/* ... */'
|
||||
if (text[i] === '/' && text[i + 1] === '*') {
|
||||
// repair block comment by skipping it
|
||||
while (i < text.length && !atEndOfBlockComment(text, i)) {
|
||||
i++;
|
||||
}
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find a line comment '// ...'
|
||||
if (text[i] === '/' && text[i + 1] === '/') {
|
||||
// repair line comment by skipping it
|
||||
while (i < text.length && text[i] !== '\n') {
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseMarkdownCodeBlock(blocks) {
|
||||
// find and skip over a Markdown fenced code block:
|
||||
// ``` ... ```
|
||||
// or
|
||||
// ```json ... ```
|
||||
if (skipMarkdownCodeBlock(blocks)) {
|
||||
if (isFunctionNameCharStart(text[i])) {
|
||||
// strip the optional language specifier like "json"
|
||||
while (i < text.length && isFunctionNameChar(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipMarkdownCodeBlock(blocks) {
|
||||
parseWhitespace(true);
|
||||
for (const block of blocks) {
|
||||
const end = i + block.length;
|
||||
if (text.slice(i, end) === block) {
|
||||
i = end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
output += text[i];
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipEscapeCharacter() {
|
||||
return skipCharacter('\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ellipsis like "[1,2,3,...]" or "[1,2,3,...,9]" or "[...,7,8,9]"
|
||||
* or a similar construct in objects.
|
||||
*/
|
||||
function skipEllipsis() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (text[i] === '.' && text[i + 1] === '.' && text[i + 2] === '.') {
|
||||
// repair: remove the ellipsis (three dots) and optionally a comma
|
||||
i += 3;
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipCharacter(',');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an object like '{"key": "value"}'
|
||||
*/
|
||||
function parseObject() {
|
||||
if (text[i] === '{') {
|
||||
output += '{';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in {, message: "hi"}
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== '}') {
|
||||
let processedComma;
|
||||
if (!initial) {
|
||||
processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
} else {
|
||||
processedComma = true;
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedKey = parseString() || parseUnquotedString(true);
|
||||
if (!processedKey) {
|
||||
if (text[i] === '}' || text[i] === '{' || text[i] === ']' || text[i] === '[' || text[i] === undefined) {
|
||||
// repair trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
} else {
|
||||
throwObjectKeyExpected();
|
||||
}
|
||||
break;
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processedColon = parseCharacter(':');
|
||||
const truncatedText = i >= text.length;
|
||||
if (!processedColon) {
|
||||
if (isStartOfValue(text[i]) || truncatedText) {
|
||||
// repair missing colon
|
||||
output = insertBeforeLastWhitespace(output, ':');
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
if (processedColon || truncatedText) {
|
||||
// repair missing object value
|
||||
output += 'null';
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text[i] === '}') {
|
||||
output += '}';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing end bracket
|
||||
output = insertBeforeLastWhitespace(output, '}');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array like '["item1", "item2", ...]'
|
||||
*/
|
||||
function parseArray() {
|
||||
if (text[i] === '[') {
|
||||
output += '[';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in [,1,2,3]
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== ']') {
|
||||
if (!initial) {
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
// repair trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text[i] === ']') {
|
||||
output += ']';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing closing array bracket
|
||||
output = insertBeforeLastWhitespace(output, ']');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and repair Newline Delimited JSON (NDJSON):
|
||||
* multiple JSON objects separated by a newline character
|
||||
*/
|
||||
function parseNewlineDelimitedJSON() {
|
||||
// repair NDJSON
|
||||
let initial = true;
|
||||
let processedValue = true;
|
||||
while (processedValue) {
|
||||
if (!initial) {
|
||||
// parse optional comma, insert when missing
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair: add missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
processedValue = parseValue();
|
||||
}
|
||||
if (!processedValue) {
|
||||
// repair: remove trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
}
|
||||
|
||||
// repair: wrap the output inside array brackets
|
||||
output = `[\n${output}\n]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string enclosed by double quotes "...". Can contain escaped quotes
|
||||
* Repair strings enclosed in single quotes or special quotes
|
||||
* Repair an escaped string
|
||||
*
|
||||
* The function can run in two stages:
|
||||
* - First, it assumes the string has a valid end quote
|
||||
* - If it turns out that the string does not have a valid end quote followed
|
||||
* by a delimiter (which should be the case), the function runs again in a
|
||||
* more conservative way, stopping the string at the first next delimiter
|
||||
* and fixing the string by inserting a quote there, or stopping at a
|
||||
* stop index detected in the first iteration.
|
||||
*/
|
||||
function parseString() {
|
||||
let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
|
||||
let skipEscapeChars = text[i] === '\\';
|
||||
if (skipEscapeChars) {
|
||||
// repair: remove the first escape character
|
||||
i++;
|
||||
skipEscapeChars = true;
|
||||
}
|
||||
if (isQuote(text[i])) {
|
||||
// double quotes are correct JSON,
|
||||
// single quotes come from JavaScript for example, we assume it will have a correct single end quote too
|
||||
// otherwise, we will match any double-quote-like start with a double-quote-like end,
|
||||
// or any single-quote-like start with a single-quote-like end
|
||||
const isEndQuote = isDoubleQuote(text[i]) ? isDoubleQuote : isSingleQuote(text[i]) ? isSingleQuote : isSingleQuoteLike(text[i]) ? isSingleQuoteLike : isDoubleQuoteLike;
|
||||
const iBefore = i;
|
||||
const oBefore = output.length;
|
||||
let str = '"';
|
||||
i++;
|
||||
while (true) {
|
||||
if (i >= text.length) {
|
||||
// end of text, we are missing an end quote
|
||||
|
||||
const iPrev = prevNonWhitespaceIndex(i - 1);
|
||||
if (!stopAtDelimiter && isDelimiter(text.charAt(iPrev))) {
|
||||
// if the text ends with a delimiter, like ["hello],
|
||||
// so the missing end quote should be inserted before this delimiter
|
||||
// retry parsing the string, stopping at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (i === stopAtIndex) {
|
||||
// use the stop index detected in the first iteration, and repair end quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (isEndQuote(text[i])) {
|
||||
// end quote
|
||||
// let us check what is before and after the quote to verify whether this is a legit end quote
|
||||
const iQuote = i;
|
||||
const oQuote = str.length;
|
||||
str += '"';
|
||||
i++;
|
||||
output += str;
|
||||
parseWhitespaceAndSkipComments(false);
|
||||
if (stopAtDelimiter || i >= text.length || isDelimiter(text[i]) || isQuote(text[i]) || isDigit(text[i])) {
|
||||
// The quote is followed by the end of the text, a delimiter,
|
||||
// or a next value. So the quote is indeed the end of the string.
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
}
|
||||
const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);
|
||||
const prevChar = text.charAt(iPrevChar);
|
||||
if (prevChar === ',') {
|
||||
// A comma followed by a quote, like '{"a":"b,c,"d":"e"}'.
|
||||
// We assume that the quote is a start quote, and that the end quote
|
||||
// should have been located right before the comma but is missing.
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(false, iPrevChar);
|
||||
}
|
||||
if (isDelimiter(prevChar)) {
|
||||
// This is not the right end quote: it is preceded by a delimiter,
|
||||
// and NOT followed by a delimiter. So, there is an end quote missing
|
||||
// parse the string again and then stop at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// revert to right after the quote but before any whitespace, and continue parsing the string
|
||||
output = output.substring(0, oBefore);
|
||||
i = iQuote + 1;
|
||||
|
||||
// repair unescaped quote
|
||||
str = `${str.substring(0, oQuote)}\\${str.substring(oQuote)}`;
|
||||
} else if (stopAtDelimiter && isUnquotedStringDelimiter(text[i])) {
|
||||
// we're in the mode to stop the string at the first delimiter
|
||||
// because there is an end quote missing
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && regexUrlStart.test(text.substring(iBefore + 1, i + 2))) {
|
||||
while (i < text.length && regexUrlChar.test(text[i])) {
|
||||
str += text[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
} else if (text[i] === '\\') {
|
||||
// handle escaped content like \n or \u2605
|
||||
const char = text.charAt(i + 1);
|
||||
const escapeChar = escapeCharacters[char];
|
||||
if (escapeChar !== undefined) {
|
||||
str += text.slice(i, i + 2);
|
||||
i += 2;
|
||||
} else if (char === 'u') {
|
||||
let j = 2;
|
||||
while (j < 6 && isHex(text[i + j])) {
|
||||
j++;
|
||||
}
|
||||
if (j === 6) {
|
||||
str += text.slice(i, i + 6);
|
||||
i += 6;
|
||||
} else if (i + j >= text.length) {
|
||||
// repair invalid or truncated unicode char at the end of the text
|
||||
// by removing the unicode char and ending the string here
|
||||
i = text.length;
|
||||
} else {
|
||||
throwInvalidUnicodeCharacter();
|
||||
}
|
||||
} else {
|
||||
// repair invalid escape character: remove it
|
||||
str += char;
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
// handle regular characters
|
||||
const char = text.charAt(i);
|
||||
if (char === '"' && text[i - 1] !== '\\') {
|
||||
// repair unescaped double quote
|
||||
str += `\\${char}`;
|
||||
i++;
|
||||
} else if (isControlCharacter(char)) {
|
||||
// unescaped control character
|
||||
str += controlCharacters[char];
|
||||
i++;
|
||||
} else {
|
||||
if (!isValidStringCharacter(char)) {
|
||||
throwInvalidCharacter(char);
|
||||
}
|
||||
str += char;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (skipEscapeChars) {
|
||||
// repair: skipped escape character (nothing to do)
|
||||
skipEscapeCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair concatenated strings like "hello" + "world", change this into "helloworld"
|
||||
*/
|
||||
function parseConcatenatedString() {
|
||||
let processed = false;
|
||||
parseWhitespaceAndSkipComments();
|
||||
while (text[i] === '+') {
|
||||
processed = true;
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: remove the end quote of the first string
|
||||
output = stripLastOccurrence(output, '"', true);
|
||||
const start = output.length;
|
||||
const parsedStr = parseString();
|
||||
if (parsedStr) {
|
||||
// repair: remove the start quote of the second string
|
||||
output = removeAtIndex(output, start, 1);
|
||||
} else {
|
||||
// repair: remove the + because it is not followed by a string
|
||||
output = insertBeforeLastWhitespace(output, '"');
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number like 2.4 or 2.4e6
|
||||
*/
|
||||
function parseNumber() {
|
||||
const start = i;
|
||||
if (text[i] === '-') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in JSON leading zeros like "00789" are not allowed.
|
||||
// We will allow all leading zeros here though and at the end of parseNumber
|
||||
// check against trailing zeros and repair that if needed.
|
||||
// Leading zeros can have meaning, so we should not clear them.
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
if (text[i] === '.') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (text[i] === 'e' || text[i] === 'E') {
|
||||
i++;
|
||||
if (text[i] === '-' || text[i] === '+') {
|
||||
i++;
|
||||
}
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not at the end of the number by this point, allow this to be parsed as another type
|
||||
if (!atEndOfNumber()) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
if (i > start) {
|
||||
// repair a number with leading zeros like "00789"
|
||||
const num = text.slice(start, i);
|
||||
const hasInvalidLeadingZero = /^0\d/.test(num);
|
||||
output += hasInvalidLeadingZero ? `"${num}"` : num;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keywords true, false, null
|
||||
* Repair Python keywords True, False, None
|
||||
*/
|
||||
function parseKeywords() {
|
||||
return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||
|
||||
// repair Python keywords True, False, None
|
||||
parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');
|
||||
}
|
||||
function parseKeyword(name, value) {
|
||||
if (text.slice(i, i + name.length) === name) {
|
||||
output += value;
|
||||
i += name.length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair an unquoted string by adding quotes around it
|
||||
* Repair a MongoDB function call like NumberLong("2")
|
||||
* Repair a JSONP function call like callback({...});
|
||||
*/
|
||||
function parseUnquotedString(isKey) {
|
||||
// note that the symbol can end with whitespaces: we stop at the next delimiter
|
||||
// also, note that we allow strings to contain a slash / in order to support repairing regular expressions
|
||||
const start = i;
|
||||
if (isFunctionNameCharStart(text[i])) {
|
||||
while (i < text.length && isFunctionNameChar(text[i])) {
|
||||
i++;
|
||||
}
|
||||
let j = i;
|
||||
while (isWhitespace(text, j)) {
|
||||
j++;
|
||||
}
|
||||
if (text[j] === '(') {
|
||||
// repair a MongoDB function call like NumberLong("2")
|
||||
// repair a JSONP function call like callback({...});
|
||||
i = j + 1;
|
||||
parseValue();
|
||||
if (text[i] === ')') {
|
||||
// repair: skip close bracket of function call
|
||||
i++;
|
||||
if (text[i] === ';') {
|
||||
// repair: skip semicolon after JSONP call
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (i < text.length && !isUnquotedStringDelimiter(text[i]) && !isQuote(text[i]) && (!isKey || text[i] !== ':')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && regexUrlStart.test(text.substring(start, i + 2))) {
|
||||
while (i < text.length && regexUrlChar.test(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i > start) {
|
||||
// repair unquoted string
|
||||
// also, repair undefined into null
|
||||
|
||||
// first, go back to prevent getting trailing whitespaces in the string
|
||||
while (isWhitespace(text, i - 1) && i > 0) {
|
||||
i--;
|
||||
}
|
||||
const symbol = text.slice(start, i);
|
||||
output += symbol === 'undefined' ? 'null' : JSON.stringify(symbol);
|
||||
if (text[i] === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function parseRegex() {
|
||||
if (text[i] === '/') {
|
||||
const start = i;
|
||||
i++;
|
||||
while (i < text.length && (text[i] !== '/' || text[i - 1] === '\\')) {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
output += `"${text.substring(start, i)}"`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function prevNonWhitespaceIndex(start) {
|
||||
let prev = start;
|
||||
while (prev > 0 && isWhitespace(text, prev)) {
|
||||
prev--;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
function atEndOfNumber() {
|
||||
return i >= text.length || isDelimiter(text[i]) || isWhitespace(text, i);
|
||||
}
|
||||
function repairNumberEndingWithNumericSymbol(start) {
|
||||
// repair numbers cut off at the end
|
||||
// this will only be called when we end after a '.', '-', or 'e' and does not
|
||||
// change the number more than it needs to make it valid JSON
|
||||
output += `${text.slice(start, i)}0`;
|
||||
}
|
||||
function throwInvalidCharacter(char) {
|
||||
throw new JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);
|
||||
}
|
||||
function throwUnexpectedCharacter() {
|
||||
throw new JSONRepairError(`Unexpected character ${JSON.stringify(text[i])}`, i);
|
||||
}
|
||||
function throwUnexpectedEnd() {
|
||||
throw new JSONRepairError('Unexpected end of json string', text.length);
|
||||
}
|
||||
function throwObjectKeyExpected() {
|
||||
throw new JSONRepairError('Object key expected', i);
|
||||
}
|
||||
function throwColonExpected() {
|
||||
throw new JSONRepairError('Colon expected', i);
|
||||
}
|
||||
function throwInvalidUnicodeCharacter() {
|
||||
const chars = text.slice(i, i + 6);
|
||||
throw new JSONRepairError(`Invalid unicode character "${chars}"`, i);
|
||||
}
|
||||
}
|
||||
function atEndOfBlockComment(text, i) {
|
||||
return text[i] === '*' && text[i + 1] === '/';
|
||||
}
|
||||
//# sourceMappingURL=jsonrepair.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/regular/jsonrepair.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/regular/jsonrepair.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/jsonrepair/lib/esm/stream.js
generated
vendored
Normal file
3
node_modules/jsonrepair/lib/esm/stream.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Node.js streaming API
|
||||
export { jsonrepairTransform } from './streaming/stream.js';
|
||||
//# sourceMappingURL=stream.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/stream.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/stream.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.js","names":["jsonrepairTransform"],"sources":["../../src/stream.ts"],"sourcesContent":["// Node.js streaming API\nexport { type JsonRepairTransformOptions, jsonrepairTransform } from './streaming/stream.js'\n"],"mappings":"AAAA;AACA,SAA0CA,mBAAmB,QAAQ,uBAAuB","ignoreList":[]}
|
||||
69
node_modules/jsonrepair/lib/esm/streaming/buffer/InputBuffer.js
generated
vendored
Normal file
69
node_modules/jsonrepair/lib/esm/streaming/buffer/InputBuffer.js
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
export function createInputBuffer() {
|
||||
let buffer = '';
|
||||
let offset = 0;
|
||||
let currentLength = 0;
|
||||
let closed = false;
|
||||
function ensure(index) {
|
||||
if (index < offset) {
|
||||
throw new Error(`${indexOutOfRangeMessage} (index: ${index}, offset: ${offset})`);
|
||||
}
|
||||
if (index >= currentLength) {
|
||||
if (!closed) {
|
||||
throw new Error(`${indexOutOfRangeMessage} (index: ${index})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function push(chunk) {
|
||||
buffer += chunk;
|
||||
currentLength += chunk.length;
|
||||
}
|
||||
function flush(position) {
|
||||
if (position > currentLength) {
|
||||
return;
|
||||
}
|
||||
buffer = buffer.substring(position - offset);
|
||||
offset = position;
|
||||
}
|
||||
function charAt(index) {
|
||||
ensure(index);
|
||||
return buffer.charAt(index - offset);
|
||||
}
|
||||
function charCodeAt(index) {
|
||||
ensure(index);
|
||||
return buffer.charCodeAt(index - offset);
|
||||
}
|
||||
function substring(start, end) {
|
||||
ensure(end - 1); // -1 because end is excluded
|
||||
ensure(start);
|
||||
return buffer.slice(start - offset, end - offset);
|
||||
}
|
||||
function length() {
|
||||
if (!closed) {
|
||||
throw new Error('Cannot get length: input is not yet closed');
|
||||
}
|
||||
return currentLength;
|
||||
}
|
||||
function isEnd(index) {
|
||||
if (!closed) {
|
||||
ensure(index);
|
||||
}
|
||||
return index >= currentLength;
|
||||
}
|
||||
function close() {
|
||||
closed = true;
|
||||
}
|
||||
return {
|
||||
push,
|
||||
flush,
|
||||
charAt,
|
||||
charCodeAt,
|
||||
substring,
|
||||
length,
|
||||
currentLength: () => currentLength,
|
||||
currentBufferSize: () => buffer.length,
|
||||
isEnd,
|
||||
close
|
||||
};
|
||||
}
|
||||
const indexOutOfRangeMessage = 'Index out of range, please configure a larger buffer size';
|
||||
//# sourceMappingURL=InputBuffer.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/streaming/buffer/InputBuffer.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/streaming/buffer/InputBuffer.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"InputBuffer.js","names":["createInputBuffer","buffer","offset","currentLength","closed","ensure","index","Error","indexOutOfRangeMessage","push","chunk","length","flush","position","substring","charAt","charCodeAt","start","end","slice","isEnd","close","currentBufferSize"],"sources":["../../../../src/streaming/buffer/InputBuffer.ts"],"sourcesContent":["export interface InputBuffer {\n push: (chunk: string) => void\n flush: (position: number) => void\n charAt: (index: number) => string\n charCodeAt: (index: number) => number\n substring: (start: number, end: number) => string\n length: () => number\n currentLength: () => number\n currentBufferSize: () => number\n isEnd: (index: number) => boolean\n close: () => void\n}\n\nexport function createInputBuffer(): InputBuffer {\n let buffer = ''\n let offset = 0\n let currentLength = 0\n let closed = false\n\n function ensure(index: number) {\n if (index < offset) {\n throw new Error(`${indexOutOfRangeMessage} (index: ${index}, offset: ${offset})`)\n }\n\n if (index >= currentLength) {\n if (!closed) {\n throw new Error(`${indexOutOfRangeMessage} (index: ${index})`)\n }\n }\n }\n\n function push(chunk: string) {\n buffer += chunk\n currentLength += chunk.length\n }\n\n function flush(position: number) {\n if (position > currentLength) {\n return\n }\n\n buffer = buffer.substring(position - offset)\n offset = position\n }\n\n function charAt(index: number): string {\n ensure(index)\n\n return buffer.charAt(index - offset)\n }\n\n function charCodeAt(index: number): number {\n ensure(index)\n\n return buffer.charCodeAt(index - offset)\n }\n\n function substring(start: number, end: number): string {\n ensure(end - 1) // -1 because end is excluded\n ensure(start)\n\n return buffer.slice(start - offset, end - offset)\n }\n\n function length(): number {\n if (!closed) {\n throw new Error('Cannot get length: input is not yet closed')\n }\n\n return currentLength\n }\n\n function isEnd(index: number): boolean {\n if (!closed) {\n ensure(index)\n }\n\n return index >= currentLength\n }\n\n function close() {\n closed = true\n }\n\n return {\n push,\n flush,\n charAt,\n charCodeAt,\n substring,\n length,\n currentLength: () => currentLength,\n currentBufferSize: () => buffer.length,\n isEnd,\n close\n }\n}\n\nconst indexOutOfRangeMessage = 'Index out of range, please configure a larger buffer size'\n"],"mappings":"AAaA,OAAO,SAASA,iBAAiBA,CAAA,EAAgB;EAC/C,IAAIC,MAAM,GAAG,EAAE;EACf,IAAIC,MAAM,GAAG,CAAC;EACd,IAAIC,aAAa,GAAG,CAAC;EACrB,IAAIC,MAAM,GAAG,KAAK;EAElB,SAASC,MAAMA,CAACC,KAAa,EAAE;IAC7B,IAAIA,KAAK,GAAGJ,MAAM,EAAE;MAClB,MAAM,IAAIK,KAAK,CAAC,GAAGC,sBAAsB,YAAYF,KAAK,aAAaJ,MAAM,GAAG,CAAC;IACnF;IAEA,IAAII,KAAK,IAAIH,aAAa,EAAE;MAC1B,IAAI,CAACC,MAAM,EAAE;QACX,MAAM,IAAIG,KAAK,CAAC,GAAGC,sBAAsB,YAAYF,KAAK,GAAG,CAAC;MAChE;IACF;EACF;EAEA,SAASG,IAAIA,CAACC,KAAa,EAAE;IAC3BT,MAAM,IAAIS,KAAK;IACfP,aAAa,IAAIO,KAAK,CAACC,MAAM;EAC/B;EAEA,SAASC,KAAKA,CAACC,QAAgB,EAAE;IAC/B,IAAIA,QAAQ,GAAGV,aAAa,EAAE;MAC5B;IACF;IAEAF,MAAM,GAAGA,MAAM,CAACa,SAAS,CAACD,QAAQ,GAAGX,MAAM,CAAC;IAC5CA,MAAM,GAAGW,QAAQ;EACnB;EAEA,SAASE,MAAMA,CAACT,KAAa,EAAU;IACrCD,MAAM,CAACC,KAAK,CAAC;IAEb,OAAOL,MAAM,CAACc,MAAM,CAACT,KAAK,GAAGJ,MAAM,CAAC;EACtC;EAEA,SAASc,UAAUA,CAACV,KAAa,EAAU;IACzCD,MAAM,CAACC,KAAK,CAAC;IAEb,OAAOL,MAAM,CAACe,UAAU,CAACV,KAAK,GAAGJ,MAAM,CAAC;EAC1C;EAEA,SAASY,SAASA,CAACG,KAAa,EAAEC,GAAW,EAAU;IACrDb,MAAM,CAACa,GAAG,GAAG,CAAC,CAAC,EAAC;IAChBb,MAAM,CAACY,KAAK,CAAC;IAEb,OAAOhB,MAAM,CAACkB,KAAK,CAACF,KAAK,GAAGf,MAAM,EAAEgB,GAAG,GAAGhB,MAAM,CAAC;EACnD;EAEA,SAASS,MAAMA,CAAA,EAAW;IACxB,IAAI,CAACP,MAAM,EAAE;MACX,MAAM,IAAIG,KAAK,CAAC,4CAA4C,CAAC;IAC/D;IAEA,OAAOJ,aAAa;EACtB;EAEA,SAASiB,KAAKA,CAACd,KAAa,EAAW;IACrC,IAAI,CAACF,MAAM,EAAE;MACXC,MAAM,CAACC,KAAK,CAAC;IACf;IAEA,OAAOA,KAAK,IAAIH,aAAa;EAC/B;EAEA,SAASkB,KAAKA,CAAA,EAAG;IACfjB,MAAM,GAAG,IAAI;EACf;EAEA,OAAO;IACLK,IAAI;IACJG,KAAK;IACLG,MAAM;IACNC,UAAU;IACVF,SAAS;IACTH,MAAM;IACNR,aAAa,EAAEA,CAAA,KAAMA,aAAa;IAClCmB,iBAAiB,EAAEA,CAAA,KAAMrB,MAAM,CAACU,MAAM;IACtCS,KAAK;IACLC;EACF,CAAC;AACH;AAEA,MAAMb,sBAAsB,GAAG,2DAA2D","ignoreList":[]}
|
||||
111
node_modules/jsonrepair/lib/esm/streaming/buffer/OutputBuffer.js
generated
vendored
Normal file
111
node_modules/jsonrepair/lib/esm/streaming/buffer/OutputBuffer.js
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
import { isWhitespace } from '../../utils/stringUtils.js';
|
||||
export function createOutputBuffer(_ref) {
|
||||
let {
|
||||
write,
|
||||
chunkSize,
|
||||
bufferSize
|
||||
} = _ref;
|
||||
let buffer = '';
|
||||
let offset = 0;
|
||||
function flushChunks() {
|
||||
let minSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : bufferSize;
|
||||
while (buffer.length >= minSize + chunkSize) {
|
||||
const chunk = buffer.substring(0, chunkSize);
|
||||
write(chunk);
|
||||
offset += chunkSize;
|
||||
buffer = buffer.substring(chunkSize);
|
||||
}
|
||||
}
|
||||
function flush() {
|
||||
flushChunks(0);
|
||||
if (buffer.length > 0) {
|
||||
write(buffer);
|
||||
offset += buffer.length;
|
||||
buffer = '';
|
||||
}
|
||||
}
|
||||
function push(text) {
|
||||
buffer += text;
|
||||
flushChunks();
|
||||
}
|
||||
function unshift(text) {
|
||||
if (offset > 0) {
|
||||
throw new Error(`Cannot unshift: ${flushedMessage}`);
|
||||
}
|
||||
buffer = text + buffer;
|
||||
flushChunks();
|
||||
}
|
||||
function remove(start, end) {
|
||||
if (start < offset) {
|
||||
throw new Error(`Cannot remove: ${flushedMessage}`);
|
||||
}
|
||||
if (end !== undefined) {
|
||||
buffer = buffer.substring(0, start - offset) + buffer.substring(end - offset);
|
||||
} else {
|
||||
buffer = buffer.substring(0, start - offset);
|
||||
}
|
||||
}
|
||||
function insertAt(index, text) {
|
||||
if (index < offset) {
|
||||
throw new Error(`Cannot insert: ${flushedMessage}`);
|
||||
}
|
||||
buffer = buffer.substring(0, index - offset) + text + buffer.substring(index - offset);
|
||||
}
|
||||
function length() {
|
||||
return offset + buffer.length;
|
||||
}
|
||||
function stripLastOccurrence(textToStrip) {
|
||||
let stripRemainingText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
const bufferIndex = buffer.lastIndexOf(textToStrip);
|
||||
if (bufferIndex !== -1) {
|
||||
if (stripRemainingText) {
|
||||
buffer = buffer.substring(0, bufferIndex);
|
||||
} else {
|
||||
buffer = buffer.substring(0, bufferIndex) + buffer.substring(bufferIndex + textToStrip.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
function insertBeforeLastWhitespace(textToInsert) {
|
||||
let bufferIndex = buffer.length; // index relative to the start of the buffer, not taking `offset` into account
|
||||
|
||||
if (!isWhitespace(buffer, bufferIndex - 1)) {
|
||||
// no trailing whitespaces
|
||||
push(textToInsert);
|
||||
return;
|
||||
}
|
||||
while (isWhitespace(buffer, bufferIndex - 1)) {
|
||||
bufferIndex--;
|
||||
}
|
||||
if (bufferIndex <= 0) {
|
||||
throw new Error(`Cannot insert: ${flushedMessage}`);
|
||||
}
|
||||
buffer = buffer.substring(0, bufferIndex) + textToInsert + buffer.substring(bufferIndex);
|
||||
flushChunks();
|
||||
}
|
||||
function endsWithIgnoringWhitespace(char) {
|
||||
let i = buffer.length - 1;
|
||||
while (i > 0) {
|
||||
if (char === buffer.charAt(i)) {
|
||||
return true;
|
||||
}
|
||||
if (!isWhitespace(buffer, i)) {
|
||||
return false;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
push,
|
||||
unshift,
|
||||
remove,
|
||||
insertAt,
|
||||
length,
|
||||
flush,
|
||||
stripLastOccurrence,
|
||||
insertBeforeLastWhitespace,
|
||||
endsWithIgnoringWhitespace
|
||||
};
|
||||
}
|
||||
const flushedMessage = 'start of the output is already flushed from the buffer';
|
||||
//# sourceMappingURL=OutputBuffer.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/streaming/buffer/OutputBuffer.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/streaming/buffer/OutputBuffer.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
818
node_modules/jsonrepair/lib/esm/streaming/core.js
generated
vendored
Normal file
818
node_modules/jsonrepair/lib/esm/streaming/core.js
generated
vendored
Normal file
@ -0,0 +1,818 @@
|
||||
import { JSONRepairError } from '../utils/JSONRepairError.js';
|
||||
import { isControlCharacter, isDelimiter, isDigit, isDoubleQuote, isDoubleQuoteLike, isFunctionNameChar, isFunctionNameCharStart, isHex, isQuote, isSingleQuote, isSingleQuoteLike, isSpecialWhitespace, isStartOfValue, isUnquotedStringDelimiter, isValidStringCharacter, isWhitespace, isWhitespaceExceptNewline, regexUrlChar, regexUrlStart } from '../utils/stringUtils.js';
|
||||
import { createInputBuffer } from './buffer/InputBuffer.js';
|
||||
import { createOutputBuffer } from './buffer/OutputBuffer.js';
|
||||
import { Caret, createStack, StackType } from './stack.js';
|
||||
const controlCharacters = {
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
};
|
||||
|
||||
// map with all escape characters
|
||||
const escapeCharacters = {
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
b: '\b',
|
||||
f: '\f',
|
||||
n: '\n',
|
||||
r: '\r',
|
||||
t: '\t'
|
||||
// note that \u is handled separately in parseString()
|
||||
};
|
||||
export function jsonrepairCore(_ref) {
|
||||
let {
|
||||
onData,
|
||||
bufferSize = 65536,
|
||||
chunkSize = 65536
|
||||
} = _ref;
|
||||
const input = createInputBuffer();
|
||||
const output = createOutputBuffer({
|
||||
write: onData,
|
||||
bufferSize,
|
||||
chunkSize
|
||||
});
|
||||
let i = 0;
|
||||
let iFlushed = 0;
|
||||
const stack = createStack();
|
||||
function flushInputBuffer() {
|
||||
while (iFlushed < i - bufferSize - chunkSize) {
|
||||
iFlushed += chunkSize;
|
||||
input.flush(iFlushed);
|
||||
}
|
||||
}
|
||||
function transform(chunk) {
|
||||
input.push(chunk);
|
||||
while (i < input.currentLength() - bufferSize && parse()) {
|
||||
// loop until there is nothing more to process
|
||||
}
|
||||
flushInputBuffer();
|
||||
}
|
||||
function flush() {
|
||||
input.close();
|
||||
while (parse()) {
|
||||
// loop until there is nothing more to process
|
||||
}
|
||||
output.flush();
|
||||
}
|
||||
function parse() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
switch (stack.type) {
|
||||
case StackType.object:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case Caret.beforeKey:
|
||||
return skipEllipsis() || parseObjectKey() || parseUnexpectedColon() || parseRepairTrailingComma() || parseRepairObjectEndOrComma();
|
||||
case Caret.beforeValue:
|
||||
return parseValue() || parseRepairMissingObjectValue();
|
||||
case Caret.afterValue:
|
||||
return parseObjectComma() || parseObjectEnd() || parseRepairObjectEndOrComma();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case StackType.array:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case Caret.beforeValue:
|
||||
return skipEllipsis() || parseValue() || parseRepairTrailingComma() || parseRepairArrayEnd();
|
||||
case Caret.afterValue:
|
||||
return parseArrayComma() || parseArrayEnd() || parseRepairMissingComma() || parseRepairArrayEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case StackType.ndJson:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case Caret.beforeValue:
|
||||
return parseValue() || parseRepairTrailingComma();
|
||||
case Caret.afterValue:
|
||||
return parseArrayComma() || parseRepairMissingComma() || parseRepairNdJsonEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case StackType.functionCall:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case Caret.beforeValue:
|
||||
return parseValue();
|
||||
case Caret.afterValue:
|
||||
return parseFunctionCallEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case StackType.root:
|
||||
{
|
||||
switch (stack.caret) {
|
||||
case Caret.beforeValue:
|
||||
return parseRootStart();
|
||||
case Caret.afterValue:
|
||||
return parseRootEnd();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function parseValue() {
|
||||
return parseObjectStart() || parseArrayStart() || parseString() || parseNumber() || parseKeywords() || parseRepairUnquotedString() || parseRepairRegex();
|
||||
}
|
||||
function parseObjectStart() {
|
||||
if (parseCharacter('{')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipEllipsis();
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (parseCharacter('}')) {
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return stack.push(StackType.object, Caret.beforeKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseArrayStart() {
|
||||
if (parseCharacter('[')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipEllipsis();
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (parseCharacter(']')) {
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return stack.push(StackType.array, Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairUnquotedString() {
|
||||
let j = i;
|
||||
if (isFunctionNameCharStart(input.charAt(j))) {
|
||||
while (!input.isEnd(j) && isFunctionNameChar(input.charAt(j))) {
|
||||
j++;
|
||||
}
|
||||
let k = j;
|
||||
while (isWhitespace(input, k)) {
|
||||
k++;
|
||||
}
|
||||
if (input.charAt(k) === '(') {
|
||||
// repair a MongoDB function call like NumberLong("2")
|
||||
// repair a JSONP function call like callback({...});
|
||||
k++;
|
||||
i = k;
|
||||
return stack.push(StackType.functionCall, Caret.beforeValue);
|
||||
}
|
||||
}
|
||||
j = findNextDelimiter(false, j);
|
||||
if (j !== null) {
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (input.charAt(j - 1) === ':' && regexUrlStart.test(input.substring(i, j + 2))) {
|
||||
while (!input.isEnd(j) && regexUrlChar.test(input.charAt(j))) {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
const symbol = input.substring(i, j);
|
||||
i = j;
|
||||
output.push(symbol === 'undefined' ? 'null' : JSON.stringify(symbol));
|
||||
if (input.charAt(i) === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairRegex() {
|
||||
if (input.charAt(i) === '/') {
|
||||
const start = i;
|
||||
i++;
|
||||
while (!input.isEnd(i) && (input.charAt(i) !== '/' || input.charAt(i - 1) === '\\')) {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
output.push(`"${input.substring(start, i)}"`);
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
}
|
||||
function parseRepairMissingObjectValue() {
|
||||
// repair missing object value
|
||||
output.push('null');
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
function parseRepairTrailingComma() {
|
||||
// repair trailing comma
|
||||
if (output.endsWithIgnoringWhitespace(',')) {
|
||||
output.stripLastOccurrence(',');
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnexpectedColon() {
|
||||
if (input.charAt(i) === ':') {
|
||||
throwObjectKeyExpected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnexpectedEnd() {
|
||||
if (input.isEnd(i)) {
|
||||
throwUnexpectedEnd();
|
||||
} else {
|
||||
throwUnexpectedCharacter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectKey() {
|
||||
const parsedKey = parseString() || parseUnquotedKey();
|
||||
if (parsedKey) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (parseCharacter(':')) {
|
||||
// expect a value after the :
|
||||
return stack.update(Caret.beforeValue);
|
||||
}
|
||||
const truncatedText = input.isEnd(i);
|
||||
if (isStartOfValue(input.charAt(i)) || truncatedText) {
|
||||
// repair missing colon
|
||||
output.insertBeforeLastWhitespace(':');
|
||||
return stack.update(Caret.beforeValue);
|
||||
}
|
||||
throwColonExpected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectComma() {
|
||||
if (parseCharacter(',')) {
|
||||
return stack.update(Caret.beforeKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseObjectEnd() {
|
||||
if (parseCharacter('}')) {
|
||||
return stack.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairObjectEndOrComma() {
|
||||
// repair missing object end and trailing comma
|
||||
if (input.charAt(i) === '{') {
|
||||
output.stripLastOccurrence(',');
|
||||
output.insertBeforeLastWhitespace('}');
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
// repair missing comma
|
||||
if (!input.isEnd(i) && isStartOfValue(input.charAt(i))) {
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
return stack.update(Caret.beforeKey);
|
||||
}
|
||||
|
||||
// repair missing closing brace
|
||||
output.insertBeforeLastWhitespace('}');
|
||||
return stack.pop();
|
||||
}
|
||||
function parseArrayComma() {
|
||||
if (parseCharacter(',')) {
|
||||
return stack.update(Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseArrayEnd() {
|
||||
if (parseCharacter(']')) {
|
||||
return stack.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairMissingComma() {
|
||||
// repair missing comma
|
||||
if (!input.isEnd(i) && isStartOfValue(input.charAt(i))) {
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
return stack.update(Caret.beforeValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseRepairArrayEnd() {
|
||||
// repair missing closing bracket
|
||||
output.insertBeforeLastWhitespace(']');
|
||||
return stack.pop();
|
||||
}
|
||||
function parseRepairNdJsonEnd() {
|
||||
if (input.isEnd(i)) {
|
||||
output.push('\n]');
|
||||
return stack.pop();
|
||||
}
|
||||
throwUnexpectedEnd();
|
||||
return false; // just to make TS happy
|
||||
}
|
||||
function parseFunctionCallEnd() {
|
||||
if (skipCharacter(')')) {
|
||||
skipCharacter(';');
|
||||
}
|
||||
return stack.pop();
|
||||
}
|
||||
function parseRootStart() {
|
||||
parseMarkdownCodeBlock(['```', '[```', '{```']);
|
||||
return parseValue() || parseUnexpectedEnd();
|
||||
}
|
||||
function parseRootEnd() {
|
||||
parseMarkdownCodeBlock(['```', '```]', '```}']);
|
||||
const parsedComma = parseCharacter(',');
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (isStartOfValue(input.charAt(i)) && (output.endsWithIgnoringWhitespace(',') || output.endsWithIgnoringWhitespace('\n'))) {
|
||||
// start of a new value after end of the root level object: looks like
|
||||
// newline delimited JSON -> turn into a root level array
|
||||
if (!parsedComma) {
|
||||
// repair missing comma
|
||||
output.insertBeforeLastWhitespace(',');
|
||||
}
|
||||
output.unshift('[\n');
|
||||
return stack.push(StackType.ndJson, Caret.beforeValue);
|
||||
}
|
||||
if (parsedComma) {
|
||||
// repair: remove trailing comma
|
||||
output.stripLastOccurrence(',');
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
|
||||
// repair redundant end braces and brackets
|
||||
while (input.charAt(i) === '}' || input.charAt(i) === ']') {
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (!input.isEnd(i)) {
|
||||
throwUnexpectedCharacter();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseWhitespaceAndSkipComments() {
|
||||
let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
const start = i;
|
||||
let changed = parseWhitespace(skipNewline);
|
||||
do {
|
||||
changed = parseComment();
|
||||
if (changed) {
|
||||
changed = parseWhitespace(skipNewline);
|
||||
}
|
||||
} while (changed);
|
||||
return i > start;
|
||||
}
|
||||
function parseWhitespace(skipNewline) {
|
||||
const _isWhiteSpace = skipNewline ? isWhitespace : isWhitespaceExceptNewline;
|
||||
let whitespace = '';
|
||||
while (true) {
|
||||
if (_isWhiteSpace(input, i)) {
|
||||
whitespace += input.charAt(i);
|
||||
i++;
|
||||
} else if (isSpecialWhitespace(input, i)) {
|
||||
// repair special whitespace
|
||||
whitespace += ' ';
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace.length > 0) {
|
||||
output.push(whitespace);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseComment() {
|
||||
// find a block comment '/* ... */'
|
||||
if (input.charAt(i) === '/' && input.charAt(i + 1) === '*') {
|
||||
// repair block comment by skipping it
|
||||
while (!input.isEnd(i) && !atEndOfBlockComment(i)) {
|
||||
i++;
|
||||
}
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find a line comment '// ...'
|
||||
if (input.charAt(i) === '/' && input.charAt(i + 1) === '/') {
|
||||
// repair line comment by skipping it
|
||||
while (!input.isEnd(i) && input.charAt(i) !== '\n') {
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseMarkdownCodeBlock(blocks) {
|
||||
// find and skip over a Markdown fenced code block:
|
||||
// ``` ... ```
|
||||
// or
|
||||
// ```json ... ```
|
||||
if (skipMarkdownCodeBlock(blocks)) {
|
||||
if (isFunctionNameCharStart(input.charAt(i))) {
|
||||
// strip the optional language specifier like "json"
|
||||
while (!input.isEnd(i) && isFunctionNameChar(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipMarkdownCodeBlock(blocks) {
|
||||
for (const block of blocks) {
|
||||
const end = i + block.length;
|
||||
if (input.substring(i, end) === block) {
|
||||
i = end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseCharacter(char) {
|
||||
if (input.charAt(i) === char) {
|
||||
output.push(input.charAt(i));
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipCharacter(char) {
|
||||
if (input.charAt(i) === char) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipEscapeCharacter() {
|
||||
return skipCharacter('\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ellipsis like "[1,2,3,...]" or "[1,2,3,...,9]" or "[...,7,8,9]"
|
||||
* or a similar construct in objects.
|
||||
*/
|
||||
function skipEllipsis() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (input.charAt(i) === '.' && input.charAt(i + 1) === '.' && input.charAt(i + 2) === '.') {
|
||||
// repair: remove the ellipsis (three dots) and optionally a comma
|
||||
i += 3;
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipCharacter(',');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string enclosed by double quotes "...". Can contain escaped quotes
|
||||
* Repair strings enclosed in single quotes or special quotes
|
||||
* Repair an escaped string
|
||||
*
|
||||
* The function can run in two stages:
|
||||
* - First, it assumes the string has a valid end quote
|
||||
* - If it turns out that the string does not have a valid end quote followed
|
||||
* by a delimiter (which should be the case), the function runs again in a
|
||||
* more conservative way, stopping the string at the first next delimiter
|
||||
* and fixing the string by inserting a quote there, or stopping at a
|
||||
* stop index detected in the first iteration.
|
||||
*/
|
||||
function parseString() {
|
||||
let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
|
||||
let skipEscapeChars = input.charAt(i) === '\\';
|
||||
if (skipEscapeChars) {
|
||||
// repair: remove the first escape character
|
||||
i++;
|
||||
skipEscapeChars = true;
|
||||
}
|
||||
if (isQuote(input.charAt(i))) {
|
||||
// double quotes are correct JSON,
|
||||
// single quotes come from JavaScript for example, we assume it will have a correct single end quote too
|
||||
// otherwise, we will match any double-quote-like start with a double-quote-like end,
|
||||
// or any single-quote-like start with a single-quote-like end
|
||||
const isEndQuote = isDoubleQuote(input.charAt(i)) ? isDoubleQuote : isSingleQuote(input.charAt(i)) ? isSingleQuote : isSingleQuoteLike(input.charAt(i)) ? isSingleQuoteLike : isDoubleQuoteLike;
|
||||
const iBefore = i;
|
||||
const oBefore = output.length();
|
||||
output.push('"');
|
||||
i++;
|
||||
while (true) {
|
||||
if (input.isEnd(i)) {
|
||||
// end of text, we have a missing quote somewhere
|
||||
|
||||
const iPrev = prevNonWhitespaceIndex(i - 1);
|
||||
if (!stopAtDelimiter && isDelimiter(input.charAt(iPrev))) {
|
||||
// if the text ends with a delimiter, like ["hello],
|
||||
// so the missing end quote should be inserted before this delimiter
|
||||
// retry parsing the string, stopping at the first next delimiter
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
if (i === stopAtIndex) {
|
||||
// use the stop index detected in the first iteration, and repair end quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
if (isEndQuote(input.charAt(i))) {
|
||||
// end quote
|
||||
// let us check what is before and after the quote to verify whether this is a legit end quote
|
||||
const iQuote = i;
|
||||
const oQuote = output.length();
|
||||
output.push('"');
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments(false);
|
||||
if (stopAtDelimiter || input.isEnd(i) || isDelimiter(input.charAt(i)) || isQuote(input.charAt(i)) || isDigit(input.charAt(i))) {
|
||||
// The quote is followed by the end of the text, a delimiter, or a next value
|
||||
// so the quote is indeed the end of the string
|
||||
parseConcatenatedString();
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);
|
||||
const prevChar = input.charAt(iPrevChar);
|
||||
if (prevChar === ',') {
|
||||
// A comma followed by a quote, like '{"a":"b,c,"d":"e"}'.
|
||||
// We assume that the quote is a start quote, and that the end quote
|
||||
// should have been located right before the comma but is missing.
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(false, iPrevChar);
|
||||
}
|
||||
if (isDelimiter(prevChar)) {
|
||||
// This is not the right end quote: it is preceded by a delimiter,
|
||||
// and NOT followed by a delimiter. So, there is an end quote missing
|
||||
// parse the string again and then stop at the first next delimiter
|
||||
i = iBefore;
|
||||
output.remove(oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// revert to right after the quote but before any whitespace, and continue parsing the string
|
||||
output.remove(oQuote + 1);
|
||||
i = iQuote + 1;
|
||||
|
||||
// repair unescaped quote
|
||||
output.insertAt(oQuote, '\\');
|
||||
} else if (stopAtDelimiter && isUnquotedStringDelimiter(input.charAt(i))) {
|
||||
// we're in the mode to stop the string at the first delimiter
|
||||
// because there is an end quote missing
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (input.charAt(i - 1) === ':' && regexUrlStart.test(input.substring(iBefore + 1, i + 2))) {
|
||||
while (!input.isEnd(i) && regexUrlChar.test(input.charAt(i))) {
|
||||
output.push(input.charAt(i));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
parseConcatenatedString();
|
||||
return stack.update(Caret.afterValue);
|
||||
} else if (input.charAt(i) === '\\') {
|
||||
// handle escaped content like \n or \u2605
|
||||
const char = input.charAt(i + 1);
|
||||
const escapeChar = escapeCharacters[char];
|
||||
if (escapeChar !== undefined) {
|
||||
output.push(input.substring(i, i + 2));
|
||||
i += 2;
|
||||
} else if (char === 'u') {
|
||||
let j = 2;
|
||||
while (j < 6 && isHex(input.charAt(i + j))) {
|
||||
j++;
|
||||
}
|
||||
if (j === 6) {
|
||||
output.push(input.substring(i, i + 6));
|
||||
i += 6;
|
||||
} else if (input.isEnd(i + j)) {
|
||||
// repair invalid or truncated unicode char at the end of the text
|
||||
// by removing the unicode char and ending the string here
|
||||
i += j;
|
||||
} else {
|
||||
throwInvalidUnicodeCharacter();
|
||||
}
|
||||
} else {
|
||||
// repair invalid escape character: remove it
|
||||
output.push(char);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
// handle regular characters
|
||||
const char = input.charAt(i);
|
||||
if (char === '"' && input.charAt(i - 1) !== '\\') {
|
||||
// repair unescaped double quote
|
||||
output.push(`\\${char}`);
|
||||
i++;
|
||||
} else if (isControlCharacter(char)) {
|
||||
// unescaped control character
|
||||
output.push(controlCharacters[char]);
|
||||
i++;
|
||||
} else {
|
||||
if (!isValidStringCharacter(char)) {
|
||||
throwInvalidCharacter(char);
|
||||
}
|
||||
output.push(char);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (skipEscapeChars) {
|
||||
// repair: skipped escape character (nothing to do)
|
||||
skipEscapeCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair concatenated strings like "hello" + "world", change this into "helloworld"
|
||||
*/
|
||||
function parseConcatenatedString() {
|
||||
let parsed = false;
|
||||
parseWhitespaceAndSkipComments();
|
||||
while (input.charAt(i) === '+') {
|
||||
parsed = true;
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: remove the end quote of the first string
|
||||
output.stripLastOccurrence('"', true);
|
||||
const start = output.length();
|
||||
const parsedStr = parseString();
|
||||
if (parsedStr) {
|
||||
// repair: remove the start quote of the second string
|
||||
output.remove(start, start + 1);
|
||||
} else {
|
||||
// repair: remove the + because it is not followed by a string
|
||||
output.insertBeforeLastWhitespace('"');
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number like 2.4 or 2.4e6
|
||||
*/
|
||||
function parseNumber() {
|
||||
const start = i;
|
||||
if (input.charAt(i) === '-') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
if (!isDigit(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in JSON leading zeros like "00789" are not allowed.
|
||||
// We will allow all leading zeros here though and at the end of parseNumber
|
||||
// check against trailing zeros and repair that if needed.
|
||||
// Leading zeros can have meaning, so we should not clear them.
|
||||
while (isDigit(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
if (input.charAt(i) === '.') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
if (!isDigit(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (input.charAt(i) === 'e' || input.charAt(i) === 'E') {
|
||||
i++;
|
||||
if (input.charAt(i) === '-' || input.charAt(i) === '+') {
|
||||
i++;
|
||||
}
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
if (!isDigit(input.charAt(i))) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(input.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not at the end of the number by this point, allow this to be parsed as another type
|
||||
if (!atEndOfNumber()) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
if (i > start) {
|
||||
// repair a number with leading zeros like "00789"
|
||||
const num = input.substring(start, i);
|
||||
const hasInvalidLeadingZero = /^0\d/.test(num);
|
||||
output.push(hasInvalidLeadingZero ? `"${num}"` : num);
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keywords true, false, null
|
||||
* Repair Python keywords True, False, None
|
||||
*/
|
||||
function parseKeywords() {
|
||||
return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||
|
||||
// repair Python keywords True, False, None
|
||||
parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');
|
||||
}
|
||||
function parseKeyword(name, value) {
|
||||
if (input.substring(i, i + name.length) === name) {
|
||||
output.push(value);
|
||||
i += name.length;
|
||||
return stack.update(Caret.afterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseUnquotedKey() {
|
||||
let end = findNextDelimiter(true, i);
|
||||
if (end !== null) {
|
||||
// first, go back to prevent getting trailing whitespaces in the string
|
||||
while (isWhitespace(input, end - 1) && end > i) {
|
||||
end--;
|
||||
}
|
||||
const symbol = input.substring(i, end);
|
||||
output.push(JSON.stringify(symbol));
|
||||
i = end;
|
||||
if (input.charAt(i) === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return stack.update(Caret.afterValue); // we do not have a state Caret.afterKey, therefore we use afterValue here
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function findNextDelimiter(isKey, start) {
|
||||
// note that the symbol can end with whitespaces: we stop at the next delimiter
|
||||
// also, note that we allow strings to contain a slash / in order to support repairing regular expressions
|
||||
let j = start;
|
||||
while (!input.isEnd(j) && !isUnquotedStringDelimiter(input.charAt(j)) && !isQuote(input.charAt(j)) && (!isKey || input.charAt(j) !== ':')) {
|
||||
j++;
|
||||
}
|
||||
return j > i ? j : null;
|
||||
}
|
||||
function prevNonWhitespaceIndex(start) {
|
||||
let prev = start;
|
||||
while (prev > 0 && isWhitespace(input, prev)) {
|
||||
prev--;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
function atEndOfNumber() {
|
||||
return input.isEnd(i) || isDelimiter(input.charAt(i)) || isWhitespace(input, i);
|
||||
}
|
||||
function repairNumberEndingWithNumericSymbol(start) {
|
||||
// repair numbers cut off at the end
|
||||
// this will only be called when we end after a '.', '-', or 'e' and does not
|
||||
// change the number more than it needs to make it valid JSON
|
||||
output.push(`${input.substring(start, i)}0`);
|
||||
}
|
||||
function throwInvalidCharacter(char) {
|
||||
throw new JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);
|
||||
}
|
||||
function throwUnexpectedCharacter() {
|
||||
throw new JSONRepairError(`Unexpected character ${JSON.stringify(input.charAt(i))}`, i);
|
||||
}
|
||||
function throwUnexpectedEnd() {
|
||||
throw new JSONRepairError('Unexpected end of json string', i);
|
||||
}
|
||||
function throwObjectKeyExpected() {
|
||||
throw new JSONRepairError('Object key expected', i);
|
||||
}
|
||||
function throwColonExpected() {
|
||||
throw new JSONRepairError('Colon expected', i);
|
||||
}
|
||||
function throwInvalidUnicodeCharacter() {
|
||||
const chars = input.substring(i, i + 6);
|
||||
throw new JSONRepairError(`Invalid unicode character "${chars}"`, i);
|
||||
}
|
||||
function atEndOfBlockComment(i) {
|
||||
return input.charAt(i) === '*' && input.charAt(i + 1) === '/';
|
||||
}
|
||||
return {
|
||||
transform,
|
||||
flush
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=core.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/streaming/core.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/streaming/core.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
44
node_modules/jsonrepair/lib/esm/streaming/stack.js
generated
vendored
Normal file
44
node_modules/jsonrepair/lib/esm/streaming/stack.js
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
export let Caret = /*#__PURE__*/function (Caret) {
|
||||
Caret["beforeValue"] = "beforeValue";
|
||||
Caret["afterValue"] = "afterValue";
|
||||
Caret["beforeKey"] = "beforeKey";
|
||||
return Caret;
|
||||
}({});
|
||||
export let StackType = /*#__PURE__*/function (StackType) {
|
||||
StackType["root"] = "root";
|
||||
StackType["object"] = "object";
|
||||
StackType["array"] = "array";
|
||||
StackType["ndJson"] = "ndJson";
|
||||
StackType["functionCall"] = "dataType";
|
||||
return StackType;
|
||||
}({});
|
||||
export function createStack() {
|
||||
const stack = [StackType.root];
|
||||
let caret = Caret.beforeValue;
|
||||
return {
|
||||
get type() {
|
||||
return last(stack);
|
||||
},
|
||||
get caret() {
|
||||
return caret;
|
||||
},
|
||||
pop() {
|
||||
stack.pop();
|
||||
caret = Caret.afterValue;
|
||||
return true;
|
||||
},
|
||||
push(type, newCaret) {
|
||||
stack.push(type);
|
||||
caret = newCaret;
|
||||
return true;
|
||||
},
|
||||
update(newCaret) {
|
||||
caret = newCaret;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
function last(array) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
//# sourceMappingURL=stack.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/streaming/stack.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/streaming/stack.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stack.js","names":["Caret","StackType","createStack","stack","root","caret","beforeValue","type","last","pop","afterValue","push","newCaret","update","array","length"],"sources":["../../../src/streaming/stack.ts"],"sourcesContent":["export enum Caret {\n beforeValue = 'beforeValue',\n afterValue = 'afterValue',\n beforeKey = 'beforeKey'\n}\n\nexport enum StackType {\n root = 'root',\n object = 'object',\n array = 'array',\n ndJson = 'ndJson',\n functionCall = 'dataType'\n}\n\nexport function createStack() {\n const stack: StackType[] = [StackType.root]\n let caret = Caret.beforeValue\n\n return {\n get type() {\n return last(stack)\n },\n\n get caret() {\n return caret\n },\n\n pop(): true {\n stack.pop()\n caret = Caret.afterValue\n\n return true\n },\n\n push(type: StackType, newCaret: Caret): true {\n stack.push(type)\n caret = newCaret\n\n return true\n },\n\n update(newCaret: Caret): true {\n caret = newCaret\n\n return true\n }\n }\n}\n\nfunction last<T>(array: T[]): T | undefined {\n return array[array.length - 1]\n}\n"],"mappings":"AAAA,WAAYA,KAAK,0BAALA,KAAK;EAALA,KAAK;EAALA,KAAK;EAALA,KAAK;EAAA,OAALA,KAAK;AAAA;AAMjB,WAAYC,SAAS,0BAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAATA,SAAS;EAAA,OAATA,SAAS;AAAA;AAQrB,OAAO,SAASC,WAAWA,CAAA,EAAG;EAC5B,MAAMC,KAAkB,GAAG,CAACF,SAAS,CAACG,IAAI,CAAC;EAC3C,IAAIC,KAAK,GAAGL,KAAK,CAACM,WAAW;EAE7B,OAAO;IACL,IAAIC,IAAIA,CAAA,EAAG;MACT,OAAOC,IAAI,CAACL,KAAK,CAAC;IACpB,CAAC;IAED,IAAIE,KAAKA,CAAA,EAAG;MACV,OAAOA,KAAK;IACd,CAAC;IAEDI,GAAGA,CAAA,EAAS;MACVN,KAAK,CAACM,GAAG,CAAC,CAAC;MACXJ,KAAK,GAAGL,KAAK,CAACU,UAAU;MAExB,OAAO,IAAI;IACb,CAAC;IAEDC,IAAIA,CAACJ,IAAe,EAAEK,QAAe,EAAQ;MAC3CT,KAAK,CAACQ,IAAI,CAACJ,IAAI,CAAC;MAChBF,KAAK,GAAGO,QAAQ;MAEhB,OAAO,IAAI;IACb,CAAC;IAEDC,MAAMA,CAACD,QAAe,EAAQ;MAC5BP,KAAK,GAAGO,QAAQ;MAEhB,OAAO,IAAI;IACb;EACF,CAAC;AACH;AAEA,SAASJ,IAAIA,CAAIM,KAAU,EAAiB;EAC1C,OAAOA,KAAK,CAACA,KAAK,CAACC,MAAM,GAAG,CAAC,CAAC;AAChC","ignoreList":[]}
|
||||
31
node_modules/jsonrepair/lib/esm/streaming/stream.js
generated
vendored
Normal file
31
node_modules/jsonrepair/lib/esm/streaming/stream.js
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { Transform } from 'node:stream';
|
||||
import { jsonrepairCore } from './core.js';
|
||||
export function jsonrepairTransform(options) {
|
||||
const repair = jsonrepairCore({
|
||||
onData: chunk => transform.push(chunk),
|
||||
bufferSize: options?.bufferSize,
|
||||
chunkSize: options?.chunkSize
|
||||
});
|
||||
const transform = new Transform({
|
||||
transform(chunk, _encoding, callback) {
|
||||
try {
|
||||
repair.transform(chunk.toString());
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
} finally {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
flush(callback) {
|
||||
try {
|
||||
repair.flush();
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
} finally {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
return transform;
|
||||
}
|
||||
//# sourceMappingURL=stream.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/streaming/stream.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/streaming/stream.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.js","names":["Transform","jsonrepairCore","jsonrepairTransform","options","repair","onData","chunk","transform","push","bufferSize","chunkSize","_encoding","callback","toString","err","emit","flush"],"sources":["../../../src/streaming/stream.ts"],"sourcesContent":["import { Transform } from 'node:stream'\nimport { jsonrepairCore } from './core.js'\n\nexport interface JsonRepairTransformOptions {\n chunkSize?: number\n bufferSize?: number\n}\n\nexport function jsonrepairTransform(options?: JsonRepairTransformOptions): Transform {\n const repair = jsonrepairCore({\n onData: (chunk) => transform.push(chunk),\n bufferSize: options?.bufferSize,\n chunkSize: options?.chunkSize\n })\n\n const transform = new Transform({\n transform(chunk, _encoding, callback) {\n try {\n repair.transform(chunk.toString())\n } catch (err) {\n this.emit('error', err)\n } finally {\n callback()\n }\n },\n\n flush(callback) {\n try {\n repair.flush()\n } catch (err) {\n this.emit('error', err)\n } finally {\n callback()\n }\n }\n })\n\n return transform\n}\n"],"mappings":"AAAA,SAASA,SAAS,QAAQ,aAAa;AACvC,SAASC,cAAc,QAAQ,WAAW;AAO1C,OAAO,SAASC,mBAAmBA,CAACC,OAAoC,EAAa;EACnF,MAAMC,MAAM,GAAGH,cAAc,CAAC;IAC5BI,MAAM,EAAGC,KAAK,IAAKC,SAAS,CAACC,IAAI,CAACF,KAAK,CAAC;IACxCG,UAAU,EAAEN,OAAO,EAAEM,UAAU;IAC/BC,SAAS,EAAEP,OAAO,EAAEO;EACtB,CAAC,CAAC;EAEF,MAAMH,SAAS,GAAG,IAAIP,SAAS,CAAC;IAC9BO,SAASA,CAACD,KAAK,EAAEK,SAAS,EAAEC,QAAQ,EAAE;MACpC,IAAI;QACFR,MAAM,CAACG,SAAS,CAACD,KAAK,CAACO,QAAQ,CAAC,CAAC,CAAC;MACpC,CAAC,CAAC,OAAOC,GAAG,EAAE;QACZ,IAAI,CAACC,IAAI,CAAC,OAAO,EAAED,GAAG,CAAC;MACzB,CAAC,SAAS;QACRF,QAAQ,CAAC,CAAC;MACZ;IACF,CAAC;IAEDI,KAAKA,CAACJ,QAAQ,EAAE;MACd,IAAI;QACFR,MAAM,CAACY,KAAK,CAAC,CAAC;MAChB,CAAC,CAAC,OAAOF,GAAG,EAAE;QACZ,IAAI,CAACC,IAAI,CAAC,OAAO,EAAED,GAAG,CAAC;MACzB,CAAC,SAAS;QACRF,QAAQ,CAAC,CAAC;MACZ;IACF;EACF,CAAC,CAAC;EAEF,OAAOL,SAAS;AAClB","ignoreList":[]}
|
||||
7
node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js
generated
vendored
Normal file
7
node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export class JSONRepairError extends Error {
|
||||
constructor(message, position) {
|
||||
super(`${message} at position ${position}`);
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=JSONRepairError.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"JSONRepairError.js","names":["JSONRepairError","Error","constructor","message","position"],"sources":["../../../src/utils/JSONRepairError.ts"],"sourcesContent":["export class JSONRepairError extends Error {\n position: number\n\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`)\n\n this.position = position\n }\n}\n"],"mappings":"AAAA,OAAO,MAAMA,eAAe,SAASC,KAAK,CAAC;EAGzCC,WAAWA,CAACC,OAAe,EAAEC,QAAgB,EAAE;IAC7C,KAAK,CAAC,GAAGD,OAAO,gBAAgBC,QAAQ,EAAE,CAAC;IAE3C,IAAI,CAACA,QAAQ,GAAGA,QAAQ;EAC1B;AACF","ignoreList":[]}
|
||||
147
node_modules/jsonrepair/lib/esm/utils/stringUtils.js
generated
vendored
Normal file
147
node_modules/jsonrepair/lib/esm/utils/stringUtils.js
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
const codeSpace = 0x20; // " "
|
||||
const codeNewline = 0xa; // "\n"
|
||||
const codeTab = 0x9; // "\t"
|
||||
const codeReturn = 0xd; // "\r"
|
||||
const codeNonBreakingSpace = 0xa0;
|
||||
const codeEnQuad = 0x2000;
|
||||
const codeHairSpace = 0x200a;
|
||||
const codeNarrowNoBreakSpace = 0x202f;
|
||||
const codeMediumMathematicalSpace = 0x205f;
|
||||
const codeIdeographicSpace = 0x3000;
|
||||
export function isHex(char) {
|
||||
return /^[0-9A-Fa-f]$/.test(char);
|
||||
}
|
||||
export function isDigit(char) {
|
||||
return char >= '0' && char <= '9';
|
||||
}
|
||||
export function isValidStringCharacter(char) {
|
||||
// note that the valid range is between \u{0020} and \u{10ffff},
|
||||
// but in JavaScript it is not possible to create a code point larger than
|
||||
// \u{10ffff}, so there is no need to test for that here.
|
||||
return char >= '\u0020';
|
||||
}
|
||||
export function isDelimiter(char) {
|
||||
return ',:[]/{}()\n+'.includes(char);
|
||||
}
|
||||
export function isFunctionNameCharStart(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$';
|
||||
}
|
||||
export function isFunctionNameChar(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$' || char >= '0' && char <= '9';
|
||||
}
|
||||
|
||||
// matches "https://" and other schemas
|
||||
export const regexUrlStart = /^(http|https|ftp|mailto|file|data|irc):\/\/$/;
|
||||
|
||||
// matches all valid URL characters EXCEPT "[", "]", and ",", since that are important JSON delimiters
|
||||
export const regexUrlChar = /^[A-Za-z0-9-._~:/?#@!$&'()*+;=]$/;
|
||||
export function isUnquotedStringDelimiter(char) {
|
||||
return ',[]/{}\n+'.includes(char);
|
||||
}
|
||||
export function isStartOfValue(char) {
|
||||
return isQuote(char) || regexStartOfValue.test(char);
|
||||
}
|
||||
|
||||
// alpha, number, minus, or opening bracket or brace
|
||||
const regexStartOfValue = /^[[{\w-]$/;
|
||||
export function isControlCharacter(char) {
|
||||
return char === '\n' || char === '\r' || char === '\t' || char === '\b' || char === '\f';
|
||||
}
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space, tab, or
|
||||
* newline
|
||||
*/
|
||||
export function isWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeNewline || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space or tab,
|
||||
* but NOT a newline
|
||||
*/
|
||||
export function isWhitespaceExceptNewline(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a special whitespace character, some
|
||||
* unicode variant
|
||||
*/
|
||||
export function isSpecialWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeNonBreakingSpace || code >= codeEnQuad && code <= codeHairSpace || code === codeNarrowNoBreakSpace || code === codeMediumMathematicalSpace || code === codeIdeographicSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a quote or double quote character.
|
||||
* Also tests for special variants of quotes.
|
||||
*/
|
||||
export function isQuote(char) {
|
||||
// the first check double quotes, since that occurs most often
|
||||
return isDoubleQuoteLike(char) || isSingleQuoteLike(char);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Also tests for special variants of double quotes.
|
||||
*/
|
||||
export function isDoubleQuoteLike(char) {
|
||||
return char === '"' || char === '\u201c' || char === '\u201d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Does NOT test for special variants of double quotes.
|
||||
*/
|
||||
export function isDoubleQuote(char) {
|
||||
return char === '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Also tests for special variants of single quotes.
|
||||
*/
|
||||
export function isSingleQuoteLike(char) {
|
||||
return char === "'" || char === '\u2018' || char === '\u2019' || char === '\u0060' || char === '\u00b4';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Does NOT test for special variants of single quotes.
|
||||
*/
|
||||
export function isSingleQuote(char) {
|
||||
return char === "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip last occurrence of textToStrip from text
|
||||
*/
|
||||
export function stripLastOccurrence(text, textToStrip) {
|
||||
let stripRemainingText = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||||
const index = text.lastIndexOf(textToStrip);
|
||||
return index !== -1 ? text.substring(0, index) + (stripRemainingText ? '' : text.substring(index + 1)) : text;
|
||||
}
|
||||
export function insertBeforeLastWhitespace(text, textToInsert) {
|
||||
let index = text.length;
|
||||
if (!isWhitespace(text, index - 1)) {
|
||||
// no trailing whitespaces
|
||||
return text + textToInsert;
|
||||
}
|
||||
while (isWhitespace(text, index - 1)) {
|
||||
index--;
|
||||
}
|
||||
return text.substring(0, index) + textToInsert + text.substring(index);
|
||||
}
|
||||
export function removeAtIndex(text, start, count) {
|
||||
return text.substring(0, start) + text.substring(start + count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a string ends with a newline or comma character and optional whitespace
|
||||
*/
|
||||
export function endsWithCommaOrNewline(text) {
|
||||
return /[,\n][ \t\r]*$/.test(text);
|
||||
}
|
||||
//# sourceMappingURL=stringUtils.js.map
|
||||
1
node_modules/jsonrepair/lib/esm/utils/stringUtils.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/esm/utils/stringUtils.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/jsonrepair/lib/types/index.d.ts
generated
vendored
Normal file
3
node_modules/jsonrepair/lib/types/index.d.ts
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export { jsonrepair } from './regular/jsonrepair.js';
|
||||
export { JSONRepairError } from './utils/JSONRepairError.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/index.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/index.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA"}
|
||||
18
node_modules/jsonrepair/lib/types/regular/jsonrepair.d.ts
generated
vendored
Normal file
18
node_modules/jsonrepair/lib/types/regular/jsonrepair.d.ts
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Repair a string containing an invalid JSON document.
|
||||
* For example changes JavaScript notation into JSON notation.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* try {
|
||||
* const json = "{name: 'John'}"
|
||||
* const repaired = jsonrepair(json)
|
||||
* console.log(repaired)
|
||||
* // '{"name": "John"}'
|
||||
* } catch (err) {
|
||||
* console.error(err)
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export declare function jsonrepair(text: string): string;
|
||||
//# sourceMappingURL=jsonrepair.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/regular/jsonrepair.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/regular/jsonrepair.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"jsonrepair.d.ts","sourceRoot":"","sources":["../../../src/regular/jsonrepair.ts"],"names":[],"mappings":"AAgDA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAq0B/C"}
|
||||
2
node_modules/jsonrepair/lib/types/stream.d.ts
generated
vendored
Normal file
2
node_modules/jsonrepair/lib/types/stream.d.ts
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { type JsonRepairTransformOptions, jsonrepairTransform } from './streaming/stream.js';
|
||||
//# sourceMappingURL=stream.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/stream.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/stream.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/stream.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA"}
|
||||
14
node_modules/jsonrepair/lib/types/streaming/buffer/InputBuffer.d.ts
generated
vendored
Normal file
14
node_modules/jsonrepair/lib/types/streaming/buffer/InputBuffer.d.ts
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
export interface InputBuffer {
|
||||
push: (chunk: string) => void;
|
||||
flush: (position: number) => void;
|
||||
charAt: (index: number) => string;
|
||||
charCodeAt: (index: number) => number;
|
||||
substring: (start: number, end: number) => string;
|
||||
length: () => number;
|
||||
currentLength: () => number;
|
||||
currentBufferSize: () => number;
|
||||
isEnd: (index: number) => boolean;
|
||||
close: () => void;
|
||||
}
|
||||
export declare function createInputBuffer(): InputBuffer;
|
||||
//# sourceMappingURL=InputBuffer.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/streaming/buffer/InputBuffer.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/streaming/buffer/InputBuffer.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"InputBuffer.d.ts","sourceRoot":"","sources":["../../../../src/streaming/buffer/InputBuffer.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACjC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IACjD,MAAM,EAAE,MAAM,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,MAAM,CAAA;IAC3B,iBAAiB,EAAE,MAAM,MAAM,CAAA;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACjC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAmF/C"}
|
||||
18
node_modules/jsonrepair/lib/types/streaming/buffer/OutputBuffer.d.ts
generated
vendored
Normal file
18
node_modules/jsonrepair/lib/types/streaming/buffer/OutputBuffer.d.ts
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
export interface OutputBuffer {
|
||||
push: (text: string) => void;
|
||||
unshift: (text: string) => void;
|
||||
remove: (start: number, end?: number) => void;
|
||||
insertAt: (index: number, text: string) => void;
|
||||
length: () => number;
|
||||
flush: () => void;
|
||||
stripLastOccurrence: (textToStrip: string, stripRemainingText?: boolean) => void;
|
||||
insertBeforeLastWhitespace: (textToInsert: string) => void;
|
||||
endsWithIgnoringWhitespace: (char: string) => boolean;
|
||||
}
|
||||
export interface OutputBufferOptions {
|
||||
write: (chunk: string) => void;
|
||||
chunkSize: number;
|
||||
bufferSize: number;
|
||||
}
|
||||
export declare function createOutputBuffer({ write, chunkSize, bufferSize }: OutputBufferOptions): OutputBuffer;
|
||||
//# sourceMappingURL=OutputBuffer.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/streaming/buffer/OutputBuffer.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/streaming/buffer/OutputBuffer.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"OutputBuffer.d.ts","sourceRoot":"","sources":["../../../../src/streaming/buffer/OutputBuffer.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,MAAM,EAAE,MAAM,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,IAAI,CAAA;IAEjB,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAChF,0BAA0B,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1D,0BAA0B,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CACtD;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,SAAS,EACT,UAAU,EACX,EAAE,mBAAmB,GAAG,YAAY,CA6HpC"}
|
||||
11
node_modules/jsonrepair/lib/types/streaming/core.d.ts
generated
vendored
Normal file
11
node_modules/jsonrepair/lib/types/streaming/core.d.ts
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
export interface JsonRepairCoreOptions {
|
||||
onData: (chunk: string) => void;
|
||||
chunkSize?: number;
|
||||
bufferSize?: number;
|
||||
}
|
||||
export interface JsonRepairCore {
|
||||
transform: (chunk: string) => void;
|
||||
flush: () => void;
|
||||
}
|
||||
export declare function jsonrepairCore({ onData, bufferSize, chunkSize }: JsonRepairCoreOptions): JsonRepairCore;
|
||||
//# sourceMappingURL=core.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/streaming/core.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/streaming/core.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/streaming/core.ts"],"names":[],"mappings":"AA+CA,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,UAAkB,EAClB,SAAiB,EAClB,EAAE,qBAAqB,GAAG,cAAc,CA49BxC"}
|
||||
20
node_modules/jsonrepair/lib/types/streaming/stack.d.ts
generated
vendored
Normal file
20
node_modules/jsonrepair/lib/types/streaming/stack.d.ts
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
export declare enum Caret {
|
||||
beforeValue = "beforeValue",
|
||||
afterValue = "afterValue",
|
||||
beforeKey = "beforeKey"
|
||||
}
|
||||
export declare enum StackType {
|
||||
root = "root",
|
||||
object = "object",
|
||||
array = "array",
|
||||
ndJson = "ndJson",
|
||||
functionCall = "dataType"
|
||||
}
|
||||
export declare function createStack(): {
|
||||
readonly type: StackType;
|
||||
readonly caret: Caret;
|
||||
pop(): true;
|
||||
push(type: StackType, newCaret: Caret): true;
|
||||
update(newCaret: Caret): true;
|
||||
};
|
||||
//# sourceMappingURL=stack.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/streaming/stack.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/streaming/stack.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../src/streaming/stack.ts"],"names":[],"mappings":"AAAA,oBAAY,KAAK;IACf,WAAW,gBAAgB;IAC3B,UAAU,eAAe;IACzB,SAAS,cAAc;CACxB;AAED,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,YAAY,aAAa;CAC1B;AAED,wBAAgB,WAAW;;;WAahB,IAAI;eAOA,SAAS,YAAY,KAAK,GAAG,IAAI;qBAO3B,KAAK,GAAG,IAAI;EAMhC"}
|
||||
7
node_modules/jsonrepair/lib/types/streaming/stream.d.ts
generated
vendored
Normal file
7
node_modules/jsonrepair/lib/types/streaming/stream.d.ts
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import { Transform } from 'node:stream';
|
||||
export interface JsonRepairTransformOptions {
|
||||
chunkSize?: number;
|
||||
bufferSize?: number;
|
||||
}
|
||||
export declare function jsonrepairTransform(options?: JsonRepairTransformOptions): Transform;
|
||||
//# sourceMappingURL=stream.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/streaming/stream.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/streaming/stream.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/streaming/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGvC,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,SAAS,CA8BnF"}
|
||||
5
node_modules/jsonrepair/lib/types/utils/JSONRepairError.d.ts
generated
vendored
Normal file
5
node_modules/jsonrepair/lib/types/utils/JSONRepairError.d.ts
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export declare class JSONRepairError extends Error {
|
||||
position: number;
|
||||
constructor(message: string, position: number);
|
||||
}
|
||||
//# sourceMappingURL=JSONRepairError.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/utils/JSONRepairError.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/utils/JSONRepairError.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"JSONRepairError.d.ts","sourceRoot":"","sources":["../../../src/utils/JSONRepairError.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,EAAE,MAAM,CAAA;gBAEJ,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAK9C"}
|
||||
65
node_modules/jsonrepair/lib/types/utils/stringUtils.d.ts
generated
vendored
Normal file
65
node_modules/jsonrepair/lib/types/utils/stringUtils.d.ts
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
export declare function isHex(char: string): boolean;
|
||||
export declare function isDigit(char: string): boolean;
|
||||
export declare function isValidStringCharacter(char: string): boolean;
|
||||
export declare function isDelimiter(char: string): boolean;
|
||||
export declare function isFunctionNameCharStart(char: string): boolean;
|
||||
export declare function isFunctionNameChar(char: string): boolean;
|
||||
export declare const regexUrlStart: RegExp;
|
||||
export declare const regexUrlChar: RegExp;
|
||||
export declare function isUnquotedStringDelimiter(char: string): boolean;
|
||||
export declare function isStartOfValue(char: string): boolean;
|
||||
export declare function isControlCharacter(char: string): char is "\n" | "\r" | "\t" | "\b" | "\f";
|
||||
export interface Text {
|
||||
charCodeAt: (index: number) => number;
|
||||
}
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space, tab, or
|
||||
* newline
|
||||
*/
|
||||
export declare function isWhitespace(text: Text, index: number): boolean;
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space or tab,
|
||||
* but NOT a newline
|
||||
*/
|
||||
export declare function isWhitespaceExceptNewline(text: Text, index: number): boolean;
|
||||
/**
|
||||
* Check if the given character is a special whitespace character, some
|
||||
* unicode variant
|
||||
*/
|
||||
export declare function isSpecialWhitespace(text: Text, index: number): boolean;
|
||||
/**
|
||||
* Test whether the given character is a quote or double quote character.
|
||||
* Also tests for special variants of quotes.
|
||||
*/
|
||||
export declare function isQuote(char: string): boolean;
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Also tests for special variants of double quotes.
|
||||
*/
|
||||
export declare function isDoubleQuoteLike(char: string): boolean;
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Does NOT test for special variants of double quotes.
|
||||
*/
|
||||
export declare function isDoubleQuote(char: string): boolean;
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Also tests for special variants of single quotes.
|
||||
*/
|
||||
export declare function isSingleQuoteLike(char: string): boolean;
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Does NOT test for special variants of single quotes.
|
||||
*/
|
||||
export declare function isSingleQuote(char: string): boolean;
|
||||
/**
|
||||
* Strip last occurrence of textToStrip from text
|
||||
*/
|
||||
export declare function stripLastOccurrence(text: string, textToStrip: string, stripRemainingText?: boolean): string;
|
||||
export declare function insertBeforeLastWhitespace(text: string, textToInsert: string): string;
|
||||
export declare function removeAtIndex(text: string, start: number, count: number): string;
|
||||
/**
|
||||
* Test whether a string ends with a newline or comma character and optional whitespace
|
||||
*/
|
||||
export declare function endsWithCommaOrNewline(text: string): boolean;
|
||||
//# sourceMappingURL=stringUtils.d.ts.map
|
||||
1
node_modules/jsonrepair/lib/types/utils/stringUtils.d.ts.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/types/utils/stringUtils.d.ts.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stringUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/stringUtils.ts"],"names":[],"mappings":"AAWA,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3C;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,WAInD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,WAQ9C;AAGD,eAAO,MAAM,aAAa,QAAiD,CAAA;AAG3E,eAAO,MAAM,YAAY,QAAqC,CAAA;AAE9D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAKD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,4CAE9C;AAED,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACtC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI5E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAUtE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG7C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,kBAAkB,UAAQ,GACzB,MAAM,CAKR;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAarF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAEvE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|
||||
903
node_modules/jsonrepair/lib/umd/jsonrepair.js
generated
vendored
Normal file
903
node_modules/jsonrepair/lib/umd/jsonrepair.js
generated
vendored
Normal file
@ -0,0 +1,903 @@
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JSONRepair = {}));
|
||||
})(this, (function (exports) { 'use strict';
|
||||
|
||||
class JSONRepairError extends Error {
|
||||
constructor(message, position) {
|
||||
super(`${message} at position ${position}`);
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
const codeSpace = 0x20; // " "
|
||||
const codeNewline = 0xa; // "\n"
|
||||
const codeTab = 0x9; // "\t"
|
||||
const codeReturn = 0xd; // "\r"
|
||||
const codeNonBreakingSpace = 0xa0;
|
||||
const codeEnQuad = 0x2000;
|
||||
const codeHairSpace = 0x200a;
|
||||
const codeNarrowNoBreakSpace = 0x202f;
|
||||
const codeMediumMathematicalSpace = 0x205f;
|
||||
const codeIdeographicSpace = 0x3000;
|
||||
function isHex(char) {
|
||||
return /^[0-9A-Fa-f]$/.test(char);
|
||||
}
|
||||
function isDigit(char) {
|
||||
return char >= '0' && char <= '9';
|
||||
}
|
||||
function isValidStringCharacter(char) {
|
||||
// note that the valid range is between \u{0020} and \u{10ffff},
|
||||
// but in JavaScript it is not possible to create a code point larger than
|
||||
// \u{10ffff}, so there is no need to test for that here.
|
||||
return char >= '\u0020';
|
||||
}
|
||||
function isDelimiter(char) {
|
||||
return ',:[]/{}()\n+'.includes(char);
|
||||
}
|
||||
function isFunctionNameCharStart(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$';
|
||||
}
|
||||
function isFunctionNameChar(char) {
|
||||
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char === '_' || char === '$' || char >= '0' && char <= '9';
|
||||
}
|
||||
|
||||
// matches "https://" and other schemas
|
||||
const regexUrlStart = /^(http|https|ftp|mailto|file|data|irc):\/\/$/;
|
||||
|
||||
// matches all valid URL characters EXCEPT "[", "]", and ",", since that are important JSON delimiters
|
||||
const regexUrlChar = /^[A-Za-z0-9-._~:/?#@!$&'()*+;=]$/;
|
||||
function isUnquotedStringDelimiter(char) {
|
||||
return ',[]/{}\n+'.includes(char);
|
||||
}
|
||||
function isStartOfValue(char) {
|
||||
return isQuote(char) || regexStartOfValue.test(char);
|
||||
}
|
||||
|
||||
// alpha, number, minus, or opening bracket or brace
|
||||
const regexStartOfValue = /^[[{\w-]$/;
|
||||
function isControlCharacter(char) {
|
||||
return char === '\n' || char === '\r' || char === '\t' || char === '\b' || char === '\f';
|
||||
}
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space, tab, or
|
||||
* newline
|
||||
*/
|
||||
function isWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeNewline || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a whitespace character like space or tab,
|
||||
* but NOT a newline
|
||||
*/
|
||||
function isWhitespaceExceptNewline(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeSpace || code === codeTab || code === codeReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character is a special whitespace character, some
|
||||
* unicode variant
|
||||
*/
|
||||
function isSpecialWhitespace(text, index) {
|
||||
const code = text.charCodeAt(index);
|
||||
return code === codeNonBreakingSpace || code >= codeEnQuad && code <= codeHairSpace || code === codeNarrowNoBreakSpace || code === codeMediumMathematicalSpace || code === codeIdeographicSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a quote or double quote character.
|
||||
* Also tests for special variants of quotes.
|
||||
*/
|
||||
function isQuote(char) {
|
||||
// the first check double quotes, since that occurs most often
|
||||
return isDoubleQuoteLike(char) || isSingleQuoteLike(char);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Also tests for special variants of double quotes.
|
||||
*/
|
||||
function isDoubleQuoteLike(char) {
|
||||
return char === '"' || char === '\u201c' || char === '\u201d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a double quote character.
|
||||
* Does NOT test for special variants of double quotes.
|
||||
*/
|
||||
function isDoubleQuote(char) {
|
||||
return char === '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Also tests for special variants of single quotes.
|
||||
*/
|
||||
function isSingleQuoteLike(char) {
|
||||
return char === "'" || char === '\u2018' || char === '\u2019' || char === '\u0060' || char === '\u00b4';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given character is a single quote character.
|
||||
* Does NOT test for special variants of single quotes.
|
||||
*/
|
||||
function isSingleQuote(char) {
|
||||
return char === "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip last occurrence of textToStrip from text
|
||||
*/
|
||||
function stripLastOccurrence(text, textToStrip) {
|
||||
let stripRemainingText = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||||
const index = text.lastIndexOf(textToStrip);
|
||||
return index !== -1 ? text.substring(0, index) + (stripRemainingText ? '' : text.substring(index + 1)) : text;
|
||||
}
|
||||
function insertBeforeLastWhitespace(text, textToInsert) {
|
||||
let index = text.length;
|
||||
if (!isWhitespace(text, index - 1)) {
|
||||
// no trailing whitespaces
|
||||
return text + textToInsert;
|
||||
}
|
||||
while (isWhitespace(text, index - 1)) {
|
||||
index--;
|
||||
}
|
||||
return text.substring(0, index) + textToInsert + text.substring(index);
|
||||
}
|
||||
function removeAtIndex(text, start, count) {
|
||||
return text.substring(0, start) + text.substring(start + count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a string ends with a newline or comma character and optional whitespace
|
||||
*/
|
||||
function endsWithCommaOrNewline(text) {
|
||||
return /[,\n][ \t\r]*$/.test(text);
|
||||
}
|
||||
|
||||
const controlCharacters = {
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
};
|
||||
|
||||
// map with all escape characters
|
||||
const escapeCharacters = {
|
||||
'"': '"',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
b: '\b',
|
||||
f: '\f',
|
||||
n: '\n',
|
||||
r: '\r',
|
||||
t: '\t'
|
||||
// note that \u is handled separately in parseString()
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair a string containing an invalid JSON document.
|
||||
* For example changes JavaScript notation into JSON notation.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* try {
|
||||
* const json = "{name: 'John'}"
|
||||
* const repaired = jsonrepair(json)
|
||||
* console.log(repaired)
|
||||
* // '{"name": "John"}'
|
||||
* } catch (err) {
|
||||
* console.error(err)
|
||||
* }
|
||||
*
|
||||
*/
|
||||
function jsonrepair(text) {
|
||||
let i = 0; // current index in text
|
||||
let output = ''; // generated output
|
||||
|
||||
parseMarkdownCodeBlock(['```', '[```', '{```']);
|
||||
const processed = parseValue();
|
||||
if (!processed) {
|
||||
throwUnexpectedEnd();
|
||||
}
|
||||
parseMarkdownCodeBlock(['```', '```]', '```}']);
|
||||
const processedComma = parseCharacter(',');
|
||||
if (processedComma) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (isStartOfValue(text[i]) && endsWithCommaOrNewline(output)) {
|
||||
// start of a new value after end of the root level object: looks like
|
||||
// newline delimited JSON -> turn into a root level array
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
parseNewlineDelimitedJSON();
|
||||
} else if (processedComma) {
|
||||
// repair: remove trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
}
|
||||
|
||||
// repair redundant end quotes
|
||||
while (text[i] === '}' || text[i] === ']') {
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
if (i >= text.length) {
|
||||
// reached the end of the document properly
|
||||
return output;
|
||||
}
|
||||
throwUnexpectedCharacter();
|
||||
function parseValue() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processed = parseObject() || parseArray() || parseString() || parseNumber() || parseKeywords() || parseUnquotedString(false) || parseRegex();
|
||||
parseWhitespaceAndSkipComments();
|
||||
return processed;
|
||||
}
|
||||
function parseWhitespaceAndSkipComments() {
|
||||
let skipNewline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
const start = i;
|
||||
let changed = parseWhitespace(skipNewline);
|
||||
do {
|
||||
changed = parseComment();
|
||||
if (changed) {
|
||||
changed = parseWhitespace(skipNewline);
|
||||
}
|
||||
} while (changed);
|
||||
return i > start;
|
||||
}
|
||||
function parseWhitespace(skipNewline) {
|
||||
const _isWhiteSpace = skipNewline ? isWhitespace : isWhitespaceExceptNewline;
|
||||
let whitespace = '';
|
||||
while (true) {
|
||||
if (_isWhiteSpace(text, i)) {
|
||||
whitespace += text[i];
|
||||
i++;
|
||||
} else if (isSpecialWhitespace(text, i)) {
|
||||
// repair special whitespace
|
||||
whitespace += ' ';
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace.length > 0) {
|
||||
output += whitespace;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseComment() {
|
||||
// find a block comment '/* ... */'
|
||||
if (text[i] === '/' && text[i + 1] === '*') {
|
||||
// repair block comment by skipping it
|
||||
while (i < text.length && !atEndOfBlockComment(text, i)) {
|
||||
i++;
|
||||
}
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find a line comment '// ...'
|
||||
if (text[i] === '/' && text[i + 1] === '/') {
|
||||
// repair line comment by skipping it
|
||||
while (i < text.length && text[i] !== '\n') {
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseMarkdownCodeBlock(blocks) {
|
||||
// find and skip over a Markdown fenced code block:
|
||||
// ``` ... ```
|
||||
// or
|
||||
// ```json ... ```
|
||||
if (skipMarkdownCodeBlock(blocks)) {
|
||||
if (isFunctionNameCharStart(text[i])) {
|
||||
// strip the optional language specifier like "json"
|
||||
while (i < text.length && isFunctionNameChar(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipMarkdownCodeBlock(blocks) {
|
||||
parseWhitespace(true);
|
||||
for (const block of blocks) {
|
||||
const end = i + block.length;
|
||||
if (text.slice(i, end) === block) {
|
||||
i = end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function parseCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
output += text[i];
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipCharacter(char) {
|
||||
if (text[i] === char) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function skipEscapeCharacter() {
|
||||
return skipCharacter('\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ellipsis like "[1,2,3,...]" or "[1,2,3,...,9]" or "[...,7,8,9]"
|
||||
* or a similar construct in objects.
|
||||
*/
|
||||
function skipEllipsis() {
|
||||
parseWhitespaceAndSkipComments();
|
||||
if (text[i] === '.' && text[i + 1] === '.' && text[i + 2] === '.') {
|
||||
// repair: remove the ellipsis (three dots) and optionally a comma
|
||||
i += 3;
|
||||
parseWhitespaceAndSkipComments();
|
||||
skipCharacter(',');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an object like '{"key": "value"}'
|
||||
*/
|
||||
function parseObject() {
|
||||
if (text[i] === '{') {
|
||||
output += '{';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in {, message: "hi"}
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== '}') {
|
||||
let processedComma;
|
||||
if (!initial) {
|
||||
processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
} else {
|
||||
processedComma = true;
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedKey = parseString() || parseUnquotedString(true);
|
||||
if (!processedKey) {
|
||||
if (text[i] === '}' || text[i] === '{' || text[i] === ']' || text[i] === '[' || text[i] === undefined) {
|
||||
// repair trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
} else {
|
||||
throwObjectKeyExpected();
|
||||
}
|
||||
break;
|
||||
}
|
||||
parseWhitespaceAndSkipComments();
|
||||
const processedColon = parseCharacter(':');
|
||||
const truncatedText = i >= text.length;
|
||||
if (!processedColon) {
|
||||
if (isStartOfValue(text[i]) || truncatedText) {
|
||||
// repair missing colon
|
||||
output = insertBeforeLastWhitespace(output, ':');
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
if (processedColon || truncatedText) {
|
||||
// repair missing object value
|
||||
output += 'null';
|
||||
} else {
|
||||
throwColonExpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text[i] === '}') {
|
||||
output += '}';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing end bracket
|
||||
output = insertBeforeLastWhitespace(output, '}');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array like '["item1", "item2", ...]'
|
||||
*/
|
||||
function parseArray() {
|
||||
if (text[i] === '[') {
|
||||
output += '[';
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: skip leading comma like in [,1,2,3]
|
||||
if (skipCharacter(',')) {
|
||||
parseWhitespaceAndSkipComments();
|
||||
}
|
||||
let initial = true;
|
||||
while (i < text.length && text[i] !== ']') {
|
||||
if (!initial) {
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
skipEllipsis();
|
||||
const processedValue = parseValue();
|
||||
if (!processedValue) {
|
||||
// repair trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text[i] === ']') {
|
||||
output += ']';
|
||||
i++;
|
||||
} else {
|
||||
// repair missing closing array bracket
|
||||
output = insertBeforeLastWhitespace(output, ']');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and repair Newline Delimited JSON (NDJSON):
|
||||
* multiple JSON objects separated by a newline character
|
||||
*/
|
||||
function parseNewlineDelimitedJSON() {
|
||||
// repair NDJSON
|
||||
let initial = true;
|
||||
let processedValue = true;
|
||||
while (processedValue) {
|
||||
if (!initial) {
|
||||
// parse optional comma, insert when missing
|
||||
const processedComma = parseCharacter(',');
|
||||
if (!processedComma) {
|
||||
// repair: add missing comma
|
||||
output = insertBeforeLastWhitespace(output, ',');
|
||||
}
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
processedValue = parseValue();
|
||||
}
|
||||
if (!processedValue) {
|
||||
// repair: remove trailing comma
|
||||
output = stripLastOccurrence(output, ',');
|
||||
}
|
||||
|
||||
// repair: wrap the output inside array brackets
|
||||
output = `[\n${output}\n]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string enclosed by double quotes "...". Can contain escaped quotes
|
||||
* Repair strings enclosed in single quotes or special quotes
|
||||
* Repair an escaped string
|
||||
*
|
||||
* The function can run in two stages:
|
||||
* - First, it assumes the string has a valid end quote
|
||||
* - If it turns out that the string does not have a valid end quote followed
|
||||
* by a delimiter (which should be the case), the function runs again in a
|
||||
* more conservative way, stopping the string at the first next delimiter
|
||||
* and fixing the string by inserting a quote there, or stopping at a
|
||||
* stop index detected in the first iteration.
|
||||
*/
|
||||
function parseString() {
|
||||
let stopAtDelimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
let stopAtIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
|
||||
let skipEscapeChars = text[i] === '\\';
|
||||
if (skipEscapeChars) {
|
||||
// repair: remove the first escape character
|
||||
i++;
|
||||
skipEscapeChars = true;
|
||||
}
|
||||
if (isQuote(text[i])) {
|
||||
// double quotes are correct JSON,
|
||||
// single quotes come from JavaScript for example, we assume it will have a correct single end quote too
|
||||
// otherwise, we will match any double-quote-like start with a double-quote-like end,
|
||||
// or any single-quote-like start with a single-quote-like end
|
||||
const isEndQuote = isDoubleQuote(text[i]) ? isDoubleQuote : isSingleQuote(text[i]) ? isSingleQuote : isSingleQuoteLike(text[i]) ? isSingleQuoteLike : isDoubleQuoteLike;
|
||||
const iBefore = i;
|
||||
const oBefore = output.length;
|
||||
let str = '"';
|
||||
i++;
|
||||
while (true) {
|
||||
if (i >= text.length) {
|
||||
// end of text, we are missing an end quote
|
||||
|
||||
const iPrev = prevNonWhitespaceIndex(i - 1);
|
||||
if (!stopAtDelimiter && isDelimiter(text.charAt(iPrev))) {
|
||||
// if the text ends with a delimiter, like ["hello],
|
||||
// so the missing end quote should be inserted before this delimiter
|
||||
// retry parsing the string, stopping at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (i === stopAtIndex) {
|
||||
// use the stop index detected in the first iteration, and repair end quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
return true;
|
||||
}
|
||||
if (isEndQuote(text[i])) {
|
||||
// end quote
|
||||
// let us check what is before and after the quote to verify whether this is a legit end quote
|
||||
const iQuote = i;
|
||||
const oQuote = str.length;
|
||||
str += '"';
|
||||
i++;
|
||||
output += str;
|
||||
parseWhitespaceAndSkipComments(false);
|
||||
if (stopAtDelimiter || i >= text.length || isDelimiter(text[i]) || isQuote(text[i]) || isDigit(text[i])) {
|
||||
// The quote is followed by the end of the text, a delimiter,
|
||||
// or a next value. So the quote is indeed the end of the string.
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
}
|
||||
const iPrevChar = prevNonWhitespaceIndex(iQuote - 1);
|
||||
const prevChar = text.charAt(iPrevChar);
|
||||
if (prevChar === ',') {
|
||||
// A comma followed by a quote, like '{"a":"b,c,"d":"e"}'.
|
||||
// We assume that the quote is a start quote, and that the end quote
|
||||
// should have been located right before the comma but is missing.
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(false, iPrevChar);
|
||||
}
|
||||
if (isDelimiter(prevChar)) {
|
||||
// This is not the right end quote: it is preceded by a delimiter,
|
||||
// and NOT followed by a delimiter. So, there is an end quote missing
|
||||
// parse the string again and then stop at the first next delimiter
|
||||
i = iBefore;
|
||||
output = output.substring(0, oBefore);
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
// revert to right after the quote but before any whitespace, and continue parsing the string
|
||||
output = output.substring(0, oBefore);
|
||||
i = iQuote + 1;
|
||||
|
||||
// repair unescaped quote
|
||||
str = `${str.substring(0, oQuote)}\\${str.substring(oQuote)}`;
|
||||
} else if (stopAtDelimiter && isUnquotedStringDelimiter(text[i])) {
|
||||
// we're in the mode to stop the string at the first delimiter
|
||||
// because there is an end quote missing
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && regexUrlStart.test(text.substring(iBefore + 1, i + 2))) {
|
||||
while (i < text.length && regexUrlChar.test(text[i])) {
|
||||
str += text[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// repair missing quote
|
||||
str = insertBeforeLastWhitespace(str, '"');
|
||||
output += str;
|
||||
parseConcatenatedString();
|
||||
return true;
|
||||
} else if (text[i] === '\\') {
|
||||
// handle escaped content like \n or \u2605
|
||||
const char = text.charAt(i + 1);
|
||||
const escapeChar = escapeCharacters[char];
|
||||
if (escapeChar !== undefined) {
|
||||
str += text.slice(i, i + 2);
|
||||
i += 2;
|
||||
} else if (char === 'u') {
|
||||
let j = 2;
|
||||
while (j < 6 && isHex(text[i + j])) {
|
||||
j++;
|
||||
}
|
||||
if (j === 6) {
|
||||
str += text.slice(i, i + 6);
|
||||
i += 6;
|
||||
} else if (i + j >= text.length) {
|
||||
// repair invalid or truncated unicode char at the end of the text
|
||||
// by removing the unicode char and ending the string here
|
||||
i = text.length;
|
||||
} else {
|
||||
throwInvalidUnicodeCharacter();
|
||||
}
|
||||
} else {
|
||||
// repair invalid escape character: remove it
|
||||
str += char;
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
// handle regular characters
|
||||
const char = text.charAt(i);
|
||||
if (char === '"' && text[i - 1] !== '\\') {
|
||||
// repair unescaped double quote
|
||||
str += `\\${char}`;
|
||||
i++;
|
||||
} else if (isControlCharacter(char)) {
|
||||
// unescaped control character
|
||||
str += controlCharacters[char];
|
||||
i++;
|
||||
} else {
|
||||
if (!isValidStringCharacter(char)) {
|
||||
throwInvalidCharacter(char);
|
||||
}
|
||||
str += char;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (skipEscapeChars) {
|
||||
// repair: skipped escape character (nothing to do)
|
||||
skipEscapeCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair concatenated strings like "hello" + "world", change this into "helloworld"
|
||||
*/
|
||||
function parseConcatenatedString() {
|
||||
let processed = false;
|
||||
parseWhitespaceAndSkipComments();
|
||||
while (text[i] === '+') {
|
||||
processed = true;
|
||||
i++;
|
||||
parseWhitespaceAndSkipComments();
|
||||
|
||||
// repair: remove the end quote of the first string
|
||||
output = stripLastOccurrence(output, '"', true);
|
||||
const start = output.length;
|
||||
const parsedStr = parseString();
|
||||
if (parsedStr) {
|
||||
// repair: remove the start quote of the second string
|
||||
output = removeAtIndex(output, start, 1);
|
||||
} else {
|
||||
// repair: remove the + because it is not followed by a string
|
||||
output = insertBeforeLastWhitespace(output, '"');
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number like 2.4 or 2.4e6
|
||||
*/
|
||||
function parseNumber() {
|
||||
const start = i;
|
||||
if (text[i] === '-') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in JSON leading zeros like "00789" are not allowed.
|
||||
// We will allow all leading zeros here though and at the end of parseNumber
|
||||
// check against trailing zeros and repair that if needed.
|
||||
// Leading zeros can have meaning, so we should not clear them.
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
if (text[i] === '.') {
|
||||
i++;
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (text[i] === 'e' || text[i] === 'E') {
|
||||
i++;
|
||||
if (text[i] === '-' || text[i] === '+') {
|
||||
i++;
|
||||
}
|
||||
if (atEndOfNumber()) {
|
||||
repairNumberEndingWithNumericSymbol(start);
|
||||
return true;
|
||||
}
|
||||
if (!isDigit(text[i])) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
while (isDigit(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not at the end of the number by this point, allow this to be parsed as another type
|
||||
if (!atEndOfNumber()) {
|
||||
i = start;
|
||||
return false;
|
||||
}
|
||||
if (i > start) {
|
||||
// repair a number with leading zeros like "00789"
|
||||
const num = text.slice(start, i);
|
||||
const hasInvalidLeadingZero = /^0\d/.test(num);
|
||||
output += hasInvalidLeadingZero ? `"${num}"` : num;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keywords true, false, null
|
||||
* Repair Python keywords True, False, None
|
||||
*/
|
||||
function parseKeywords() {
|
||||
return parseKeyword('true', 'true') || parseKeyword('false', 'false') || parseKeyword('null', 'null') ||
|
||||
// repair Python keywords True, False, None
|
||||
parseKeyword('True', 'true') || parseKeyword('False', 'false') || parseKeyword('None', 'null');
|
||||
}
|
||||
function parseKeyword(name, value) {
|
||||
if (text.slice(i, i + name.length) === name) {
|
||||
output += value;
|
||||
i += name.length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair an unquoted string by adding quotes around it
|
||||
* Repair a MongoDB function call like NumberLong("2")
|
||||
* Repair a JSONP function call like callback({...});
|
||||
*/
|
||||
function parseUnquotedString(isKey) {
|
||||
// note that the symbol can end with whitespaces: we stop at the next delimiter
|
||||
// also, note that we allow strings to contain a slash / in order to support repairing regular expressions
|
||||
const start = i;
|
||||
if (isFunctionNameCharStart(text[i])) {
|
||||
while (i < text.length && isFunctionNameChar(text[i])) {
|
||||
i++;
|
||||
}
|
||||
let j = i;
|
||||
while (isWhitespace(text, j)) {
|
||||
j++;
|
||||
}
|
||||
if (text[j] === '(') {
|
||||
// repair a MongoDB function call like NumberLong("2")
|
||||
// repair a JSONP function call like callback({...});
|
||||
i = j + 1;
|
||||
parseValue();
|
||||
if (text[i] === ')') {
|
||||
// repair: skip close bracket of function call
|
||||
i++;
|
||||
if (text[i] === ';') {
|
||||
// repair: skip semicolon after JSONP call
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (i < text.length && !isUnquotedStringDelimiter(text[i]) && !isQuote(text[i]) && (!isKey || text[i] !== ':')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// test start of an url like "https://..." (this would be parsed as a comment)
|
||||
if (text[i - 1] === ':' && regexUrlStart.test(text.substring(start, i + 2))) {
|
||||
while (i < text.length && regexUrlChar.test(text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i > start) {
|
||||
// repair unquoted string
|
||||
// also, repair undefined into null
|
||||
|
||||
// first, go back to prevent getting trailing whitespaces in the string
|
||||
while (isWhitespace(text, i - 1) && i > 0) {
|
||||
i--;
|
||||
}
|
||||
const symbol = text.slice(start, i);
|
||||
output += symbol === 'undefined' ? 'null' : JSON.stringify(symbol);
|
||||
if (text[i] === '"') {
|
||||
// we had a missing start quote, but now we encountered the end quote, so we can skip that one
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function parseRegex() {
|
||||
if (text[i] === '/') {
|
||||
const start = i;
|
||||
i++;
|
||||
while (i < text.length && (text[i] !== '/' || text[i - 1] === '\\')) {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
output += `"${text.substring(start, i)}"`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function prevNonWhitespaceIndex(start) {
|
||||
let prev = start;
|
||||
while (prev > 0 && isWhitespace(text, prev)) {
|
||||
prev--;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
function atEndOfNumber() {
|
||||
return i >= text.length || isDelimiter(text[i]) || isWhitespace(text, i);
|
||||
}
|
||||
function repairNumberEndingWithNumericSymbol(start) {
|
||||
// repair numbers cut off at the end
|
||||
// this will only be called when we end after a '.', '-', or 'e' and does not
|
||||
// change the number more than it needs to make it valid JSON
|
||||
output += `${text.slice(start, i)}0`;
|
||||
}
|
||||
function throwInvalidCharacter(char) {
|
||||
throw new JSONRepairError(`Invalid character ${JSON.stringify(char)}`, i);
|
||||
}
|
||||
function throwUnexpectedCharacter() {
|
||||
throw new JSONRepairError(`Unexpected character ${JSON.stringify(text[i])}`, i);
|
||||
}
|
||||
function throwUnexpectedEnd() {
|
||||
throw new JSONRepairError('Unexpected end of json string', text.length);
|
||||
}
|
||||
function throwObjectKeyExpected() {
|
||||
throw new JSONRepairError('Object key expected', i);
|
||||
}
|
||||
function throwColonExpected() {
|
||||
throw new JSONRepairError('Colon expected', i);
|
||||
}
|
||||
function throwInvalidUnicodeCharacter() {
|
||||
const chars = text.slice(i, i + 6);
|
||||
throw new JSONRepairError(`Invalid unicode character "${chars}"`, i);
|
||||
}
|
||||
}
|
||||
function atEndOfBlockComment(text, i) {
|
||||
return text[i] === '*' && text[i + 1] === '/';
|
||||
}
|
||||
|
||||
exports.JSONRepairError = JSONRepairError;
|
||||
exports.jsonrepair = jsonrepair;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=jsonrepair.js.map
|
||||
1
node_modules/jsonrepair/lib/umd/jsonrepair.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/umd/jsonrepair.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/jsonrepair/lib/umd/jsonrepair.min.js
generated
vendored
Normal file
3
node_modules/jsonrepair/lib/umd/jsonrepair.min.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
((t,n)=>{"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).JSONRepair={})})(this,function(t){class x extends Error{constructor(t,n){super(t+" at position "+n),this.position=n}}let r=32,e=10,i=9,f=13,a=160,h=8192,y=8202,O=8239,N=8287,J=12288;function S(t){return"0"<=t&&t<="9"}function j(t){return",:[]/{}()\n+".includes(t)}function k(t){return"a"<=t&&t<="z"||"A"<=t&&t<="Z"||"_"===t||"$"===t}function C(t){return"a"<=t&&t<="z"||"A"<=t&&t<="Z"||"_"===t||"$"===t||"0"<=t&&t<="9"}let m=/^(http|https|ftp|mailto|file|data|irc):\/\/$/,z=/^[A-Za-z0-9-._~:/?#@!$&'()*+;=]$/;function E(t){return",[]/{}\n+".includes(t)}function I(t){return _(t)||n.test(t)}let n=/^[[{\w-]$/;function T(t,n){t=t.charCodeAt(n);return t===r||t===e||t===i||t===f}function Z(t,n){t=t.charCodeAt(n);return t===r||t===i||t===f}function _(t){return F(t)||U(t)}function F(t){return'"'===t||"“"===t||"”"===t}function R(t){return'"'===t}function U(t){return"'"===t||"‘"===t||"’"===t||"`"===t||"´"===t}function q(t){return"'"===t}function B(t,n,r){r=2<arguments.length&&void 0!==r&&r,n=t.lastIndexOf(n);return-1!==n?t.substring(0,n)+(r?"":t.substring(n+1)):t}function D(t,n){let r=t.length;if(!T(t,r-1))return t+n;for(;T(t,r-1);)r--;return t.substring(0,r)+n+t.substring(r)}let G={"\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t"},H={'"':'"',"\\":"\\","/":"/",b:"\b",f:"\f",n:"\n",r:"\r",t:"\t"};t.JSONRepairError=x,t.jsonrepair=function(g){let d=0,v="";if(n(["```","[```","{```"]),!f())throw new x("Unexpected end of json string",g.length);n(["```","```]","```}"]);var t=u(",");if(t&&b(),I(g[d])&&/[,\n][ \t\r]*$/.test(v)){t||(v=D(v,","));{let t=!0,n=!0;for(;n;)t?t=!1:u(",")||(v=D(v,",")),n=f();v=`[
|
||||
${v=n?v:B(v,",")}
|
||||
]`}}else t&&(v=B(v,","));for(;"}"===g[d]||"]"===g[d];)d++,b();if(d>=g.length)return v;throw new x("Unexpected character "+JSON.stringify(g[d]),d);function f(){b();var t=(()=>{if("{"!==g[d])return!1;{v+="{",d++,b(),p(",")&&b();let n=!0;for(;d<g.length&&"}"!==g[d];){let t;if(n?(t=!0,n=!1):((t=u(","))||(v=D(v,",")),b()),o(),!(w()||l(!0))){"}"===g[d]||"{"===g[d]||"]"===g[d]||"["===g[d]||void 0===g[d]?v=B(v,","):(()=>{throw new x("Object key expected",d)})();break}b();var r=u(":"),e=d>=g.length,i=(r||(I(g[d])||e?v=D(v,":"):c()),f());i||(r||e?v+="null":c())}return"}"===g[d]?(v+="}",d++):v=D(v,"}"),!0}})()||(()=>{if("["!==g[d])return!1;{v+="[",d++,b(),p(",")&&b();let t=!0;for(;d<g.length&&"]"!==g[d];){t?t=!1:u(",")||(v=D(v,",")),o();var n=f();if(!n){v=B(v,",");break}}return"]"===g[d]?(v+="]",d++):v=D(v,"]"),!0}})()||w()||(()=>{var t,n,r=d;if("-"===g[d]){if(d++,i())return s(r),!0;if(!S(g[d]))return d=r,!1}for(;S(g[d]);)d++;if("."===g[d]){if(d++,i())return s(r),!0;if(!S(g[d]))return d=r,!1;for(;S(g[d]);)d++}if("e"===g[d]||"E"===g[d]){if(d++,"-"!==g[d]&&"+"!==g[d]||d++,i())return s(r),!0;if(!S(g[d]))return d=r,!1;for(;S(g[d]);)d++}if(i()){if(d>r)return t=g.slice(r,d),n=/^0\d/.test(t),v+=n?`"${t}"`:t,!0}else d=r;return!1})()||r("true","true")||r("false","false")||r("null","null")||r("True","true")||r("False","false")||r("None","null")||l(!1)||(()=>{if("/"===g[d]){var t=d;for(d++;d<g.length&&("/"!==g[d]||"\\"===g[d-1]);)d++;return d++,v+=`"${g.substring(t,d)}"`,!0}})();return b(),t}function b(t){var n=!(0<arguments.length&&void 0!==t)||t;d;let r=e(n);for(;r=(r=(()=>{if("/"===g[d]&&"*"===g[d+1]){for(;d<g.length&&!((t,n)=>"*"===t[n]&&"/"===t[n+1])(g,d);)d++;d+=2}else{if("/"!==g[d]||"/"!==g[d+1])return!1;for(;d<g.length&&"\n"!==g[d];)d++}return!0})())&&e(n););d}function e(t){var n,r,e=t?T:Z;let i="";for(;;){if(e(g,d))i+=g[d];else{if(n=g,r=d,!((n=n.charCodeAt(r))===a||n>=h&&n<=y||n===O||n===N||n===J))break;i+=" "}d++}return 0<i.length&&(v+=i,!0)}function n(t){if((t=>{e(!0);for(var n of t){var r=d+n.length;if(g.slice(d,r)===n)return d=r,1}})(t)){if(k(g[d]))for(;d<g.length&&C(g[d]);)d++;b()}}function u(t){return g[d]===t&&(v+=g[d],d++,!0)}function p(t){return g[d]===t&&(d++,!0)}function o(){b(),"."===g[d]&&"."===g[d+1]&&"."===g[d+2]&&(d+=3,b(),p(","))}function w(t,n){var r=0<arguments.length&&void 0!==t&&t,e=1<arguments.length&&void 0!==n?n:-1;let i="\\"===g[d];if(i&&(d++,i=!0),_(g[d])){var f=R(g[d])?R:q(g[d])?q:U(g[d])?U:F,u=d,o=v.length;let n='"';for(d++;;){if(d>=g.length)return l=A(d-1),!r&&j(g.charAt(l))?(d=u,v=v.substring(0,o),w(!0)):(n=D(n,'"'),v+=n,!0);if(d===e)return n=D(n,'"'),v+=n,!0;if(f(g[d])){var l=d,s=n.length;if(n+='"',d++,v+=n,b(!1),r||d>=g.length||j(g[d])||_(g[d])||S(g[d]))return $(),!0;var c=A(l-1),a=g.charAt(c);if(","===a)return d=u,v=v.substring(0,o),w(!1,c);if(j(a))return d=u,v=v.substring(0,o),w(!0);v=v.substring(0,o),d=l+1,n=n.substring(0,s)+"\\"+n.substring(s)}else{if(r&&E(g[d])){if(":"===g[d-1]&&m.test(g.substring(u+1,d+2)))for(;d<g.length&&z.test(g[d]);)n+=g[d],d++;return n=D(n,'"'),v+=n,$(),!0}if("\\"===g[d]){c=g.charAt(d+1);if(void 0!==H[c])n+=g.slice(d,d+2),d+=2;else if("u"===c){let t=2;for(;t<6&&/^[0-9A-Fa-f]$/.test(g[d+t]);)t++;if(6===t)n+=g.slice(d,d+6),d+=6;else{if(!(d+t>=g.length))throw a=void 0,a=g.slice(d,d+6),new x(`Invalid unicode character "${a}"`,d);d=g.length}}else n+=c,d+=2}else{var h,s=g.charAt(d);if('"'===s&&"\\"!==g[d-1])n+="\\"+s;else if("\n"===(h=s)||"\r"===h||"\t"===h||"\b"===h||"\f"===h)n+=G[s];else{if(!(" "<=s))throw h=void 0,h=s,new x("Invalid character "+JSON.stringify(h),d);n+=s}d++}}i&&p("\\")}}return!1}function $(){let t=!1;for(b();"+"===g[d];){t=!0,d++,b();var n=(v=B(v,'"',!0)).length,r=w();v=r?(r=v,n=n,e=1,r.substring(0,n)+r.substring(n+e)):D(v,'"')}var e;t}function r(t,n){return g.slice(d,d+t.length)===t&&(v+=n,d+=t.length,!0)}function l(t){var n=d;if(k(g[d])){for(;d<g.length&&C(g[d]);)d++;let t=d;for(;T(g,t);)t++;if("("===g[t])return d=t+1,f(),")"===g[d]&&(d++,";"===g[d])&&d++,!0}for(;d<g.length&&!E(g[d])&&!_(g[d])&&(!t||":"!==g[d]);)d++;if(":"===g[d-1]&&m.test(g.substring(n,d+2)))for(;d<g.length&&z.test(g[d]);)d++;if(d>n){for(;T(g,d-1)&&0<d;)d--;n=g.slice(n,d);return v+="undefined"===n?"null":JSON.stringify(n),'"'===g[d]&&d++,!0}}function A(t){let n=t;for(;0<n&&T(g,n);)n--;return n}function i(){return d>=g.length||j(g[d])||T(g,d)}function s(t){v+=g.slice(t,d)+"0"}function c(){throw new x("Colon expected",d)}}});
|
||||
1
node_modules/jsonrepair/lib/umd/jsonrepair.min.js.map
generated
vendored
Normal file
1
node_modules/jsonrepair/lib/umd/jsonrepair.min.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/jsonrepair/lib/umd/package.json
generated
vendored
Normal file
3
node_modules/jsonrepair/lib/umd/package.json
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
91
node_modules/jsonrepair/package.json
generated
vendored
Normal file
91
node_modules/jsonrepair/package.json
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "jsonrepair",
|
||||
"version": "3.13.1",
|
||||
"description": "Repair broken JSON documents",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/josdejong/jsonrepair.git"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "lib/cjs/index.js",
|
||||
"module": "lib/esm/index.js",
|
||||
"browser": "lib/umd/jsonrepair.min.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/esm/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./stream": {
|
||||
"import": "./lib/esm/stream.js",
|
||||
"require": "./lib/cjs/stream.js",
|
||||
"types": "./lib/types/stream.d.ts"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"simple",
|
||||
"json",
|
||||
"repair",
|
||||
"fix",
|
||||
"invalid",
|
||||
"stream",
|
||||
"streaming"
|
||||
],
|
||||
"bin": {
|
||||
"jsonrepair": "./bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest watch src",
|
||||
"test:it": "vitest run src",
|
||||
"build": "npm-run-all build:**",
|
||||
"build:clean": "del-cli lib",
|
||||
"build:esm": "babel src --out-dir lib/esm --extensions \".ts\" --source-maps --config-file ./babel.config.json",
|
||||
"build:cjs": "babel src --out-dir lib/cjs --extensions \".ts\" --source-maps --config-file ./babel-cjs.config.json && cpy tools/cjs lib/cjs --flat",
|
||||
"build:umd": "rollup lib/esm/index.js --format umd --name JSONRepair --sourcemap --output.file lib/umd/jsonrepair.js && cpy tools/cjs/package.json lib/umd --flat",
|
||||
"build:umd:min": "uglifyjs --compress --mangle --source-map --comments --output lib/umd/jsonrepair.min.js -- lib/umd/jsonrepair.js",
|
||||
"build:types": "tsc --project tsconfig-types.json",
|
||||
"build:validate": "vitest run test-lib",
|
||||
"lint": "biome check",
|
||||
"format": "biome check --write",
|
||||
"benchmark": "npm run build:esm && node tools/benchmark/run.mjs",
|
||||
"build-and-test": "npm run lint && npm run test:it && npm run build",
|
||||
"release": "npm-run-all release:**",
|
||||
"release:build-and-test": "npm run build-and-test",
|
||||
"release:version": "standard-version",
|
||||
"release:push": "git push && git push --tag",
|
||||
"release:publish": "npm publish",
|
||||
"release-dry-run": "npm run build-and-test && standard-version --dry-run",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"LICENSE.md",
|
||||
"lib"
|
||||
],
|
||||
"author": "Jos de Jong",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.28.3",
|
||||
"@babel/core": "7.28.4",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.3",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@commitlint/cli": "19.8.1",
|
||||
"@commitlint/config-conventional": "19.8.1",
|
||||
"@types/node": "24.5.2",
|
||||
"cpy-cli": "6.0.0",
|
||||
"del-cli": "7.0.0",
|
||||
"husky": "9.1.7",
|
||||
"npm-run-all": "4.1.5",
|
||||
"rollup": "4.51.0",
|
||||
"standard-version": "9.5.0",
|
||||
"tinybench": "5.0.1",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.2",
|
||||
"uglify-js": "3.19.3",
|
||||
"vitest": "3.2.4"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user