javascript extract parts of a sentence - javascript

Hi I'm not that experienced in JavaScript, and I'm trying to parse commands given as full sentences. It has to be able to match the sentence to a function that will answer, and select sections that it will pass as arguments. I know that's not too clear so here's an example:
sentnce = "Show me a gift of a car"
commands = {
gif: {
pattern: "*gif of a [target]*"
action: gifMe(target)
This scenario should result in the call gifMe("a car") or even better, gimme could be called with a object containing all of the arguments specified, there may be more than just target. I have no idea how to go about coding this or searching for a method to do this. Thanks in advance.
It's not relevant (I think) but I'm using jquery and Coffeescript.

I think this is the code you are looking for. See the comment in the code for some more information about how it works.
var sentence = "Show me a gift of a car";
// specify commands
var commands = {
gif: {
pattern: /gift of a (.*).?/,
action: call
}
// you can add more commands here
}
// iterate over all commands
for(var key in commands)
{
// get settings for command
var settings = commands[key];
// apply command regex to sentence
var result = sentence.match( settings.pattern );
// if there is a match trigger the action with the word as argument
if( result && result.length > 1 )
settings.action.call(this, result[1]);
}
function call(value)
{
alert(value);
}

Related

Use specific codewords as arguments/prefixes in a message?

I'm working in JavaScript with discord.js#v12. I asked this question in their support Discord, but due to issue in question being more related to JavaScript than Discordjs, here I am!
Let's get on it. I would like to fill out a RichEmbed in one single message. This RichEmbed will include a title, a description and 1 field. There can be more than one field in RichEmbeds, so I want to get it clear I only need 1.
Now, I realize I could do this using the following ways a) using awaitMessages and filling in the info one message at a time b) Separating arguments in the message with a space and making each argument text-like-this c) Separating arguments in the message with a newline and making each line a full argument. However, neither of these look very appealing to me. I also have a few people who are going to use my command who are not very technical and thus may not understand the usage of the command unless having a very detailed explanation, which is not very efficient when I'm not available.
I've used a) in the past and it's proven rather unefficient in a fast workflow, and b is straight up useless if I require a long description with a lot of sentences. C is usable but, again, there will be people using this command who are not technically "capable" and thus may struggle on their own. I want to make this as fluent as I can.
I also tried this method, which I found fairly useful but can be confusing:
/commandname Separating title / Description and / Field by a forward slash
So I came to the conclusion I want my syntax looking something like this:
/commandname title:My title! desc:This is the description field1:Field 1 and its title
EDIT: I spent a bit of research after posting, and found out I can use replace method to get the above result. This is currently my code:
let embed_title
let embed_desc
let embed_field
let firstarg = args[1]
if (firstarg.includes(`title:`)) {
var split = firstarg.replace(`title:`, ``)
embed_title = split
// expected output with "/commandname title:Testing123!": /commandname Testing123!
} else if (firstarg.includes(`desc:`)) {
var split = firstarg.replace(`desc:`, ``)
embed_desc = split
// expected output with "/commandname desc:Testing123!": /commandname Testing123!
} else if (firstarg.includes(`field:`)) {
var split = firstarg.replace(`field:`, ``)
embed_field = split
// expected output with "/commandname field:Testing123!": /commandname Testing123!
}
With this I can easily replace the first argument that has title:, desc: or field: in it, however I'd like to scour through the whole message (message.content). After that, I want to find which argument has title: in it. Then I want to feed every argument after title until it hits desc:. Repeat that until field is covered.
I solved my question by using a different syntax.
Instead of using /command and then:
title:Title of message desc:Description of message field1:Field of message
I'm using /command and then:
title Title of new message
description Description of new message
field Field 1 of new message
I'm splitting the arguments by newline \n instead of space/whitespace.
const msgArray = message.content.split(/ +/g)
const args = message.content.slice(prefix.length + msgArray[0].length).trim().split(`\n`)
Since I'm splitting the arguments by newline, it allows me to use spaces in my arguments without worrying about them coming out weird.
I then check for each argument and if it begins with Title, Description or Field, or none of them. Then, I iterate through each argument (from 1 to 3 in human terms) and check if they start with any of the three, then go from there. Note that this is manual and does not have superb compatibility if I wish to add another field or an image, but this works for my case and thus, I've solved it for now.
I realized this afternoon that I can simply loop through all the args and find which one contains a certain keyword. In the process, I can also filter out anything that does not match certain keyword(s).
Complete code as follows:
var _title = ``
var _desc = ``
var _fieldvalue = ``
var _useless = ``
args.forEach((element, i) => {
i += 1
if (element.startsWith(`description `)) {
_desc = element.slice(12)
}else if (element.startsWith(`title `)) {
_title = element.slice(6)
}else if (element.startsWith(`field `)) {
_fieldvalue = element.slice(6)
}else{
_useless = element
}
})
if (!_title && !_desc) return message.channel.send(`Please include at least a title or a description by using \`title\` or \`description\`.`)
if (!_fieldvalue) {
message.channel.send({embed: {
title: _title || null,
description: _desc || null,
}})
} else {
message.channel.send({embed: {
title: _title || null,
description: _desc || null,
fields: [
{
name: `\u200b`,
value: _fieldvalue,
},
],
}})
}
For those of you who want to see the code for the original result, see this pastebin result.

Using regex to test out of 4 words in an array has a perfect match

I was tasked with a project of creating a CV site were the user uploads a .docx file and the details is extracted from the file and is automatically inputted in the template designed by me,
I have been able to extract the details .docx file with JavaScript and the extracted details was kept in an array to make it easy to identify words with indexing. For example
[Adeola Emmanuel, adeolaemmanuel#gmail.com, pharmacist, 2 ketu ikorodu lagos, etc].
where i need help is not all CV uploaded by the user has the name coming first or email coming second but its sure that they come within 0,6 of the array so i decided to write a function that will help with that but its not working
var email = email(text.slice(0, 5));
function email(email) {
var re = /.{1,}#[^.]{1,}/ig;
if (!re.test(email)) {
email = text.slice(0, 1);
return email;
} else if (re.test(email)) {
email = text.slice(3, 5);
return email;
}
}
You can use the find array method:
function getEmail(arr) {
let re = /\S#[^.\s]/;
return arr.find(str => re.test(str));
}
let text = ["Adeola Emmanuel", "adeolaemmanuel#gmail.com", "pharmacist", "2 ketu ikorodu lagos"];
let email = getEmail(text.slice(0, 5));
console.log(email);
Some remarks:
{1,} in regular expressions can be shortened to just +
You actually don't need to test for multiple occurrences with +, since you would already accept one occurrence. So that also means you would be OK with just one non-point character after the #.
Neither of the regex suffixes (ig) have any use in your regex.
The .test method should get a string as argument, not an array. So you need to pass it email[0] for example.
For a full test of whether some string is a valid email address, the regular expression would be way more complex
When an if condition is false, there is no need to test the exact opposite in the else block: by exclusion that opposite condition will always be true when it gets executed.
The slice of an array is still an array, so returning text.slice(3, 5); in the else block does not make sense. You want to return a string.
You need a loop to inspect other array elements for as long as you don't have a match and have not reached the end of the array. So some loop construct is needed. You can use for, while, or any of the array methods that do such looping. find is particular useful in this case.
Don't give your function the same name as another variable (email) as only one value can be assigned to that variable (a function, a string, or still something else). So in your case you'll lose the function definition by the var initialisation.

Find String in a Txt File, Delete Entire Line

I'm currently working with node.js to create an IRC bot. The bot allows users to add song links to a database. Each time someone submits a song, it is added to a new line of "shuffle.txt" as such:
user1,The Beatles,Yesterday,(youtube link)
user2,The Rolling Stones,Angie,(youtube link)
user1,The Bealtes,Yellow Sumbarine,(youtube link)
Notice that user1 mistyped some information in their latest addition. I'm trying to make an UNDO command so that a user can delete their most recently entered line. I plan on doing this by finding the latest occurrence of their name in shuffle.txt and deleting the entire line that it's found on. Here's my message listener:
bot.addListener('message', function(from, to, message) {
if (message.indexOf(config.prefix) == 0) {
message = message.slice(1);
var token = message.split(" ");
if (token[0] == 'undo') {
//find and delete
}
}
});
the user entering the command is stored as from
I'm assuming I'll have to do something along the lines of this:
var songList = fs.readFileSync('shuffle.txt', 'utf8');
var position = songList.indexOf(from);
if (position != -1) { //if 'from' found
//find LAST occurrence of 'from'
//get length from here to next occurrence of '\n'
//substr(length + 1)
fs.writeFile('shuffle.txt', songList, function(err) {
if (err) {
console.log (err);
}
}
I'm new to JavaScript and this is my first time using node.js so I can use any help I can get! Thanks everyone.
EDIT: I should also point out that I don't need help with the command recognition. I only need help with the finding/deleting portion. Cheers!
Edit2: edited with a new solution.
You could try this:
fs.readFile('shuffle.txt', function read(err, data) {
if (err) {
throw err;
}
lastIndex = function(){
for (var i = data_array.length - 1; i > -1; i--)
if (data_array[i].match('user1'))
return i;
}()
delete data_array[lastIndex];
});
Split file into lines, find the last line with a simple loop going backwards, remove the line using delete, then patch it back together.
demo
Also, you should not use readFileSync in node, as blocking node can be dangerous since it's single threaded.
This seems to work ok. It's self contained so you can just paste into a HTML file to see it run.
Basically keep running a regular expression to match the whole line starting with the username passed. The whole line is returned ("gm" part of regular expression tells it to match a line at a time) as a string.
Then you just to a replace of that returned line (as string) within the data.
Note that this assumes that line is unique in the text file. If you think people might have entered the same line a few times (but only want to remove the last one) then the 'non lazy' option whcih I've left uncommented is best. I haven't tested what happens if the line is first in the data or last.
<html>
<head>
<script type="text/javascript">
var sData="user1,The Beatles,Yesterday,(youtube link)\nuser2,The Rolling Stones,Angie,(youtube link)\nuser1,The Bealtes,Yellow Sumbarine,(youtube link)\nuser3,The Rolling Stones,Angie,(youtube link)";
function replaceLastEntry(sText) {
sNewData=sData;
var re=new RegExp("^"+sText+".*$","gm"); // matches whole lines starting with sText
var lastIndex=-1;
var i=0;
var sMatch=re.exec(sData)!= null;
while (sMatch!=null)
{
i++
lastIndex=re.lastIndex;
lastMatch=sMatch.toString(); // make note of last successful match - gives full line
sMatch=re.exec(sData);
}
// at this point lastMatch contains the whole line which needs to be removed.
// lastIndex is the string position of the character after the LAST string matched (ie the end of the matched line)
// sNewData = sData.replace(lastMatch,""); // Lazy way - assumes the line is unique within the file
// non-lazy way : locate the text by position returned and remove it
sNewData = sData.substr(0, lastIndex-lastMatch.length-1) + sData.substr(lastIndex);
document.getElementById("Data").innerHTML=sData
document.getElementById("NewData").innerHTML=sNewData
document.getElementById("LastMatch").innerHTML=lastMatch
}
</script>
</head>
<body onload="replaceLastEntry('user1','newvalue')">
Data: <pre id="Data"></pre>
New Data:<pre id="NewData"></pre>
Last Match: <pre id="LastMatch"></pre>
</body>
</html>
hope this helps !

CodeMirror - Using RegEx with overlay

I can't seem to find an example of anyone using RegEx matches to create an overlay in CodeMirror. The Moustaches example matching one thing at a time seems simple enough, but in the API, it says that the RegEx match returns the array of matches and I can't figure out what to do with it in the context of the structure in the moustaches example.
I have a regular expression which finds all the elements I need to highlight: I've tested it and it works.
Should I be loading up the array outside of the token function and then matching each one? Or is there a way to work with the array?
The other issue is that I want to apply different styling depending on the (biz|cms) option in the regex - one for 'biz' and another for 'cms'. There will be others but I'm trying to keep it simple.
This is as far as I have got. The comments show my confusion.
CodeMirror.defineMode("tbs", function(config, parserConfig) {
var tbsOverlay = {
token: function(stream, state) {
tbsArray = match("^<(biz|cms).([a-zA-Z0-9.]*)(\s)?(\/)?>");
if (tbsArray != null) {
for (i = 0; i < tbsArray.length; i++) {
var result = tbsArray[i];
//Do I need to stream.match each element now to get hold of each bit of text?
//Or is there some way to identify and tag all the matches?
}
}
//Obviously this bit won't work either now - even with regex
while (stream.next() != null && !stream.match("<biz.", false)) {}
return null;
}
};
return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), tbsOverlay);
});
It returns the array as produced by RegExp.exec or String.prototype.match (see for example https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/match), so you probably don't want to iterate through it, but rather pick out specific elements the correspond to groups in your regexp (if (result[1] == "biz") ...)
Look at implementation of Code Mirror method match() and you'll see, that it processes method parameter for two types: string and RegExp.
Your constant in
stream.match("<biz.")
is of string type.
Define it in RegExp type:
tbsArray = /<biz./g
Thus, your stream will be matched with RegExp.

Best way to store JS Regex capturing groups in array?

Exactly what title asks. I'll provide some examples while explaining my question.
Test string:
var test = "#foo# #foo# bar #foo#";
Say, I want to extract all text between # (all foos but not bar).
var matches = test.match(/#(.*?)#/g);
Using .match as above, it'll store all matches but it'll simply throw away the capturing groups it seems.
var matches2 = /#(.*?)#/g.exec(test);
The .exec method apparently returns only the first result's matched string in the position 0 of the array and my only capturing group of that match in the position 1.
I've exhausted SO, Google and MDN looking for an answer to no avail.
So, my question is, is there any better way to store only the matched capturing groups than looping through it with .exec and calling array.push to store the captured groups?
My expected array for the test above should be:
[0] => (string) foo
[1] => (string) foo
[2] => (string) foo
Pure JS and jQuery answers are accepted, extra cookies if you post JSFiddle with console.log. =]
You can use .exec too like following to build an array
var arr = [],
s = "#foo# #bar# #test#",
re = /#(.*?)#/g,
item;
while (item = re.exec(s))
arr.push(item[1]);
alert(arr.join(' '));​
Working Fiddle
Found from Here
Well, it still has a loop, if you dont want a loop then I think you have to go with .replace(). In which case the code will be like
var arr = [];
var str = "#foo# #bar# #test#"
str.replace(/#(.*?)#/g, function(s, match) {
arr.push(match);
});
Check these lines from MDN DOC which explains your query about howexec updates lastIndex property I think,
If your regular expression uses the "g" flag, you can use the exec
method multiple times to find successive matches in the same string.
When you do so, the search starts at the substring of str specified by
the regular expression's lastIndex property (test will also advance
the lastIndex property).
I'm not sure if this is the answer you are looking for but you may try the following code:
var matches = [];
var test = "#foo# #foo# bar #foo#";
test.replace(/#(.*?)#/g, function (string, match) {
matches.push(match);
});
alert(JSON.stringify(matches));
Hope it helps.
data.replace(/.*?#(.*?#)/g, '$1').split(/#/)
No loops, no functions.
In case somebody arrives with a similar need to mine, I needed a matching function for a Django-style URL config handler that could pass path "arguments" to a controller. I came up with this. Naturally it wouldn't work very well if matching '$' but it wouldn't break on '$1.00'. It's a little bit more explicit than necessary. You could just return matchedGroups from the else statement and not bother with the for loop test but ;; in the middle of a loop declaration freaks people out sometimes.
var url = 'http://www.somesite.com/calendar/2014/june/6/';
var calendarMatch = /^http\:\/\/[^\/]*\/calendar\/(\d*)\/(\w*)\/(\d{1,2})\/$/;
function getMatches(str, matcher){
var matchedGroups = [];
for(var i=1,groupFail=false;groupFail===false;i++){
var group = str.replace(matcher,'$'+i);
groupFailTester = new RegExp('^\\$'+i+'$');
if(!groupFailTester.test(group) ){
matchedGroups.push(group);
}
else {
groupFail = true;
}
}
return matchedGroups;
}
console.log( getMatches(url, calendarMatch) );
Another thought, though exec is as efficient.
var s= "#foo# #foo# bar #foo#";
s= s.match(/#([^#])*#/g).join('#').replace(/^#+|#+$/g, '').split(/#+/);

Categories

Resources