JavaScript regular expression having a weird behaviour - javascript

I'm currently making a scripting language for a Discord bot I'm maintaining and I'm facing a weird issue. The following code takes a string as input (I think {if:3|=|0|you are|TechRax is} {range:1|100}), uses the match method of the string to get all functions (expression: /\{(.*?):(.*?)\}/g) from the string. Then using a forEach, I process all of these matches then I replace the matched content with the result on the string, using the replace method.
Here is the code I use:
let newString = 'I think {if:3|=|0|you are|TechRax is} {range:1|100}';
const functionPattern = /\{(.*?):(.*?)\}/g;
const foundFunctions = newString.match(functionPattern);
if (!foundFunctions) throw new Error('No function found');
foundFunctions.forEach((fn) => {
const parsedInput = functionPattern.exec(fn); // = null once the second iteration begins... ? only the first one work. Same issue if I invert the function orders (first works, second and + no)
if (!parsedInput || !parsedInput[1] || !parsedInput[2]) return;
try {
/*const customFunction = new (require(`../../Production/Tags/${parsedInput[1]}`))(this.client, context, contextType);
if (!customFunction) return;
const result = customFunction.run(parsedInput[2].split('|'));*/
const result = 'Stack Overflow test';
newString = newString.replace(fn, result);
} catch (e) {
newString = newString.replace(fn, e);
}
});
// Print newString here (depends if you're on browser or node)
In this context, this.client.constants.functionPattern = /\{(.*?):(.*?)\}/g, foundFunctions = ['{if:4|=|0|you are|alien is}', '{range:1|100}'] and newString = 'I think {if:{argslen}|=|0|you are|{args} is} {range:1|100}'.
Now let's start describing the behaviour, the first iteration goes well: the function module gets imported, it gets processed and the final content gets replaced on the string.
The problem concerns the second one (and all others), the exec method of the function expression returns null. I do not understand this at all, first I thought it was a kind of bug with my RegExp, maybe {random:1|100} was not matching but no because it works perfectly on Regexr.com and... the weirdest: if I eval it (/\{(.*?):(.*?)\}/g.exec('{range:1|100}), it doesn't return null but the actual result I expect.
I guess I'm wrong somewhere but after passing some hours on it I still do not get why it isn't working.
I hope you'll be able to help me out, thanks!
If you need any complementary information, I'm here.

The problem is you're defining your regex GLOBAL
but don't reset the internal pointer inside of the loop: myRegex.lastIndex = 0; (see MDN)
alternatively, you could recreate a regex inside of the forEach.
let newString = 'I think {if:3|=|0|you are|TechRax is} {range:1|100}';
let functionPattern = /\{([^}]*):([^}]*)\}/g;
const foundFunctions = newString.match(functionPattern);
if (!foundFunctions)
throw new Error('No function found');
foundFunctions.forEach(fn => {
//const functionPattern = /\{([^}]*):([^}]*)\}/g; // or redeclare
const parsedInput = functionPattern.exec(fn);
if (!parsedInput || !parsedInput[1] || !parsedInput[2]) return;
try {
const result = 'Stack Overflow test';
newString = newString.replace(fn, result);
functionPattern.lastIndex = 0; // reset internal pointer of your regex
} catch (e) {
newString = newString.replace(fn, e);
}
});
console.log(newString);
I almost forgot: I suggest a more robust regex pattern: \{(\[^}\]*):(\[^}\]*)\}
However, your pattern seems to be good enough.

Related

JavaScript: if let / if-scoped let

I cannot seem to find anything similar to if let from other programming languages in JavaScript.
If I want to get the logo text of Stack Overflow I need to do
let text = document.querySelector('[class="-img _glyph"]')
if(text) {
result = text.innerText
//do some other work
}
So after declaring, I have to check for it to not be undefined first before using it. Now what would be much more logical is the following:
if let text = document.querySelector('[class="-img _glyph"]') {
result = text.innerText
//do some other work
}
which however doesn't work in JavaScript. Is there another syntax that I can use to avoid having to use the extra line just for the undefined-check?
I found this 10 year old thread https://esdiscuss.org/topic/if-scoped-let but since there were no further responses, I don't know if there is already anything that solves this.
Well, then the answer could be to use a for loop:
for (let text = document.querySelector('[class="-img _glyph"]'); text; text = false) {
result = text.innerText;
console.log(result);
}
console.log("done");
Alternatively - and more in line with a maintainable code - you could do
{
let text = document.querySelector('[class="-img _glyph"]');
if (text) {
result = text.innerText;
console.log(result);
}
console.log("text:", text);
}
console.log(text) // will throw an error!
You can't declare a variable within the if, but you can do an assignment and check that it's not undefined pretty easily:
let text, result;
if (text = document.querySelector('[class="-img _glyph"]')) {
result = text.innerText
//do some other work
} else {
result = "not found";
}

How to check if a keyword is present regardless of capitalization?

I am writing a program that looks for specific keywords and then only performs an action when a keyword is present in a message. I am having difficulty trying to make it so that it will pickup on keywords regardless of their capitalization. Below is an example of what I currently have.
for (var i = 0; i < keyword.length; i++) {
if (msg.content.includes (keyword[i])) {
msg.channel.send("Orange")
}
var keyword = ["Apple","Banana"]
The only way I've figured out how to do this is to add each variation to the keyword list. How would I make it so that it could detect for example "apple" or "BaNaNa" without adding those variations to the keyword list?
If your message is a string, just make the whole thing lower case and match it to lowercase keywords.
let message = msg.content.toLowerCase();
if (message.includes(keyword[i].toLowerCase()) {
....
}
Convert to lowercase both, the database and the search terms.
You can convert both your message and keyword to lowercase and check the existing keyword in your message.
if (msg.content.toLowerCase().includes (keyword[i]).toLowerCase()) {
msg.channel.send("Orange")
}
If I have a big string, 'Hello Marco, how are you doing?', and I have another string, 'MarCo', and I want to check if the second string is in the first, I would do
const needle = 'MarCo';
const haystack = 'Hey Marco, how are you doing?'
const inc = haystack.toLowerCase().includes(needle.toLowerCase());
if (inc) {
console.log('its in there')
} else {
console.log('not in there');
}

How to find whole substring in string?

I have a string and I have to check if that string contains defined substring I need to do some work and otherwise, I should return some error.
I have the following code:
function isContains(myString) {
let subString = 'test1234';
if(myString.includes(subString)) {
// to do some work
} else {
// return some error.
}
}
but the problem is if myString = 'my-string-test1-rrr' its condition return true.
How can I get true only in case when the whole subString was included in myString?
Use indexOf() instead.
function isContains(myString) {
let subString = 'test1234';
if(myString.indexOf(subString) > -1) {
// to do some work
} else {
// return some error.
}
}
you can use regex to check if that value is present are not;
example 1
without containing the specific string
var test = 'my-string-test1-rrr';
console.log(' test --- ', test.match(/test1234/g))
example 2
contains the specific string
var test = 'my-string-test1234-rrr';
console.log(' test --- ', test.match(/test1234/g))
It is highly recommended to use includes() over indexOf() and further indexOf returns the index of the occurrence where you would prefer an immediate answer - false / true if that substring is found inside the searched string.
Your function does exactly what you are asking. I would suggest to isolate the retrieval of this function and make it purer like so, then when you have the return boolean value you could utilize it after to run whatever logic you wish. This way you keep this function pure and separate your concerns better.
I also believe it would be easier for you to debug your issue if you isolate this functions like In the example I provided.
function isContains(myString) {
let subString = 'test1234';
let isContains = false;
if(myString.includes(subString)) {
isContains = true;
} else {
isContains = false;
}
return isContains;
}
You could use it like so in a later phase in your code:
const myString = 'my-string-test1-rrr';
let shouldRunOtherLogic = isContains(myString);
if (shouldRunOtherLogic) {
// to do some work
} else {
// return some error.
}
Hope I could help, if there's anything further you may need feel free to let me know.

Check if array value is included in string

I'm working on some client side validation for a contact form of sorts, the website currently isn't online so server side isn't relevant.
I am trying to create a 'word filter' to catch on any abusive of obscene language before the form is 'submitted'.
Heres the code, without the obscenities...
function filterInput(str) {
var inputFilter = ['word1', 'word2', 'word3'];
var arrayLength = inputFilter.length;
if (inputFilter.indexOf(str) > - 1) {
// Word caught...
} else {
// Clear...
}
If the user were to enter 'word1', it will catch the word. If the user enters 'word1word2' or 'John is a word3', it doesn't catch it.
I originally had a for loop which worked better, but still wouldn't work without whitespace between words('word1word2').
Any input would be greatly appreciated, I've been searching but nothing quite matches my needs.
EDIT: So I too have come up with a solution, but seeing the varying ways this can be achieved I am curious as to how it works and also why a particular way is better?
Heres what I came up with...
function filterInput(str) {
var inputFilter = ['word1', 'word2', 'word3'];
var arrayLength = inputFilter.length;
for (var i = 0; i < arrayLength; i++) {
if (str.includes(inputFilter[i])) {
window.alert('Message...');
return;
}
}
}
You're looking for some rather than indexOf, since you have to do custom matching:
if (inputFilter.some(function(word) { return str.indexOf(word) != -1; })) {
// Word caught...
} else {
// Clear...
}
Or with an ES2015+ arrow function and String.prototype.includes:
if (inputFilter.some(word => str.includes(word))) {
// Word caught...
} else {
// Clear...
}
some calls the callback repeatedly until the first time it returns a truthy value. If the callback ever returns a truthy value, some returns true; otherwise, some returns false. E.g., it's asking if "some" of the entries match the predicate function. (any may have been a better term, but when adding to the built-ins, the TC39 committee have to do a lot of work to avoid conflicts with libraries and such.)
If you ever need to get back the actual entry, use find which returns the entry or undefined if not found. If you need its index, use findIndex.
Side note: Just beware that it's notoriously complicated to do this well. Beware of the Scunthorpe problem, and of course people will routinely just confuse the sequence of letters or substitute asterisks or similar to defeat filters of this sort...
you can try something like this:-
function filterInput(str) {
var badWords = ['bad', 'worst'];
var isTrue = false;
if(str) {
for (var i = 0; i < badWords.length; i++) {
isTrue = !!(str.replace(/\W|\s/g, '').toLowerCase().indexOf(badWords[i]) + 1);
if(isTrue) break;
}
}
return isTrue;
}

Best/efficent way to remember last function result

I got used to using bind to remember the last result of function and to keep track to be able to use the last result for the next result. For instance to concat or join last string to a new string without using outer variables:
function remStr(outStr){
return function c(lastStr,newStr){
if(!newStr)return lastStr;
var all = lastStr+newStr;
return c.bind(null,all);
}.bind(null,outStr);
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
The problem is that I want to call remStr several times and so bind came into play. But can it be done better or just differently, maybe it turns out that for one case an approach fulfills a task better than remStr?
If I understand your intention correctly, how about just using the closure?
function remStr(outStr) {
return function c(newStr) {
if (!newStr) return outStr;
outStr += newStr;
return c;
}
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
As mentioned by Tomalak in the comments, JavaScript strings are immutable, so if you intend to use large or many strings, you will probably want to buffer them in an array.
function remStr(outStr) {
var buffer = [outStr || ''];
return function c(newStr) {
if (!newStr) return buffer.join('');
buffer.push(newStr);
return c;
}
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
You shouldn't be using Function.bind here at all. You can cache the arguments. And then join it.
This approach is widely known as functions are also objects and can have properties. Function.bind is used to change the context of the given function and that isn't what we want.
function concat(word){
return function fn(anWord){
if(!anWord) return fn.words.join("");
(fn.words || (fn.words = [word])).push(anWord);
}
}
Now you can use it like below:
var str = concat("stack");
str("over");
str("flow");
console.log(str()); // "stackoverflow"

Categories

Resources