I am trying to create a variable name exchanger. Simple iterating over lines from a JavaScript file and exchange every variable name with a random name from a dictionary i can supply.
I can do that so far, but i am stuck optimizing the search regex for variable keywords.
I first define Keywords which i am looking for:
const VARIABLE_KEYWORDS = ["var", "let", "const"];
Then i iterate over every line from the input file and extract the variable names and store them in an array. Once i have all variable names stored, i make them unique and then simply replace them in the original file with a random word.
That is my extractVariables(line) logic, where the variable line is a string:
function extractVariables(line) {
let lineCnt = 1;
let found_vars = [];
// if the line is not empty, parse it
if (line.trim().length !== 0) {
// find a variable keyword
for (let key of VARIABLE_KEYWORDS) {
// regex to match the variable keyword and its not inside a string definition \"|\' , and is not used as a property name |\.
var re = new RegExp("(?<!\"|\')\\b" + key + "\\b(?!\"|\')", "g");
// while there are variables declared in the line, add them to the variables array
while ((match = re.exec(line)) != null) {
const indexOfKeyword = match.index + key.length;
// get the name after and store it in an array
if (indexOfKeyword > 0) {
let found_var = line.substr(indexOfKeyword).trim().split(' ').shift();
// if a keyword is found, but no variable defined afterwards do not break everything
if(found_var.length !== 0){
found_vars.push(found_var);
}
}
}
}
}
return found_vars;
}
My regex is missing some fixes.
new RegExp("(?<!\"|\')\\b" + key + "\\b(?!\"|\')", "g");
The lookbehind is not supported.
KEYWORDS inside a String like "foo var let me be" are still found, i only fixed it if they are the only word next to a " or '
Scopes {} are ignored
Can someone help me out please?
Related
I would like "category/[categoryName]/[amount]" to become "category/movies/all" (replace "[variable-name]" with the value of the variable with the same name). I have this so far but I'm missing something:
let categoryName = "movies"; // example variable, set somewhere else
let amount = "all"; // example variable, set somewhere else
...
let searchUrl = "category/[categoryName]/[amount]"; // Set dynamically, could be any params
let regex = /\[(.+?)\]/ug;
searchUrl = searchUrl.replace(regex, window['$1']);
but the value of searchUrl just becomes "category/undefined/undefined".
Is what I'm trying to do even possible? Was that asked before and my question title is just malformed? I know how to do this with 2 regexes, first getting the variable names then looping in them and substituting. However I would like to do it with one "replace" only. Is that possible or I have to use 2 regexes?
If I understand correctly for this to work as dynamically as you state you will have to do the following
// example variable, you need to use var so its
// available on the window otherwise this will not work
var categoryName = "movies";
...
let searchUrl = "category/[categoryName]/all";
let regex = /\[(.+?)\]/ug;
let variableName = searchUrl.match(regex)[0];
searchUrl = searchUrl.replace(regex, window['variableName']);
Your dynamic variable will have to be stored globally for this work!
You're so close! What you have now tries to replace [categoryName] with the global variable $1, which doesn't exist. What you want is to use searchUrl.replace(regex, categoryName), assuming categoryName is dynamically set with the correct category.
It seems that with .replace you can enter multiple 'replacers', so you could say str.replace(regex, replacer1, replacer2, replacer3...). Alternatively, you can pass a function to replace a matched value each time one is found.
I just modified your code to:
let categoryName = "movies"; // example variable, set somewhere else
let amount = "all"; // example variable, set somewhere else
// previous answer suggestion
// let replacers = [categoryName, amount];
let searchUrl = "category/[categoryName]/[amount]"; // Set dynamically, could be any params
let regex = /\[(.+?)\]/gu;
let replacers = searchUrl.match(regex).map( m => m.replace(/\[|\]/g,''));
searchUrl = searchUrl.replace(regex, () => { let val = eval(replacers.shift()); return val; });
output => "category/movies/all"
Since your regex is global, it continues to find matches but since there is only 1 replacer in your original code, it replaces the match with that replacer.
i.e. categories/undefined/undefined (using searchUrl.replace(regex, window['$1']);)
You may want to put your replacers into an array. Then with each match, use a function to replace the match with the value stored in the array, as shown in my example above.
Note: This example works for 2 matches only.
Hope this helps.
MDN - Specifying a function as a parameter
I want to retrieve inside an array all the elements who match multiple strings (all of them & not necessary words): like a search engine returning all results matching term_searched#1 && term_searched#2.
It's not a question about duplicates in the array (there's none), but about searching for a conjunction of elements: traditionally, the search is for one element, by himself or in disjunction with others (a|b|c). Just want to search (a && b && c).
I tried:
indexOf() : I can work only with one element to locate in the array.
match() : there is no AND operator in a regex expression (only | - sadly, it would be so simple). So I tried to inject these regex expressions
/(?=element1).*(?=element2)/gim
/(?=element1)(?=element2)/gim see here
The first regex expression works, but not at every time: seems very fragile...
So I don't know if I'm in the good direction (match) or if I can't figure what is the right regex expression... Need your advices.
// filter grid by searching on 'input' event
'input #search': (e)=> {
var keypressed = e.currentTarget.value;
// create array on 'space' input
var keyarr = keypressed.toLowerCase().split(" ");
// format each array's element into regex expression
var keyarrReg = [];
for(i = 0; i < keyarr.length; i++) {
var reg = '(?=' + keyarr[i] + ')';
keyarrReg.push(reg);
}
// array to regex string into '/(?=element1).*(?=element2)/gim' format
var searching = new RegExp(keyarrReg.join(".*"), 'mgi');
// set grid
var grid = new Muuri('#gridre', {
layout: {
fillGaps: true,
}
});
if (keypressed) {
// filter all grid's items (grid of items is an array)
grid.filter(function (item) {
var searchoperator = item.getElement().textContent.toLowerCase().match(searching);
// get items + only their text + lower case their text + return true (not false) in the value ('keypressed') is found in them
//var searchoperator = item.getElement().textContent.toLowerCase().indexOf(keypressed.toLowerCase()) != -1;
return searchoperator;
}
[....]
}
}
Edit with Gawil's answer adapted to my initial code (to help if needed)
// filter grid by searching on 'input' event
'input #search': (e)=> {
var keypressed = e.currentTarget.value;
// create array on 'space' input
var keyarr = keypressed.toLowerCase().split(" ");
// convert the array to a regex string, in a '^(?=.*word1)(?=.*word2).*$' format
// here is Gawil's answer, formatted by Teemu
var searching = new RegExp('^(?=.*' + keyarr.join(')(?=.*') + ').*$', 'm');
// set grid
var grid = new Muuri('#gridre', {
layout: {
fillGaps: true,
}
});
if (keypressed) {
// filter all grid's items (grid of items is an array)
grid.filter(function (item) {
// get items + only their text + lower case their text + delete space between paragraphs
var searchraw = item.getElement().textContent.toLowerCase().replace(/\r\n|\n|\r/gm,' ');
var searchoperator = searchraw.match(searching);
return searchoperator;
}
[....]
}
}
The code bellow will log each element of the array containing words cats and dogs.
It uses the regex ^(?=.*word1)(?=.*word2).*$To handle new lines, use this one instead :
^(?=(?:.|\n)*word1)(?=(?:.|\n)*word2).*$
You can add as many words as you want following the same logic, and it does not take order of the words in count.
It is very similar to what you tried, except that you have to do all (?=) checks before matching the string. Indeed, your first regex works only when the words are in the right order (element1 and then element2). Your second regex almost works, but you wrote only lookaheads, so it checks the presence of each word, but won't match anything.
var words = ["cats", "dog"]
var array = [
"this is a string",
"a string with the word cats",
"a string with the word dogs",
"a string with both words cats and dogs",
"cats rule everything",
"dogs rule cats",
"this line is for dog\nbut cats prefer this one"
]
var regexString = "^";
words.forEach(function(word) { regexString += ("(?=(?:.|\n)*"+word+")"); });
var regex = new RegExp(regexString);
array.forEach(function(str) { // Loop through the array
if(str.match(regex)) {
console.log(str); // Display if words have been found
}
});
If I've correctly understood your question, you've an array of strings, and some keywords, which have to be found from every index in the array to be accepted in the search results.
You can use a "whitelist", i.e. a regExp where the keywords are separated with |. Then iterate through the array, and on every member create an array of matches against the whitelist. Remove the duplicates from the matches array, and check, that all the keywords are in the list simply by comparing the length of the matches array to the count of the keywords. Like so:
function searchAll (arr, keywords) {
var txt = keywords.split(' '),
len = txt.length,
regex = new RegExp(txt.join('|'), 'gi'), // A pipe separated whitelist
hits; // The final results to return, an array containing the contents of the matched members
// Create an array of the rows matching all the keywords
hits = arr.filter(function (row) {
var res = row.match(regex), // An array of matched keywords
final, temp;
if (!res) {return false;}
// Remove the dups from the matches array
temp = {}; // Temporary store for the found keywords
final = res.filter(function (match) {
if (!temp[match]) {
// Add the found keyword to store, and accept the keyword to the final array
return temp[match] = true;
}
return false;
});
// Return matches count compared to keywords count to make sure all the keywords were found
return final.length === len;
});
return hits;
}
var txt = "Some text including a couple of numbers like 8 and 9. More text to retrieve, also containing some numbers 7, 8, 8, 8 and 9",
arr = txt.split('.'),
searchBut = document.getElementById('search');
searchBut.addEventListener('change', function (e) {
var hits = searchAll(arr, e.target.value);
console.log(hits);
});
<input id="search">
The advantage of the whitelist is, that you don't have to know the exact order of the keywords in the text, and the text can contain any characters.
I'm using pouchDb and to query the database it requires the creation of a map function (which is standard practice for couchDB)
This version is working:
function (doc) {
if (doc.type) {
emit(doc.type)
}
}.toString()
and it results in:
"function mapFunction(doc) {
if (doc.type) {
emit(doc.type);
}
}"
However, I'm trying to change my function call to be more dynamic so I can pass a field through that the map function should be built on. With that in mind, I have a variable called field and I change my map function to this:
var field = '_id'
function (doc) {
if (doc[field]) {
emit(doc[field)
}
}.toString()
the problem is, the string that's generated is like so:
"function mapFunction(doc) {
if (doc[field]) {
emit(doc[field]);
}
}"
but I need to it to be:
"function mapFunction(doc) {
if (doc['_id']) { //or doc._id (I don't mind)
emit(doc['_id']);
}
}"
Is it possible to achieve this?
Edit: Worse case scenario, I write it as a string and do it that way but would prefer to have it as a readable function.
Perhaps a generator that takes a function, a variable name and a value and creates the string you want would do.
Something like
function functionGenerator(func, variable, value){
var r = new RegExp(variable,'gi');
return func.toString().replace(r, value);
}
function mapFunction(doc) {
if (doc[field]) {
emit(doc[field]);
}
}
var map = functionGenerator(mapFunction, 'field','\'_id\'');
console.log(map);
You could define a new method on the Function prototype that performs a toString, but allows to pass a collection of variables in an object format -- where each key is the variable to use. Those variables are injected in the string representation of the function, as var declarations right after the function body opens with a brace.
Each variable gets the JSON representation of its original value. This, of course, has some limitations, as not all values can be represented as JSON (cyclic references, objects with methods, ...etc). But it certainly works with primitive values such as strings:
Function.prototype.toStringWith = function(vars) {
return this.toString().replace(/(\)\s*{)/,
'$1\n var ' + Object.keys(vars)
.map( key => key + ' = ' + JSON.stringify(vars[key]) )
.join(',\n ') + ';');
}
// Demo
var field = '_id'
var s = function mapFunction(doc) {
if (doc[field]) {
emit(doc[field])
}
}.toStringWith({field}); // ES6 shortcut notation
console.log(s);
If you would have more variables that the function needs to "know", like size, weight, brand, then you call .toStringWith({field, size, weight, brand}), ...etc.
NB: solutions that search for the variable name in the function source and replace it with the literal value will need to be careful: the variable name could occur in a quoted string (between single quotes, doubles quotes), or template literals, or be part of a larger name, where it should not be replaced.
I think the easiest solution is a simple regexp.
var field = '_id';
var a = function (doc) {
if (doc[field]) {
emit(doc[field])
}
}.toString();
console.log(a.replace(/field/gi, field));
As I've commented, that's broad because you need to parse and re-generate this stringified function. I can't believe a plugin will force someone to stringify a function.
Since it's broad to do that replacement from field to __id (because of other identifiers, etc.) you can only re-declare this field with its initial value in the stringified function (assign its value at the top).
Not related-advice:
(Remind: var statement declares a variable in the entire scope, so the variable can be assigned before the var statement is present, too.)
//////////////////////////////////////////////
//////////////// References //////////////////
//////////////////////////////////////////////
var _stringify = JSON.stringify
//////////////////////////////////////////////
//////////////// Variables //////////////////
//////////////////////////////////////////////
var field = '__id'
/* Store the variables to be copied in the top here */
var locals = { field: field }
/* String to contain the variables */
var stringified_locals = '',
// thanks to this var length notation
// we'll separate the variables by commas
len = 0
/* Calculate the length of vars */
for (let prop in locals)
++len
/* Also useful for the variable separation */
i = 0; var i
/* Only declare the 'var' statement if there's at least
* ONE var */
if (len)
stringified_locals = 'var '
/* Now generate the string of variables */
for (let prop in locals) {
let value = _stringify(locals[prop])
stringified_locals += prop + ' = ' + value
/* Add comma separator if neccessary */
if (i++ < (len - 1))
stringified_locals += ', '
}
/* And the complete stringified function */
stringified_locals + '\r\n' +
(function (doc) {
if (doc.type) {
emit(doc.type)
}
}).toString()
Got result:
`var field = "__id"
function (doc) {
if (doc.type) {
emit(doc.type)
}
}`
You could do this:
"(function() {\n" +
"var field = " + JSON.stringify(field) + ";\n" +
"return " + mapFunction.toString() + ";" +
"})()"
Caveat: There are rare cases where JSON.stringify doesn't produce valid javascript. I don't know exactly what those cases are or whether it would be possible for a malicious user to take advantage of them in some way. (Do you trust whoever is supplying the value of field?)
Hi I want to write clean code that I can read and have a good overview.
So I wrote this:
var id = '12345';
var coll = ['scc-roles','scc-proj-' + id];
var spm = 'some-role';
var data = {role : spm, roleNames : 'sccss-user', collection : coll}
var spmRoleId = xdmp.eval('declareUpdate();
var sec = require("/MarkLogic/security.xqy");
var roleId = sec.createRole(role, "Generated project member", roleNames, null, collection,null,null);
var uri = "http://marklogic.com/xdmp/roles/" + roleId;
xdmp.documentAddCollections(uri,collection)',data,{"database" : xdmp.securityDatabase()})
But apparently a newline is not allowed in xdmp.eval() ?
[javascript] JS-JAVASCRIPT: + 'var sec = require("/MarkLogic/security.xqy"); -- Error running JavaScript request: SyntaxError: Unexpected token ILLEGAL
I tried using a '+' sign to generate a strng over more then one line, swapping single and double quotes but no luck.
Being able to test this code (copy paste) to the security database makes a lot of sense to me...
If I wrap it all in one unreadable line , it works ok.
hugo
The way to effectively create a new line in a JavaScrit string is to escape the new line char like this
var str = "I'm displayed\
in two line";
In the final file, you will see effectively a new line.
If you want see in the dist output the new line but not in your src string you could just insert the \n equivalent of a return to line.
var str = "I'm displayed\n in two line";
In es6 you will be able to use ` char to achieve the same thing without \
var str = `I'm displayed
in two line`;
Maybe you would like the strange, yet useful array-notation way of doing this:
var multiline1 = [
'the lazy fox',
'jumped over',
'the dead chicken',
].join('\n');
and the result:
the lazy fox
jumped over
the dead chicken
In general, you should avoid string concatenation to build code for eval. Strings make it difficult to spot bugs and are a great vector for injection attacks. Instead, I'd advise you to write a proper function in XQuery or JavaScript and use xdmp.invokeFunction to evaluate it. invokeFunction takes all of the same options as xdmp.eval.
Here's an example that gets roles in the context of a security database. The applyAs function returns a function that wraps the function provided by the caller, evaluating it with the eval options provided.
function applyAs(fct, options) {
return function() {
var params = Array.prototype.slice.call(arguments);
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
var f = (function() {
return fct.apply(null, params);
}).bind(this);
// Allow passing in user name, rather than id
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; }
// Allow the functions themselves to declare their transaction mode
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; }
return xdmp.invokeFunction(f, options); // xdmp.invokeFunction returns a ValueIterator
}
}
/**
* Gets an Array of id-name Objects. Requires privileged access to security.
*
* #param names An optional Array of role IDs as strings used to filter
* #return An Array of Objects with role ID keys and role name values
*/
function getRoles(names) {
var sec = require('/MarkLogic/security.xqy');
var db = {database: xdmp.securityDatabase()};
var roleIDs = applyAs(sec.getRoleIds, db);
var rolesItr;
if(Array.isArray(names)) {
rolesItr = roleIDs(xdmp.arrayValues(names));
} else {
rolesItr = roleIDs();
}
var roleNames = applyAs(sec.getRoleNames, db)(rolesItr).toArray().map(function(el) { return el.textContent; });
var roles = [];
var i = 0;
for(var role of rolesItr) {
var r = {}
r[role.textContent] = roleNames[i++];
roles.push(r);
}
return roles;
}
getRoles();
Originally from a gist.
Is there a way to make the value of a variable the name for another variable? For example, I want the variable name (value_of_i) to be what ever number "i" is during that iteration. The while loop below is not what I'm using it for, it's just to explain what I'm asking.
var i = 1;
while(i<10)
{
var value_of_i = "This loop has ran " + i + "times.";
i++;
}
For the first iteration, "i" is equal to 1 so I would want the variable name to be "1":
var 1 = "This loop has ran " + i + "times.";
And the second interation:
var 2 = "This loop has ran " + i + "times.";
Yes. Using bracket notation (Here is a tutorial in MDN)
Here is a working fiddle
When doing something like containingObject[stringVariable] you are accessing the property in containingObject whose name is the value stored in stringVariable.
// this assumes browser JavaScript where window is the global namespace
// in node.js this would be a little different
var i=0;
while(i<10){
window["counters"+i] = "This is loop has ran " + i + "times.";
i++;
}
console.log(counters3);
If you'd like you can use this instead of window, however this might fail in strict mode.
Here is the main explanation of how bracket notation works from the MDN link above:
Properties of JavaScript objects can also be accessed or set using a bracket notation. Objects are sometimes called associative arrays, since each property is associated with a string value that can be used to access it. So, for example, you could access the properties of the myCar object as follows:
myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;
You can also access properties by using a string value that is stored in a variable:
var propertyName = "make";
myCar[propertyName] = "Ford";
propertyName = "model";
myCar[propertyName] = "Mustang";
You can't make a variable name a number, its not a valid name. So var 1="" is invalid.
But to dynamically set the value you can do
var x = "variablenamehere";
window[x] = "variablevaluehere";
Thats the same as
var variablenamehere
except that it will be scoped as a global variable and will be accessible everywhere, rather than being limited to the current function scope.
Why not store your strings in an array that is indexed by i?
That way you can reference them later efficiently and easily;
var loopI = new Array();
for(var i = 0; i < 10; ++i) {
loopI[i] = "This loop has ran " + i + "times.";
}
This works:
var o = {};
var d = "dog";
for (var k = 0; k < 5; k += 1) {
o[d+k] = k*100;
}
console.log(o.dog3); // 300
This comes closer to doing what you want:
var N = {};
var M = {};
var i = 1;
while(i<10)
{
N[i] = "This loop ran " + i + " times.";
// Or, so you can use dot notation later:
M['OO'+i] = "This loop ran " + i + " times.";
// Those are capital O's, not zeros. Numbers won't work.
i++;
}
console.log(N[3]); // This loop ran 3 times.
console.log(M.OO7); // This loop ran 7 times.
The 'OO' notation could cause bewilderment and wasted time for others trying to use your code; but it could also be a source of amusement for them. This reminds me of a chess board after white's first two moves are to bring out a knight and then put it back. The board then seems to show that black moved first, and some people will endlessly insist that the configuration proves there was illegal play unless someone tells them what happened.