Parse command string with nested quotes into arguments and flags - javascript

I'm trying to create a command parser for a Discord bot for when it receives a message, but I am having issues with dealing with nested quotes. I have made it so that it can parse a string with double quotes and flags, but it does not handle nested quotes.
Here are my requirements:
Handle double quotes.
Handle nested double quotes.
Handle flags (can be anywhere after !command).
A flag without a specified value defaults to a value of true/1.
For example, the following string:
!command that --can "handle double" quotes "and \"nested double\" quotes" --as --well=as --flags="with values"
...should result in the following arguments: command, that, handle double, quotes, and "nested double" quotes and the following flags: "can": true, "as": true, "well": "as", "flags": "with values".
Here is what I have so far:
// splits up the string into separate arguments and flags
const parts = content.slice(1).trim().match(/(--\w+=)?"[^"]*"|[^ "]+/g)
.map(arg => arg.replace(/^"(.*)"$/, '$1'));
// separates the arguments and flags
const [ args, flags ] = parts.reduce((parts, part) => {
// check if flag or argument
if (part.startsWith('--')) {
// check if has a specified value or not
if (part.includes('=')) {
// parses the specified value
part = part.split('=');
const value = part.slice(1)[0];
parts[1][part[0].slice(2)] = value.replace(/^"(.*)"$/, '$1');
} else {
parts[1][part.slice(2)] = true;
}
} else {
parts[0].push(part);
}
return parts;
}, [[], {}]);
This currently parses into the following arguments: command, that, handle double, quotes, and \, nested, double\, quotes and the following flags: "can": true, "as": true, "well": "as", "flags": "with values".

I modified the first RegEx to allow \" in the middle of quoted values. The following line:
const parts = content.slice(1).trim().match(/(--\w+=)?"[^"]*"|[^ "]+/g)
...changed to:
const parts = content.slice(1).trim().match(/(--\S+=)?"(\\"|[^"])*"|[^ "]+/g)
Modifications
The "[^"]*" section was changed to "(\\"|[^"])*" to allow \" to validate, preventing the quoted value from being terminated by quotes with backslashes before them.
I changed the \w in (--\w+=)? to a \S resulting in (--\S+=)? to allow more letters to validate.

Related

What's the difference between the two types of declaration of 'keys' in key value pairs in java script

New to js. I am hardcoding an array of objects in javaScript. the structure of it is something like this
const myArray = [
{id:dummy_String_ID1, name: full_name_with_Spaces1, department:department_withSpaces1},
{id:dummy_String_ID2, name: full_name_with_Spaces2, department:department_withSpaces2},
{id:dummy_String_ID3, name: full_name_with_Spaces3, department:department_withSpaces3}
];
What confuses me is the declaration of these keys( id , name, department), I know the values have to be in inverted quotes. But when shall I use the key fields with inverted quotes (like this "id": "12345", "name": "foo bar", "department": "dept 1" ) and when without them (like this id:"1234", name: "foo bar", dept: "dept 1"). What difference does it make?
A key-value pair can be
identifier: expression
or
string_or_number: expression
or
[expression]: expression
where
identifier is a valid variable name or a keyword, like for or if
string_or_number is a string literal ("foo" or 'bar') or a number literal (like 123)
expression is an arbitrary Javascript expression, which includes, but is not limited to, a literal.
Examples:
foo: "bar"
foo: "bar"[0] + Math.sin(14)
"hi there": "whats up"
[300+1]: "hello"
Note that this is different from JSON, which only allows string literals as keys.
JSON requires you to double-quote all keys of an object. JavaScript does not require this. There is no difference between the following two literals:
{ prop: 42 }
{ "prop": 42 }
But sometimes you want to use a key with characters that are not valid in a JavaScript identifier (leading digits, hyphens, blanks, quotes, colons, etc.), so you must quote the key to become a string which can be used:
{ "person:age": 42 }
{ `age-in-years`: 42 }
{ '"quoted prop"': 42 }
JavaScript allows double and single quotes as well as backticks. Backticks mark template literals and allow you to interpolate placeholders.
If you need dynamic keys, i.e. the value of the key is computed at runtime, you can surround the key with square brackets:
let name = 'computed somewhere else';
let obj = { [name]: 'fancy' }; // will result in {'computed somewhere else': 'fancy'}

How do I use newline characters in an interpolated string with newlines?

I am having issues adding newline characters into a string that already has newlines.
For example:
const foo = "test\ntest";
const string = `mutation {
some_field: "${foo}"
}`;
This will be output as:
mutation {
some_field: "test
test"
}
But I want it to output as:
mutation {
some_field: "test\ntest"
}
Where the existing white-space/newlines are preserved but you can add a string like "test\ntest" inside of it. This current line-breaking is causing syntax errors.
I have tried adding together these strings in various different ways but I can't figure out how to force \n to remain \n in the field value.
When you have \n in a JavaScript string literal, it represents a newline character, not a backslash followed by n. If you want a backslash followed by n, you have to escape the backslash character with another backslash character. That is, you have to put \n in the string literal.
const foo = "test\\ntest";
const string = `mutation {
some_field: "${foo}"
}`;
console.log(string)
You need to put literal \n in the string by escaping the backslash.
const foo = "test\\ntest";
const string = `mutation {
some_field: "${foo}"
}`;
console.log(string);
But this seems like it's fraught with danger. You should almost certainly be using JSON, not a template literal.
const foo = "test\ntest";
const string = `mutation ${JSON.stringify({
some_field: foo
}, null, 4)}`;
console.log(string);

Seemingly odd JSON.parse behaviour in node with double backslashes

A colleague was attempting to parse a json string generated in another system and ran into behavior we were not able to explain. I've replicated the problem with a very small code example here:
// running in node >= 8 (tried both 8 and 12)
const str1 = '{ "test": "path\\test" }';
const str2 = '{ "test": "path\\atest" }';
try {
console.log('str1:', JSON.parse(str1));
} catch (e) {
console.log('str1 fail')
}
try {
console.log('str2:', JSON.parse(str2))
} catch (e) {
console.log('str2 fail')
}
str1 will successfully parse into the desired format of { test: 'path\test' }
str2 will fail.
It seems like this will only succeed if the character immediately following \\ is one of the valid JavaScript escape characters
(\t: horizontal tab in the str1 case)
The intended behavior is to escape the \\ into a single \
Is there an explanation for this behavior? We're stumped and would appreciate any insight!
When you have a string literal with two backslashes, those two backslashes will be interpreted as a single literal backslash, Eg, the .length of '\\test' is 5, not 6.
JSON.parse only allows backslashes before characters that can be escaped, like t (horizontal tab) and n (newline). When you have a literal backslash before a character that can't be escaped (like a), JSON.parse throws an error. (This is unlike Javascript string literals, which can have unnecessarily escaped normal characters - eg const str = '\a' is equivalent to 'a', and doesn't throw an error.) You can see an illustration of what's permitted in JSON here - as you can see by the fourth graphic, after a \, the only permitted characters are one of "\/bfnrt, or uXXXX, where each X is a hex digit.
If you wanted the value of the test property in the parsed object to be the string path, followed by a literal backslash, followed by test or atest, you'd need to use four backslashes when declaring the string literal - first, to have the Javascript interpreter interpret it as two literal backslashes, and second, to have JSON.parse interpret the two literal backslashes as a single backslash in the parsed string.
const str1 = '{ "test": "path\\\\test" }';
const str2 = '{ "test": "path\\\\atest" }';
try {
console.log('str1:', JSON.parse(str1));
} catch (e) {
console.log('str1 fail')
}
try {
console.log('str2', JSON.parse(str2))
} catch (e) {
console.log('str2 fail')
}
You can also define string literals with String.raw for any single backslash in the string to be interpreted as a single literal backslash (rather than the beginning of an escape sequence):
const str1 = String.raw`{ "test": "path\\test" }`;
const str2 = String.raw`{ "test": "path\\atest" }`;
try {
console.log('str1:', JSON.parse(str1));
} catch (e) {
console.log('str1 fail')
}
try {
console.log('str2', JSON.parse(str2))
} catch (e) {
console.log('str2 fail')
}

Convert string with index in brackets to JSON array

I have various strings with numbers in brackets like "[4]Motherboard, [25]RAM" how can I convert such a string to a JSON array (keeping both ids and values) like this:
{"data":[
{"id":"4","item":"Motherboard"},
{"id":"25","item":"RAM"}
]};
I'm tried using split(",") to create the array but I really can't find out how to get the inner data in this case.
You could use a regular expression, which takes the number and the string, and assign it as property to an object.
var string = "[4]Motherboard, [25]RAM",
data = string.split(', ').map(function (a) {
var p = a.match(/^\[(\d+)\](.+)$/);
return { id: p[1], item: p[2] };
});
console.log(data);
Here one way to do it. The pattern \[(\d+?)\](.+) works like this:
(…) is a capture group. Just means whatever matches within the brackets will be a token in the result.
\d means a digit
\d+ means a digit, one or more times
\d+? means a digit, one or more times, but as few as possibile before the pattern matches something else.
.+ means any character, one or more times.
[ and ] have a special meaning in regular expression, so if you actually want to match the characters themselves, you need to escape them like so \[ and \].
The double backslashes \\ are just a JS oddity when defining a regex via a string as opposed to using a /literal/. Just two ways of saying the same thing.
There's plenty of resources to learn regex syntax, and http://regex101.com is a great place to play with patterns and experiment.
var input = "[4]Motherboard, [25]RAM";
var pattern = '\\[(\\d+?)\\](.+)';
var result = input.split(',').map(function (item) {
var matches = item.match(new RegExp(pattern));
return {id: matches[1], val: matches[2]};
});
console.log(result)
function toArray(string) {
return {
data: string.split(",").map(function(str) {
str = str.trim();
return {
id: str.substring(1, str.indexOf("]")),
item: str.substring(str.indexOf("]") + 1),
};
}),
};
}

I have a string , in that I want to search the parentheses pair with proper nested and it must have some value inside it in javascript

I have a string , in that I want to search the parentheses pair with proper nested and it must have some value inside it in javascript.
for example,
String = "#PriceMovement#-(#AssetProof#-(#TradeImpact#)/45)-#UninvestedCashImpact#"
In the above string all the variables are covered with # symbol.
As per the above statement TradeImpact will be divided by 45 first and remaining calculation has been made, if the user will enter like below then I should show error that wrong parentheses used(I cant do this with the count of opening and closing braces as the below string will also having equal start and close braces). Please help me to validate it thanks in advance.
String = "#PriceMovement#-(#AssetProof#-(#TradeImpact#))/45)-((#UninvestedCashImpact#)"
The easiest way is to replace variables with values and apply eval to the resulting string:
str = "#PriceMovement#-(#AssetProof#-(#TradeImpact#))/45)-((#UninvestedCashImpact#)"
values = {
PriceMovement: 1,
AssetProof: 2,
TradeImpact: 3,
UninvestedCashImpact: 5
}
try {
str = str.replace(/#(\w+)#/g, function($0, $1) {
return values[$1]
})
result = eval(str)
alert(result)
} catch(e) {
alert("Error: " + e)
}
This also catches other types of syntax errors, like invalid operators.
To check just brackets' nesting, replace (...) in a loop until there are no more matches. Then, check if the string contains an unpaired opening or closing bracket:
re = /\([^()]*\)/g
while(str.match(re))
str = str.replace(re, '')
if(str.match(/[()]/))
alert("INVALID NESTING")

Categories