I have a URL string:
var url = https://url.com/{{query}}/foo/{{query2}}
I have a line of code that is able to take in a string, then get an array of all the queries inside the braces:
var queries = String(url).match(/[^{\}]+(?=})/g);
Returns:
queries = ['query', 'query2']
I have a function, parse(queries), which processes these queries and returns a list of their results:
results = ['resultOfQuery', 'resultOfQuery2']
I want to be able to take this list, and then replace the queries in the URL string with their results. The final result of this example would be:
url = https://url.com/resultOfQuery/foo/resultOfQuery2
I have two separate problems:
The regex in the String.match line of code only counts for once set of curly braces, {something}. How can I modify it to look for a set of double curly braces, {{something}}?
I already have the array of results. What is the best way to do the string replacement so that the queries and each of their accompanying set of double braces are replaced with their corresponding result?
You can use replace with following pattern,
{{(.+?)}}
{{ - Matches {{
(.+?) - Matches anything except newline one or more time
let url = "https://url.com/{{query}}/foo/{{query2}}"
let result = {'query': 'queryResult1', 'query2':'queryResult2' }
let replaceDoubleBraces = (str,result) =>{
return str.replace(/{{(.+?)}}/g, (_,g1) => result[g1] || g1)
}
console.log(replaceDoubleBraces(url,result))
Note:- I am using result as object here so it becomes easy to find and replace values, if you can change your parse function consider returning an object from parse
Generalized solution which will also work with nested object.
function replaceText(text, obj, start = '{{', end = '}}') {
return text.replace(new RegExp(`${start}(.+?)${end}`, 'g'), (_, part) => {
return part.split('.')
.reduce((o, k) => (
o || {}
)[k], obj);
});
}
console.log(replaceText(
'Hello my name is {{name.first}} {{name.last}}, age: {{age}}',
{
name: {
first: 'first', last: 'last'
},
age: 20,
}
));
To use Regex inbuild symbols as template, use double backslash \\ before each character. It is because above function uses template string new RegExp('\\^(.+?)\\^', 'g') to create regular expression instead RegExp constructor new RegExp(/\^(.+?)\^/g), otherwise single backslash is enough.
For example
( => \\(
) => \\)
(( => \\(\\(
)) => \\)\\)
^ => \\^
Related
I am trying implement a replacing mechanism for a string like prepared statements that are evaluated dynamicaly in javascript. I have replacements like
[{username:"Max",age:10}]
Eg assume we have the string as input (username) is (age) so a find replace is easy by the attribute and its value.
However I want something more advanced where parentheses are 'identified' and evaluted from the inner to outer eg for input:
[{username:"Max",age:10,myDynamicAttribute:"1",label1:'awesome', label2:'ugly'}]
and string
(username) is (age) and (label(myDynamicAttribute)). In the first iteration of replacements the string should become
(username) is (age) and (label1)
and in second Peter is 10 and awesome. Is there any tool or pattern that I can use to 'understand' the inner parentheses first and the evaluate the other?. I tried regexes but I wasn't able to create a regex that matches the inner parentheses first and then the outer.
You could tokenise the string and use a recursive replacer that traverses the tokens in one pass. If text within parentheses does not match with an object property, they are left as they are. When parentheses occur in the string that is retrieved from the object, they are taken as literals, and no attempt is made to perform a lookup on those again.
function interpolate(encoded, lookup) {
const tokens = encoded.matchAll(/[^()]+|./g);
function* dfs(end="") {
while (true) {
const {value, done} = tokens.next();
if (value == end || done) return;
if (value != "(") yield value;
else {
const key = [...dfs(")")].join("");
yield lookup[key] ?? `(${key})`;
}
}
}
return [...dfs()].join("");
}
// Example run
const lookup = {
username: "Max",
age: 10,
myDynamicAttribute: "1",
label1019: 'awesome(really)', // These parentheses are treated as literals
really: "not", // ...so that this will not be retrieved
label2: 'ugly',
};
const str = "1) (username) is (age) (uh!) and (label(myDynamicAttribute)0(myDynamicAttribute)9)"
const res = interpolate(str, lookup);
console.log(res);
We can write a regular expression that finds a parenthesized expression which contains no internal parentheses, use the expression's internals as a key for our data object, replace the whole expression with that value, and then recur. We would stop when the string contains no such parenthesized expressions, and return the string intact.
Here's one way:
const interpolate = (data, str, parens = str .match (/\(([^\(\)]+)\)/)) =>
parens ? interpolate (data, str. replace (parens [0], data [parens [1]])) : str
const data = {username: 'Max', age: 10, myDynamicAttribute: '1', label1: 'awesome', label2: 'ugly'}
const str = `(username) is (age) and (label(myDynamicAttribute))`
console .log (interpolate (data, str))
This would lead to a sequence of recursive calls with these strings:
"(username) is (age) and (label(myDynamicAttribute))",
"Max is (age) and (label(myDynamicAttribute))",
"Max is 10 and (label(myDynamicAttribute))",
"Max is 10 and (label1)",
"Max is 10 and awesome"
I have a search bar which relies on this filter method.
I concatenate all the search strings in a variable concat, and then use either .includes() or .match() as shown below. If searched for multiple words, this only returns a result if the words occur consecutively in concat.
However, I want it to match ANY two words in concat, not just consecutive ones. Is there a way to do this easily?
.filter((frontMatter) => {
var concat =
frontMatter.summary +
frontMatter.title +
frontMatter.abc+
frontMatter.def+
frontMatter.ghi+
frontMatter.jkl;
return concat.toLowerCase().match(searchValue.toLowerCase());
});
Also tried;
.filter((frontMatter) => {
const concat =
frontMatter.summary +
frontMatter.title +
frontMatter.abc+
frontMatter.def+
frontMatter.ghi+
frontMatter.jkl;
return concat.toLowerCase().includes(searchValue.toLowerCase());
});
Thanks!
Everything is explained in the comments of the code.
If you don't care that "deter" matches the word "undetermined"
.filter((frontMatter) => {
// Get the front matter into a string, separated by spaces
const concat = Object.values(frontMatter).join(" ").toLowerCase();
// Look for a string in quotes, if not then just find a word
const regex = /\"([\w\s\\\-]+)\"|([\w\\\-]+)/g;
// Get all the queries
const queries = [...searchValue.toLowerCase().matchAll(regex)].map((arr) => arr[1] || arr[2]);
// Make sure that every query is satisfied
return queries.every((q) => concat.includes(q));
});
If you DO care that "deter" should NOT match the word "undetermined"
.filter((frontMatter) => {
// Get the front matter into a string, separated by spaces
// The prepended and appended spaces are important for the regex later!
const concat = ` ${Object.values(frontMatter).join(" ").toLowerCase()} `;
// Look for a string in quotes, if not then just find a word
const regex = /\"([\w\s\\\-]+)\"|([\w\\\-]+)/g;
// Get all the queries
const queries = [...searchValue.toLowerCase().matchAll(regex)].map((arr) => arr[1] || arr[2]);
// Make sure that every query is satisfied
// [\\s\\.?!_] and [\\s\\.?!_] check for a space or punctuation at the beginning and end of a word
// so that something like "deter" isn't matching inside of "undetermined"
return queries.every((q) => new RegExp(`[\\s\\.?!_]${q}[\\s\\.?!_]`).test(concat));
});
I'd use .reduce to count up the number of matches, and return true if there are at least 2:
const props = ['summary', 'title', 'abc', 'def', 'ghi', 'jkl'];
// ...
.filter((frontMatter) => {
const lowerSearch = searchValue.toLowerCase();
const matchCount = props.reduce(
(a, prop) => a + lowerSearch.includes(frontMatter[prop].toLowerCase()),
0
);
return matchCount >= 2;
})
I am trying to do the following with no avail: There are a few scenarios where self.name has a erroneous '/' in the middle of the value; ie. populating as such 'WSDH/222-310' - I am trying to simply catch this and convert it for the value to resolve in my following .load call as 'WSDH-222-310'
if (self.type === "car") {
var carClean = ${self.name}; // returns syntax error
var res = str.replace("/", "-");
console.log(carClean )
console.log(res)
// console.log({self.type});
}
self.$container.load(`view/coolObjects/${self.type}/${self.name}`, loadProfile);
I think there's a use case for a tagged template if all embedded variables should be escaped.
A more advanced form of template literals are tagged templates. Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. In the end, your function can return your manipulated string
Example:
var path = `foo/${x}/${y}/bar`;
If both x and y should be escaped so as to replace '/' with '-', then you could do this:
const sanitize_path = (strings, ...parts) =>
strings
.flatMap((str, idx) =>
[ str,
idx < parts.length
? parts[idx].replace(/\//g, '-')
: '' ])
.join('');
const x = '1/2/3';
const y = 'y';
console.log(
sanitize_path`foo/${x}/${y}/bar`
);
Why bother cleaning the strings before using them?
I'm not clear on exactly what you're trying to do, but you can "clean" on the fly like this:
self.$container.load(`view/coolObjects/${self.type}/${self.name.replace("/","-")}`, loadProfile);
Whether or not your surrounding code works, I can't tell. But here's a working sample of what I'm suggesting:
let self = {"type":"mytype","name":"myname"};
console.log(`view/coolObjects/${self.type}/${self.name.replace("my","your")}`);
If I understand your question correctly then you're on the right track using String.prototype.replace - should be something simple as this:
var self = {
name: 'WSDH/222-310'
};
self.name = self.name.replace('/', '-');
console.dir(self);
Normal String assignment:
var str1 = "\320";
console.log(str1); // "Ð"
Raw String assignment:
var str2 = String.raw`\320`;
console.log(str2); // "\320"
In raw string, the backslashes are not interpreted. I need to interpret them so that "\320" will become "Ð". Should I have to convert the raw string to normal String. If so, How? If not so, what else should I do and how do I do?
The thing is, this code is octal, and since these are mapped with linguistic symbols, javascript interpretes it when defining new string.
what you can do is make a map of all the symbols you require with their key as actual string and value as actual symbol.
for ex -
var map = {
"\\320": "\320"
}
console.log(map);
now you can search you text in the map and get the required value.
var str2 = String.raw`\320`;
var s = map[str2];
console.log(s);
to make the map, try this -
visit this site - https://brajeshwar.github.io/entities/
and run this code on console
// for latin
var tbody = document.getElementById("latin");
var trs = tbody.children;
var map = {};
for(i=1;i<trs.length;i++) {
console.log(trs[i].children[6].innerText);
key = trs[i].children[6].innerText;
value = trs[i].children[1].innerText;
map[key] = value;
}
now console map, stringify it, and paste the string in your code and parse it.
I have done this only for latin, similarly do this for other elements also.
Question is a couple of months old, but I think this answer is your best bet, yet. Transforming escape sequences from raw strings is very much doable with ES6 String.fromcodepoint(<hex-value>). I'm in the middle of writing an NPM package which deals with this exact scenario.
First, you need a regular expression which matches all escape sequences in your string. I've used this as a reference for all the different ones. (I use a raw string for this to avoid spamming backslashes)
let [single, ...hex] = String.raw`
\\[bfnrtv0'"\\]
\\x[a-fA-F0-9]{2}
(\\u[a-fA-F0-9]{4}){1,}
\\u\{([0-9a-fA-F]{1,})\}`
.split("\n").slice(1).map(cur => cur.trim());
let escapes = new RegExp(`(${[single].concat(hex).join("|")})`, "gm"),
// We need these for later when differentiating how we convert the different escapes.
uniES6 = new RegExp(`${hex.pop()}`);
single = new RegExp(`${single}`);
Now you can match all the escapes; reserved single characters, extended ASCII range, ES6 "Astral" unicode hexadecimals and surrogate pairs. (except octals because they're deprecated, but you can always add it back). The next step is writing a function which can replace the code points with the corresponding symbols. First a switch-like function for singles:
const singleEscape = seq =>
(() => ({
"\\b" : "\b",
"\\f" : "\f",
"\\n" : "\n",
"\\r" : "\r",
"\\t" : "\t",
"\\v" : "\v",
"\\0" : "\0",
"\\'" : "\'",
"\\\"" : "\"",
"\\\\" : "\\"
}[seq]))();
Then we can rely on ES6 fromcodepoint to deal with the rest which are all hexadecimals.
const convertEscape = seq => {
if (single.test(seq))
return singleEscape(seq);
else if (uniES6.test(seq))
return String.fromCodePoint(`0x${seq.split("").slice(3, -1).join("")}`);
else
return String.fromCodePoint.apply(
String, seq.split("\\").slice(1).map(pt => `0x${pt.substr(1)}`)
);
}
Lastly, we tie it all together with a tagged template literal function named normal. I do not know why you need a raw string, but here you can have access to the raw string and put any additional logic while still resulting in a string where escape sequences are properly parsed.
const normal = (strings, ...values) => strings.raw
.reduce((acc, cur, i) => acc += (values[i-1] || "") + cur, "")
.replace(escapes, match => convertEscape(match));
I'm trying to extract a substring from a file with JavaScript Regex. Here is a slice from the file :
DATE:20091201T220000
SUMMARY:Dad's birthday
the field I want to extract is "Summary". Here is the approach:
extractSummary : function(iCalContent) {
/*
input : iCal file content
return : Event summary
*/
var arr = iCalContent.match(/^SUMMARY\:(.)*$/g);
return(arr);
}
function extractSummary(iCalContent) {
var rx = /\nSUMMARY:(.*)\n/g;
var arr = rx.exec(iCalContent);
return arr[1];
}
You need these changes:
Put the * inside the parenthesis as
suggested above. Otherwise your matching
group will contain only one
character.
Get rid of the ^ and $. With the global option they match on start and end of the full string, rather than on start and end of lines. Match on explicit newlines instead.
I suppose you want the matching group (what's
inside the parenthesis) rather than
the full array? arr[0] is
the full match ("\nSUMMARY:...") and
the next indexes contain the group
matches.
String.match(regexp) is
supposed to return an array with the
matches. In my browser it doesn't (Safari on Mac returns only the full
match, not the groups), but
Regexp.exec(string) works.
You need to use the m flag:
multiline; treat beginning and end characters (^ and $) as working
over multiple lines (i.e., match the beginning or end of each line
(delimited by \n or \r), not only the very beginning or end of the
whole input string)
Also put the * in the right place:
"DATE:20091201T220000\r\nSUMMARY:Dad's birthday".match(/^SUMMARY\:(.*)$/gm);
//------------------------------------------------------------------^ ^
//-----------------------------------------------------------------------|
Your regular expression most likely wants to be
/\nSUMMARY:(.*)$/g
A helpful little trick I like to use is to default assign on match with an array.
var arr = iCalContent.match(/\nSUMMARY:(.*)$/g) || [""]; //could also use null for empty value
return arr[0];
This way you don't get annoying type errors when you go to use arr
This code works:
let str = "governance[string_i_want]";
let res = str.match(/[^governance\[](.*)[^\]]/g);
console.log(res);
res will equal "string_i_want". However, in this example res is still an array, so do not treat res like a string.
By grouping the characters I do not want, using [^string], and matching on what is between the brackets, the code extracts the string I want!
You can try it out here: https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_match_regexp
Good luck.
(.*) instead of (.)* would be a start. The latter will only capture the last character on the line.
Also, no need to escape the :.
You should use this :
var arr = iCalContent.match(/^SUMMARY\:(.)*$/g);
return(arr[0]);
this is how you can parse iCal files with javascript
function calParse(str) {
function parse() {
var obj = {};
while(str.length) {
var p = str.shift().split(":");
var k = p.shift(), p = p.join();
switch(k) {
case "BEGIN":
obj[p] = parse();
break;
case "END":
return obj;
default:
obj[k] = p;
}
}
return obj;
}
str = str.replace(/\n /g, " ").split("\n");
return parse().VCALENDAR;
}
example =
'BEGIN:VCALENDAR\n'+
'VERSION:2.0\n'+
'PRODID:-//hacksw/handcal//NONSGML v1.0//EN\n'+
'BEGIN:VEVENT\n'+
'DTSTART:19970714T170000Z\n'+
'DTEND:19970715T035959Z\n'+
'SUMMARY:Bastille Day Party\n'+
'END:VEVENT\n'+
'END:VCALENDAR\n'
cal = calParse(example);
alert(cal.VEVENT.SUMMARY);