diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 786365d4..59343039 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -11,6 +11,7 @@ ANamespace
Anps
argcomplete
argdesc
+ARGN
argname
argtype
autoapi
diff --git a/src/fprime_gds/common/data_types/cmd_data.py b/src/fprime_gds/common/data_types/cmd_data.py
index b12c285d..ea985833 100644
--- a/src/fprime_gds/common/data_types/cmd_data.py
+++ b/src/fprime_gds/common/data_types/cmd_data.py
@@ -148,13 +148,13 @@ def process_args(self, input_values):
args = []
for val, arg_tuple in zip(input_values, self.template.arguments):
try:
- _, _, arg_type = arg_tuple
+ arg_name, _, arg_type = arg_tuple
arg_value = arg_type()
self.convert_arg_value(val, arg_value)
args.append(arg_value)
errors.append("")
except Exception as exc:
- errors.append(str(exc))
+ errors.append(f"{arg_name}[{arg_type.__name__}]: {exc}")
return args, errors
@staticmethod
diff --git a/src/fprime_gds/flask/commands.py b/src/fprime_gds/flask/commands.py
index 1e8a67e7..3ce20aa7 100644
--- a/src/fprime_gds/flask/commands.py
+++ b/src/fprime_gds/flask/commands.py
@@ -49,7 +49,7 @@ class CommandArgumentsInvalidException(werkzeug.exceptions.BadRequest):
"""Command arguments failed to validate properly"""
def __init__(self, errors):
- super().__init__("Failed to validate all arguments")
+ super().__init__(f"Failed to validate all arguments: {', '.join(errors)}")
self.args = errors
diff --git a/src/fprime_gds/flask/static/addons/commanding/arguments.js b/src/fprime_gds/flask/static/addons/commanding/arguments.js
index 7dc71f03..ce8e9d00 100644
--- a/src/fprime_gds/flask/static/addons/commanding/arguments.js
+++ b/src/fprime_gds/flask/static/addons/commanding/arguments.js
@@ -74,7 +74,7 @@ export function command_argument_assignment_helper(argument, squashed_argument_v
command_argument_array_serializable_assignment_helper(argument, squashed_argument_value);
} else {
let is_not_string = typeof(argument.type.MAX_LENGTH) === "undefined";
- argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value;
+ argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value.toString();
}
}
@@ -119,7 +119,21 @@ export function squashify_argument(argument) {
let field = argument.type.MEMBER_LIST[i][0];
value[field] = squashify_argument(argument.value[field]);
}
- } else if (["U64Type", "U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) != -1) {
+ } else if (["U64Type"].indexOf(argument.type.name) !== -1) {
+ if (argument.value.startsWith("0x")) {
+ // Hexadecimal
+ value = BigInt(argument.value, 16);
+ } else if (argument.value.startsWith("0b")) {
+ // Binary
+ value = BigInt(argument.value.slice(2), 2);
+ } else if (argument.value.startsWith("0o")) {
+ // Octal
+ value = BigInt(argument.value.slice(2), 8);
+ } else {
+ // Decimal
+ value = BigInt(argument.value, 10);
+ }
+ } else if (["U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) !== -1) {
if (argument.value.startsWith("0x")) {
// Hexadecimal
value = parseInt(argument.value, 16);
@@ -134,10 +148,13 @@ export function squashify_argument(argument) {
value = parseInt(argument.value, 10);
}
}
- else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) != -1) {
+ else if (["I64Type"].indexOf(argument.type.name) !== -1) {
+ value = BigInt(argument.value, 10);
+ }
+ else if (["I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) !== -1) {
value = parseInt(argument.value, 10);
}
- else if (["F64Type", "F32Type"].indexOf(argument.type.name) != -1) {
+ else if (["F64Type", "F32Type"].indexOf(argument.type.name) !== -1) {
value = parseFloat(argument.value);
}
else if (argument.type.name == "BoolType") {
@@ -160,22 +177,35 @@ export function squashify_argument(argument) {
* @returns: string to display
*/
export function argument_display_string(argument) {
- // Base assignment of the value
- let string = `${(argument.value == null || argument.value === "") ? FILL_NEEDED: argument.value}`;
-
- if (argument.type.LENGTH) {
- string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
- } else if (argument.type.MEMBER_LIST) {
- let fields = [];
- for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
- let field = argument.type.MEMBER_LIST[i][0];
- fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
+ let string = FILL_NEEDED;
+ try {
+ // Check for array
+ if (argument.type.LENGTH) {
+ string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
+ }
+ // Serializable
+ else if (argument.type.MEMBER_LIST) {
+ let fields = [];
+ for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
+ let field = argument.type.MEMBER_LIST[i][0];
+ fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
+ }
+ string = `{${fields.join(", ")}}`
+ }
+ // String type
+ else if (argument.type.MAX_LENGTH) {
+ let value = (argument.value == null) ? "" : argument.value;
+ value = value.replace(/"/g, '\\\"');
+ string = `"${value}"`
+ }
+ // Unassigned values
+ else if (argument.value == null || argument.value === "") {
+ string = FILL_NEEDED;
+ } else {
+ string = squashify_argument(argument);
}
- string = `{${fields.join(", ")}}`
- } else if (argument.type.MAX_LENGTH) {
- let value = (argument.value == null) ? "" : argument.value;
- value = value.replace(/"/g, '\\\"');
- string = `"${value}"`
+ } catch (e) {
+ string = FILL_NEEDED;
}
return string;
}
@@ -291,7 +321,7 @@ Vue.component("command-scalar-argument", {
return ["text", "0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|[1-9]\\d*|0", ""];
}
else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(this.argument.type.name) != -1) {
- return ["number", null, "1"];
+ return ["text", "-?[1-9]\\d*|0", ""];
}
else if (["F64Type", "F32Type"].indexOf(this.argument.type.name) != -1) {
return ["number", null, "any"];
diff --git a/src/fprime_gds/flask/static/addons/commanding/command-history.js b/src/fprime_gds/flask/static/addons/commanding/command-history.js
index 0807eee6..ac5edcdd 100644
--- a/src/fprime_gds/flask/static/addons/commanding/command-history.js
+++ b/src/fprime_gds/flask/static/addons/commanding/command-history.js
@@ -10,7 +10,8 @@ import {command_argument_assignment_helper} from "./arguments.js";
import {listExistsAndItemNameNotInList, timeToString} from "../../js/vue-support/utils.js";
import {command_history_template} from "./command-history-template.js";
import {command_display_string} from "./command-string.js";
-
+import { SaferParser } from "../../js/json.js";
+SaferParser.register();
/**
* command-history:
@@ -103,27 +104,7 @@ Vue.component("command-history", {
// Can only set command if it is a child of a command input
if (this.$parent.selectCmd) {
// command-input expects an array of strings as arguments
- this.$parent.selectCmd(cmd.full_name, this.preprocess_args(cmd.args));
- }
- },
- /**
- * Process the arguments for a command. If the argument is (or contains) a number, it
- * is converted to a string. Other types that should be pre-processed can be added here.
- *
- * @param {*} args
- * @returns args processed for command input (numbers converted to strings)
- */
- preprocess_args(args) {
- if (Array.isArray(args)) {
- return args.map(el => this.preprocess_args(el));
- } else if (typeof args === 'object' && args !== null) {
- return Object.fromEntries(
- Object.entries(args).map(([key, value]) => [key, this.preprocess_args(value)])
- );
- } else if (typeof args === 'number') {
- return args.toString();
- } else {
- return args;
+ this.$parent.selectCmd(cmd.full_name, cmd.args);
}
}
}
diff --git a/src/fprime_gds/flask/static/addons/commanding/command-input.js b/src/fprime_gds/flask/static/addons/commanding/command-input.js
index aa97950c..0d07381b 100644
--- a/src/fprime_gds/flask/static/addons/commanding/command-input.js
+++ b/src/fprime_gds/flask/static/addons/commanding/command-input.js
@@ -15,6 +15,8 @@ import {
} from "../../addons/commanding/arguments.js";
import {_settings} from "../../js/settings.js";
import {command_input_template} from "./command-input-template.js";
+import { SaferParser } from "../../js/json.js";
+SaferParser.register();
/**
* This helper will help assign command and values in a safe manner by searching the command store, finding a reference,
@@ -38,6 +40,7 @@ function command_assignment_helper(desired_command_name, desired_command_args, p
return null;
}
let selected = _datastore.commands[command_name];
+
// Set arguments here
for (let i = 0; i < selected.args.length; i++) {
let assign_value = (desired_command_args.length > i)? desired_command_args[i] : null;
@@ -147,6 +150,7 @@ Vue.component("command-input", {
* command reaches the ground system.
*/
sendCommand() {
+
// Validate the command before sending anything
if (!this.validate()) {
return;
@@ -158,8 +162,9 @@ Vue.component("command-input", {
let _self = this;
_self.active = true;
let command = this.selected;
+ let squashed_args = command.args.map(serialize_arg);
this.loader.load("/commands/" + command.full_name, "PUT",
- {"key":0xfeedcafe, "arguments": command.args.map(serialize_arg)})
+ {"key":0xfeedcafe, "arguments": squashed_args})
.then(function() {
_self.active = false;
// Clear errors, as there is not a problem further
diff --git a/src/fprime_gds/flask/static/addons/commanding/command-string-template.js b/src/fprime_gds/flask/static/addons/commanding/command-string-template.js
index 9529c83a..b06c84ca 100644
--- a/src/fprime_gds/flask/static/addons/commanding/command-string-template.js
+++ b/src/fprime_gds/flask/static/addons/commanding/command-string-template.js
@@ -3,7 +3,8 @@
*
* Contains the templates used to render the command string input box.
*/
-export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...]";
+export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...] " +
+ "where ARGN is a decimal number, quoted string, or an enumerated constant";
export let command_string_template = `
diff --git a/src/fprime_gds/flask/static/addons/commanding/command-string.js b/src/fprime_gds/flask/static/addons/commanding/command-string.js
index 9a8b5a3f..a79da524 100644
--- a/src/fprime_gds/flask/static/addons/commanding/command-string.js
+++ b/src/fprime_gds/flask/static/addons/commanding/command-string.js
@@ -10,6 +10,8 @@ import {
command_string_template
} from "./command-string-template.js";
import {argument_display_string, FILL_NEEDED} from "./arguments.js"
+import {SaferParser} from "../../js/json.js";
+SaferParser.register();
let STRING_PREPROCESSOR = new RegExp(`(?:"((?:[^\"]|\\\")*)")|([a-zA-Z_][a-zA-Z_0-9.]*)|(${FILL_NEEDED})`, "g");
@@ -61,7 +63,9 @@ Vue.component("command-text", {
} catch (e) {
// JSON parsing exceptions
if (e instanceof SyntaxError) {
- this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}`;
+ this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}.`;
+ } else {
+ throw e;
}
}
}
diff --git a/src/fprime_gds/flask/static/addons/sequencer/addon.js b/src/fprime_gds/flask/static/addons/sequencer/addon.js
index a5c85745..97171dec 100644
--- a/src/fprime_gds/flask/static/addons/sequencer/addon.js
+++ b/src/fprime_gds/flask/static/addons/sequencer/addon.js
@@ -16,6 +16,8 @@ import {_datastore} from "../../js/datastore.js";
import {basicSetup, EditorState, EditorView, linter} from "./third/code-mirror.es.js"
import {sequenceLanguageSupport} from "./autocomplete.js"
import {processResponse} from "./lint.js";
+import { SaferParser } from "../../js/json.js";
+SaferParser.register();
/**
* Sequence sender function used to uplink the sequence and return a promise of how to handle the server's return.
diff --git a/src/fprime_gds/flask/static/js/json.js b/src/fprime_gds/flask/static/js/json.js
new file mode 100644
index 00000000..e92c3044
--- /dev/null
+++ b/src/fprime_gds/flask/static/js/json.js
@@ -0,0 +1,275 @@
+/**
+ * json.js:
+ *
+ * Contains specialized JSON parser to handle non-standard JSON values from the JavaScript perspective. These values
+ * are legal in Python and scala, but not in JavaScript. This parser will safely handle these values.
+ *
+ * @author mstarch
+ */
+
+
+/**
+ * Helper to determine if value is a string
+ * @param value: value to check.
+ * @return {boolean}: true if string, false otherwise
+ */
+function isString(value) {
+ return value instanceof String || typeof value === 'string';
+}
+
+/**
+ * Helper to determine if value is a function
+ * @param value: value to check
+ * @return {boolean}: true if function, false otherwise
+ */
+function isFunction(value) {
+ return value instanceof Function || typeof value == "function";
+}
+
+/**
+ * Conversion function for converting from string to BigInt or Number depending on size
+ * @param {*} string_value: value to convert
+ * @returns: Number for small values and BigInt for large values
+ */
+function convertInt(string_value) {
+ string_value = string_value.trim();
+ let number_value = Number.parseInt(string_value);
+ // When the big and normal numbers match, then return the normal number
+ if (string_value == number_value.toString()) {
+ return number_value;
+ }
+ return BigInt(string_value);
+}
+
+/**
+ * Parser to safely handle potential JSON object from Python. Python can produce some non-standard values (infinities,
+ * NaNs, etc.) These values then break on the JS Javascript parser. To localize these faults, they are replaced before
+ * processing with strings and then formally set during parsing.
+ *
+ * This is done by looking for tokens in unquoted text and replacing them with string representations.
+ *
+ * This parser will handle:
+ * - -Infinity
+ * - Infinity
+ * - NaN
+ * - null
+ * - BigInt
+ */
+export class SaferParser {
+ /**
+ * States representing QUOTED or UNQUOTED text
+ * @type {{QUOTED: number, UNQUOTED: number}}
+ */
+ static STATES = {
+ UNQUOTED: 0,
+ QUOTED: 1
+ };
+ /**
+ * List of mapping tuples for clean parsing: string match, replacement type, and real (post parse) type
+ */
+ static MAPPINGS = [
+ [/(-Infinity)/, -Infinity],
+ [/(Infinity)/, Infinity],
+ [/(NaN)/, NaN],
+ [/(null)/, null],
+ [/( -?\d{10,})/, "bigint", convertInt]
+ ];
+
+
+ // Store the language variants the first time
+ static language_parse = JSON.parse;
+ static language_stringify = JSON.stringify;
+
+ /**
+ * @brief safely process F Prime JSON syntax
+ *
+ * Parse method that will replace JSON.parse. This method pre-processes the string data incoming (to be transformed
+ * into JavaScript objects) for detection of entities not expressible in JavaScript's JSON implementation. This will
+ * replace those entities with a JSON flag object.
+ *
+ * Then the data is processed by the JavaScript built-in JSON parser (now done safely). The reviver function will
+ * safely revive the flag objects into JavaScript representations of those object.
+ *
+ * Handles:
+ * 1. BigInts
+ * 2. Inf/-Inf
+ * 3. NaN
+ * 4. null
+ *
+ * @param json_string: JSON string data containing potentially bad values
+ * @param reviver: reviver function to be combined with our reviver
+ * @return {{}}: Javascript Object representation of data safely represented in JavaScript types
+ */
+ static parse(json_string, reviver) {
+ let converted_data = SaferParser.processUnquoted(json_string, SaferParser.replaceFromString);
+ // Set up a composite reviver of the one passed in and ours
+ let input_reviver = reviver || ((key, value) => value);
+ let full_reviver = (key, value) => input_reviver(key, SaferParser.reviver(key, value));
+ try {
+ let language_parsed = SaferParser.language_parse(converted_data, full_reviver);
+ return language_parsed;
+ } catch (e) {
+ let message = e.toString();
+ const matcher = /line (\d+) column (\d+)/
+
+ // Process the match
+ let snippet = "";
+ let match = message.match(matcher);
+ if (match != null) {
+ let lines = converted_data.split("\n");
+ let line = lines[Number.parseInt(match[1]) - 1]
+ snippet = line.substring(Number.parseInt(match[2]) - 6, Number.parseInt(match[2]) + 5);
+ message += ". Offending snippet: " + snippet;
+ throw new SyntaxError(message);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * @brief safely write the F Prime JSON syntax
+ *
+ * Stringify method that will replace JSON.stringify. This method post-processes the string data outgoing from
+ * JavaScript's built-in stringify method to replace flag-objects with the correct F Prime representation in
+ * JavaScript.
+ *
+ * This uses the javascript stringify handler method to pre-convert unsupported types into a flag object. This flag
+ * object is post-converted into a normal string after JSON.stringify has done its best.
+ *
+ * Handles:
+ * 1. BigInts
+ * 2. Inf/-Inf
+ * 3. NaN
+ * 4. null
+ *
+ * @param data: data object to stringify
+ * @param replacer: replacer Array or Function
+ * @param space: space for passing into JSON.stringify
+ * @return {{}}: JSON string using JSON support for big-ints Int/-Inf, NaN and null.
+ */
+ static stringify(data, replacer, space) {
+ let full_replacer = (key, value) => {
+ // Handle array case for excluded field
+ if (Array.isArray(replacer) && replacer.indexOf(key) === -1) {
+ return undefined;
+ }
+ // Run input replacer first
+ else if (isFunction(replacer)) {
+ value = replacer(key, value);
+ }
+ // Then run our safe replacer
+ let replaced = SaferParser.replaceFromObject(key, value);
+ return replaced;
+ };
+ // Stringify JSON using built-in JSON parser and the special replacer
+ let json_string = SaferParser.language_stringify(data, full_replacer, space);
+ // Post-process JSON string to rework JSON into the wider specification
+ let post_replace = SaferParser.postReplacer(json_string);
+ return post_replace
+ }
+
+ /**
+ * Get replacement object from a JavaScript type
+ * @param value: value to replace
+ */
+ static replaceFromObject(_, value) {
+ for (let i = 0; i < SaferParser.MAPPINGS.length; i++) {
+ let mapper_type = SaferParser.MAPPINGS[i][1];
+ let mapper_is_string = isString(mapper_type);
+ // Check if the mapping matches the value, if so substitute a replacement object
+ if ((!mapper_is_string && value == mapper_type) || (mapper_is_string && typeof value == mapper_type)) {
+ return {"fprime{replacement": (value == null) ? "null" : value.toString()};
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Replace JSON notation for fprime-replacement objects with the wider JSON specification
+ *
+ * Replace {"fprime-replacement: "some value"} with restoring the full JSON specification for items not
+ * supported by JavaScript.
+ *
+ * @param json_string: JSON string to rework
+ * @return reworked JSON string
+ */
+ static postReplacer(json_string) {
+ return json_string.replace(/\{\s*"fprime\{replacement"\s*:\s*"([^"]+)"\s*\}/sg, "$1");
+ }
+
+ /**
+ * Replace string occurrences of our gnarly types with a mapping equivalent
+ * @param string_value: value to replace
+ */
+ static replaceFromString(string_value) {
+ for (let i = 0; i < SaferParser.MAPPINGS.length; i++) {
+ let mapper = SaferParser.MAPPINGS[i];
+ string_value = string_value.replace(mapper[0], "{\"fprime{replacement\": \"$1\"}");
+ }
+ return string_value;
+ }
+
+ /**
+ * Apply process function to raw json string only for data that is not qu
+ * @param json_string
+ * @param process_function
+ * @return {string}
+ */
+ static processUnquoted(json_string, process_function) {
+ // The initial state of any JSON string is unquoted
+ let state = SaferParser.STATES.UNQUOTED;
+ let unprocessed = json_string;
+ let transformed_data = "";
+
+ while (unprocessed.length > 0) {
+ let next_quote = unprocessed.indexOf("\"");
+ let section = (next_quote !== -1) ? unprocessed.substring(0, next_quote + 1) : unprocessed.substring(0);
+ unprocessed = unprocessed.substring(section.length);
+ transformed_data += (state === SaferParser.STATES.QUOTED) ? section : process_function(section);
+ state = (state === SaferParser.STATES.QUOTED) ? SaferParser.STATES.UNQUOTED : SaferParser.STATES.QUOTED;
+ }
+ return transformed_data;
+ }
+
+ /**
+ * Inverse of convert removing string and replacing back invalid JSON tokens.
+ * @param key: JSON key
+ * @param value: JSON value search for the converted value.
+ * @return {*}: reverted value or value
+ */
+ static reviver(key, value) {
+ // Look for fprime-replacement and quickly abort if not there
+ let string_value = value["fprime{replacement"];
+ if (typeof string_value === "undefined") {
+ return value;
+ }
+ // Run the mappings looking for a match
+ for (let i = 0; i < SaferParser.MAPPINGS.length; i++) {
+ let mapper = SaferParser.MAPPINGS[i];
+ if (mapper[0].test(string_value)) {
+ // Run the conversion function if it exists, otherwise return the mapped constant value
+ return (mapper.length >= 3) ? mapper[2](string_value) : mapper[1];
+ }
+ }
+ return value;
+ }
+
+ /**
+ * @brief force all calls to JSON.parse and JSON.stringify to use the SafeParser
+ */
+ static register() {
+ // Override the singleton
+ JSON.parse = SaferParser.parse;
+ JSON.stringify = SaferParser.stringify;
+ }
+
+ /**
+ * @brief remove the JSON.parse safe override
+ */
+ static deregister() {
+ JSON.parse = SaferParser.language_parse;
+ JSON.stringify = SaferParser.language_stringify;
+ }
+}
+// Take over all JSON.parse and JSON.stringify calls
+SaferParser.register();
diff --git a/src/fprime_gds/flask/static/js/loader.js b/src/fprime_gds/flask/static/js/loader.js
index 1bece886..d32960d6 100644
--- a/src/fprime_gds/flask/static/js/loader.js
+++ b/src/fprime_gds/flask/static/js/loader.js
@@ -13,7 +13,8 @@
*/
import {config} from "./config.js";
import {_settings} from "./settings.js";
-import {_validator} from "./validate.js";
+import {SaferParser} from "./json.js";
+SaferParser.register();
/**
* Function allowing for the saving of some data to a downloadable file.
@@ -50,118 +51,6 @@ export function loadTextFileInputData(event) {
});
}
-/**
- * Parser to safely handle potential JSON object from Python. Python can produce some non-standard values (infinities,
- * NaNs, etc.) These values then break on the JS Javascript parser. To localize these faults, they are replaced before
- * processing with strings and then formally set during parsing.
- *
- * This is done by looking for tokens in unquoted text and replacing them with string representations.
- *
- */
-class SaferParser {
- /**
- * Set up the parser
- */
- constructor() {
- this.STATES = {
- UNQUOTED: 0,
- QUOTED: 1
- };
- this.FLAG = "-_-您好"; // Extended character usage make collisions less-likely
- this.MAPPINGS = [
- ["-Infinity", this.FLAG + "-inf", -Infinity],
- ["Infinity", this.FLAG + "inf", Infinity],
- ["NaN", this.FLAG + "nan", NaN],
- ["null", this.FLAG + "null", null]
- ];
- this.state = this.STATES.UNQUOTED;
- }
-
- /**
- * Parse method that will replace JSON.parse. This handles known bad cases and also prints better error messages
- * including the working snippets of text.
- * @param rawData: string data
- * @return {{}|any}: Javascript Object representation of data.
- */
- parse(rawData) {
- let converted_data = this.convert(rawData);
- try {
- return JSON.parse(converted_data, this.revert.bind(this));
- } catch (e) {
- let message = e.toString();
- const matcher = /line (\d+) column (\d+)/
-
- // Process the match
- let snippet = "";
- let match = message.match(matcher);
- if (match != null) {
- let lines = converted_data.split("\n");
- let line = lines[Number.parseInt(match[1]) - 1]
- snippet = line.substring(Number.parseInt(match[2]) - 6, Number.parseInt(match[2]) + 5);
- message += ". Offending snippet: " + snippet;
- }
- _validator.updateErrors([message]);
- }
- return {};
- }
-
- /**
- * Convert data from invalid form to strings.
- * @param rawData: raw data including potentially invalid data
- * @return {string}: string data in correct JSON format
- */
- convert(rawData) {
- let unprocessed = rawData;
- let transformed_data = "";
-
- while (unprocessed.length > 0) {
- let next_quote = unprocessed.indexOf("\"");
- let section = (next_quote !== -1) ? unprocessed.substring(0, next_quote + 1) : unprocessed.substring(0);
- unprocessed = unprocessed.substring(section.length);
- transformed_data += this.processChunk(section);
- this.state = (this.state === this.STATES.QUOTED) ? this.STATES.UNQUOTED : this.STATES.QUOTED;
- }
- return transformed_data;
- }
-
- /**
- * Inverse of convert removing string and replacing back invalid JSON tokens.
- * @param key: JSON key
- * @param value: JSON value search for the converted value.
- * @return {*}: reverted value or value
- */
- revert(key, value) {
- for (let i = 0; i < this.MAPPINGS.length; i++) {
- if ((this.MAPPINGS[i][1]) === value) {
- return this.MAPPINGS[i][2];
- }
- }
- return value;
- }
-
- /**
- * Process a section of the JSON string looking for values to convert. This is intended to handle a section of
- * quoted or unquoted text but should never handle quoted and unquoted data in one call.
- * @param section: section of the data
- * @return {*}: converted data
- */
- processChunk(section) {
- // Replaces all the above mappings with a flagged value
- let replace_all = (section) => {
- for (let i = 0; i < this.MAPPINGS.length; i++) {
- section = section.replace(this.MAPPINGS[i][0], "\"" + this.MAPPINGS[i][1] + "\"");
- }
- return section;
- }
-
- // When out of quoted space,
- if (this.state === this.STATES.UNQUOTED) {
- return replace_all(section);
- }
- return section;
- }
-}
-
/**
* Loader:
*
@@ -308,7 +197,7 @@ class Loader {
if (this.readyState === 4 && this.status === 200 && raw) {
resolve(this.responseText);
} else if (this.readyState === 4 && this.status === 200) {
- let dataObj = new SaferParser().parse(this.responseText);
+ let dataObj = JSON.parse(this.responseText);
resolve(dataObj);
} else if(this.readyState === 4) {
reject(this.responseText);