I am trying to make my code looks professional by removing those duplicate code. the question is I want to get some data from a string, to be specific, I need to know the NUMBER, X, Y, Z, A, B, etc. values but the regex expression are different for each variable so I have to repeat myself writing a lot of duplicate code.
let TextString = `DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
const regNumber = /(?<=NUMBER:=)[0-9]+/gm;
let lineNumber = Number(TextString.match(regNumber));
const regX = /(?<=X:=)(-?[0-9]+)(.[0-9]+)?/gm;
let X = Number(TextString.match(regX)).toFixed(1);
const regY = /(?<=Y:=)(-?[0-9]+)(.[0-9]+)?/gm;
let Y = Number(TextString.match(regY)).toFixed(1);
const regZ = /(?<=Z:=)(-?[0-9]+)(.[0-9]+)?/gm;
let Z = Number(TextString.match(regZ)).toFixed(1);
const regA = /(?<=A:=)(-?[0-9]+)(.[0-9]+)?/gm;
let A = Number(TextString.match(regA)).toFixed(1);
const regB = /(?<=B:=)(-?[0-9]+)(.[0-9]+)?/gm;
let B = Number(TextString.match(regB)).toFixed(1);
// and many more duplicate code.
console.log(lineNumber, X, Y, Z, A, B);
I could only think of a way like the above, to match each variable individually and run .match() multiple times, but as you can see there are 17 variables total and in real situations, there are hundreds of these TextString. I was worried that this matching process will have a huge impact on performance.
Are there any other ways to fetch all variables in one match and store them in an array or object? or any other elegant way of doing this?
Every coordinate will have a single letter identifier, so you can use a more general positive lookback (?<=,[A-Z]:=). This lookback matches a comma followed by a single uppercase letter then the equality symbol.
You can then use .match() to get all matches and use .map() to run the conversion you were doing.
let TextString = `DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
const regNumber = /(?<=NUMBER:=)[0-9]+/gm;
let lineNumber = Number(TextString.match(regNumber));
const regex = /(?<=,[A-Z]:=)(-?[0-9]+)(.[0-9]+)?/gm;
let coord = TextString.match(regex).map(n => Number(n).toFixed(1));
console.log(lineNumber, coord);
You could write a single pattern:
(?<=\b(?:NUMBER|[XYZAB]):=)-?\d+(?:\.\d+)?\b
Explanation
(?<= Positive lookbehind, assert that to the left of the current position is
\b(?:NUMBER|[XYZAB]):= Match either NUMBER or one of X Y Z A B preceded by a word boundary and followed by :=
) Close the lookbehind
-? Match an optional -
\d+(?:\.\d+)? Match 1+ digits and an optional decimal part
\b A word boundary to prevent a partial word match
See a regex demo.
const TextString = `DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
const regNumber = /(?<=\b(?:NUMBER|[XYZAB]):=)-?\d+(?:\.\d+)?\b/g;
const result = TextString
.match(regNumber)
.map(s =>
Number(s).toFixed(1)
);
console.log(result);
One possible approach could be based on a regex pattern which utilizes capturing groups. The matching regex for the OP's sample text would look like this ...
/\b(NUMBER|[XYZAB])\:=([^,]+),/g
... and the description is provided with the regex' test site.
The pattern is both simple and generic. The latter is due to always capturing both the matching key like Number and its related value like 20. Thus it doesn't matter where a key-value pair occurs within a drill-data string.
Making use later of an object based Destructuring Assignment for assigning all of the OP's variables at once the post processing task needs to reduce the result array of matchAll into an object which features all the captured keys and values. Within this task one also can control how the values are computed and/or whether or how the keys might get sanitized.
const regXDrillData = /\b(NUMBER|[XYZAB])\:=([^,]+),/g;
const textString =
`DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
// - processed values via reducing the captured
// groups of a `matchAll` result array of a
// generic drill-data match-pattern.
const {
number: lineNumber,
x, y, z,
a, b,
} = [...textString.matchAll(regXDrillData)]
.reduce((result, [match, key, value]) => {
value = Number(value);
value = (key !== 'NUMBER') ? value.toFixed(1) : value;
return Object.assign(result, { [ key.toLowerCase() ]: value });
}, {})
console.log(
`processed values via reducing the captured
groups of a 'matchAll' result array of a
generic drill-data match-pattern ...`,
{ lineNumber, x, y, z, a, b },
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Every value match a pattern :=[value], or :=[value]) for the last one. So there is my regex
(?<=:=)-?[\d\w.']+(?=[,)])
Positive Lookbehind (?<=:=) look for match behind :=
-? match - optional (for negative number)
[\d\w.']+: match digit, word character, ., '
Positive Lookahead (?=[,)]) look for match ahead character , or )
Live regex101.com demo
Now change your code to
let TextString = `DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
const regexPattern= /(?<=:=)-?[\d\w.']+(?=[,)])/g;
console.log(TextString.match(regexPattern))
// ['20', "'4'", '1', '10.1', '73.344', '0', '-1.435', '1.045', '1', '2', '3', '4', '1', '10.5', '2.1', '1.2', '2', '2.4', '1', '2']
Edit
I just realized the the Positive Lookahead is unnecessary as #Peter Seliger
mentioned
(?<=:=)-?[\d\w.']+
Change your regex pattern to
const regexPattern= /(?<=:=)-?[\d\w.']+/g;
Here is a solution using a .reduce() on keys of interest and returns an object:
const TextString = `DRILL(NUMBER:=20,NAME:='4',PN:=1,X:=10.1,Y:=73.344,Z:=0,A:=-1.435,B:=1.045,M1:=1,M2:=2,M3:=3,M4:=4,M5:=1,S1:=10.5,S2:=2.1,S3:=1.2,S4:=2,S5:=2.4,RS1:=1,RS2:=2);`;
const keys = [ 'NUMBER', 'X', 'Y', 'Z', 'A', 'B' ];
let result = keys.reduce((obj, key) => {
const regex = new RegExp('(?<=\\b' + key + ':=)-?[0-9.]+');
obj[key] = Number(TextString.match(regex)).toFixed(1);
return obj;
}, {});
console.log(result);
Output:
{
"NUMBER": "20.0",
"X": "10.1",
"Y": "73.3",
"Z": "0.0",
"A": "-1.4",
"B": "1.0"
}
Notes:
The regex is built dynamically from the key
A \b word boundary is added to the regex to reduce the chance of unintended matches
If you need the line number as an integer you could take that out of the keys, and handle it separately.
Related
I'm trying to create a regex that will select the numbers/numbers with commas(if easier, can trim commas later) that do not have a parentheses after and not the numbers inside the parentheses should not be selected either.
Used with the JavaScript's String.match method
Example strings
9(296,178),5,3(123),10
10,9(296,178),2,5,3(123),3(124,125)
10,7,5(296,293,444,1255),3(218),2,4
What i have so far:
/((^\d+[^\(])|(,\d+,)|(,*\d+$))/gm
I tried this in regex101 and underlined the numbers i would like to match and x on the one that should not.
You could start with a substitution to remove all the unwanted parts:
/\d*\(.*?\),?//gm
Demo
This leaves you with
5,10
10,2,5,
10,7,2,4
which makes the matching pretty straight forward:
/(\d+)/gm
If you want it as a single match expression you could use a negative lookbehind:
/(?<!\([\d,]*)(\d+)(?:,|$)/gm
Demo - and here's the same matching expression as a runnable javascript (skeleton code borrowed from Wiktor's answer):
const text = `9(296,178),5,3(123),10
10,9(296,178),2,5,3(123),3(124,125)
10,7,5(296,293,444,1255),3(218),2,4`;
const matches = Array.from(text.matchAll(/(?<!\([\d,]*)(\d+)(?:,|$)/gm), x=>x[1])
console.log(matches);
Here, I'd recommend the so-called "best regex trick ever": just match what you do not need (negative contexts) and then match and capture what you need, and grab the captured items only.
If you want to match integer numbers that are not matched with \d+\([^()]*\) pattern (a number followed with a parenthetical substring), you can match this pattern or match and capture the \d+, one or more digit matching pattern, and then simply grab Group 1 values from matches:
const text = `9(296,178),5,3(123),10
10,9(296,178),2,5,3(123),3(124,125)
10,7,5(296,293,444,1255),3(218),2,4`;
const matches = Array.from(text.matchAll(/\d+\([^()]*\)|(\d+)/g), x=> x[1] ?? "").filter(Boolean)
console.log(matches);
Details:
text.matchAll(/\d+\([^()]*\)|(\d+)/g) - matches one or more digits (\d+) + ( (with \() + any zero or more chars other than ( and ) (with [^()]*) + \) (see \)), or (|) one or more digits captured into Group 1 ((\d+))
Array.from(..., x=> x[1] ?? "") - gets Group 1 value, or, if not assigned, just adds an empty string
.filter(Boolean) - removes empty strings.
Using several replacement regexes
var textA = `9(296,178),5,3(123),10
10,9(296,178),2,5,3(123),3(124,125)
10,7,5(296,293,444,1255),3(218),2,4
`
console.log('A', textA)
var textB = textA.replace(/\(.*?\),?/g, ';')
console.log('B', textB)
var textC = textB.replace(/^\d+|\d+$|\d*;\d*/gm, '')
console.log('C', textC)
var textD = textC.replace(/,+/g, ' ').trim(',')
console.log('D', textD)
With a loop
Here is a solution which splits the lines on comma and loops over the pieces:
var inside = false;
var result = [];
`9(296,178),5,3(123),10
10,9(296,178),2,5,3(123),3(124,125)
10,7,5(296,293,444,1255),3(218),2,4
`.split("\n").map(line => {
let pieceArray = line.split(",")
pieceArray.forEach((piece, k) => {
if (piece.includes('(')) {
inside = true
} else if (piece.includes(')')) {
inside = false
} else if (!inside && k > 0 && k < pieceArray.length-1 && !pieceArray[k-1].includes(')')) {
result.push(piece)
}
})
})
console.log(result)
It does print the expected result: ["5", "7"]
my plan is extract numbers from string except numbers with special character. What I mean?
Please imagine a following (like Excel formula):
=$A12+A$345+A6789
I need to extract numbers where in beginning of them doesn't exist any character $, so result of right regex should be:
12
6789
I made some investigation where I used a following regex:
/[A-Z][0-9]+(?![/W])/g
which extracts:
A12
A6789
I was thinking to use nested regex (to extract numbers from that result additionally) but I have no idea if it possible. My source code in javascript so far:
http://jsfiddle.net/janzitniak/fvczu7a0/7/
Regards
Jan
const regex = /(?<ref>\$?[A-Z]+(?<!\$)[0-9]+)/g;
const str = `=$A12+A$345+A6789`;
const refs = [...(str.matchAll(regex) || [])].map(result => result.groups.ref);
console.log(refs)
Matches any string containing A-Z once or more that is preceded by a $ zero or one times, followed by 0-9 once or more but not preceded by a $, all followed by + zero or one times.
You ignore all matched groups, but capture the one you want, referenced as ref (you can call it whatever you want).
Output:
["$A12","A6789"]
If you want just the number part, you can use:
const regex = /\$?[A-Z]+(?<!\$)(?<num>[0-9]+)/g;
const str = `=$A12+A$345+A6789`;
const nums = [...(str.matchAll(regex) || [])].map(result => +result.groups.num);
console.log(nums)
Output:
[12, 6789]
const charSequence = '=$A12+A$345+A6789';
const numberList = (charSequence
.split(/\$\d+/) // - split at "'$' followed by one or more numbers".
.join('') // - join array of split results into string again.
.match(/\d+/g) || []) // - match any number-sequence or fall back to empty array.
.map(str => +str); // - typecast string into number.
//.map(str => parseInt(str, 10)); // parse string into integer.
console.log('numberList : ', numberList);
.as-console-wrapper { min-height: 100%!important; top: 0; }
#ibraheem can you help me once again please? How can I increment ref output if I want to have the following result ["$A13","A6790"]? - JanZitniak 23 mins ago
... the split/join/match approach can be iterated very fast, thus it proves to be quite flexible.
const charSequence = '=$A13+A$345+A6790';
const numberList = (charSequence
.split(/\$\d+/) // - split at "'$' followed by one or more numbers".
.join('') // - join array of split results into string again.
.match(/\$*[A-Z]\d+/g) || []); // - match any sequence of an optional '$' followed
// by 1 basic latin uppercase character followed
// by one or more number character(s).
console.log('numberList : ', numberList);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter thank you for your quick response about increment but on start I have const charSequence = '=$A12+A$345+A6789'; and as output I need ["$A13","A6790"]. – JanZitniak
... ok, finally one is going to get a full picture of the entire problem ... which is (1) getting rid of the not necessary patterns ...(2) matching numbers within specific patterns AND somehow remember the latter (3) increment such numbers AND somehow rework them into their remembered/recallable pattern.
const anchorSequence = '=$A12+A$345+A6789';
const listOfIncrementedAnchorCoordinates = [...(anchorSequence
// - split at "'$' followed by one or more numbers".
.split(/\$\d+/)
// - join array of split results into string again.
.join('')
// - match any sequence of an optional '$' followed by 1 basic latin
// uppercase character followed by one or more number character(s)
// and store each capture into a named group.
.matchAll(/(?<anchor>\$*[A-Z])(?<integer>\d+)/g) || [])
// map each regexp result from a list of RegExpStringIterator entries.
].map(({ groups }) => `${ groups.anchor }${ (+groups.integer + 1) }`);
console.log('listOfIncrementedAnchorCoordinates : ', listOfIncrementedAnchorCoordinates);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter if you are interest(...ed) in another problem I have one. How can I change const anchorSequence = '=$A12+A$345+A6789'; to following output ["B$345","B6789"]? I mean to change letter to next one in alphabetical order (if it is A then change to B, if it is B change to C and so on) if letter doesn't start with $. In my example it should change only A$345 and A6789. – JanZitniak
... with a little thinking effort it was not that hard to iterate/refactor the version before to this last one ...
const anchorSequence = '=$A12+A$345+A6789';
const listOfIncrementedColumns = [...(anchorSequence
// - split at "'$' followed by 1 basic latin uppercase character
// followed by one or more number character(s)".
.split(/\$[A-Z]\d+/)
// - join array of split results into string again.
.join('')
// - match any sequence of 1 basic latin uppercase character
// followed by an optional '$' followed by one or more number
// character(s) and store each capture into a named group.
.matchAll(/(?<column>[A-Z])(?<anchor>\$*)(?<row>\d+)/g) || [])
// map each regexp result from a list of RegExpStringIterator entries.
].map(({ groups }) => [
// - be aware that "Z" (charCode:90) will be followed by "[" (charCode:91)
// - thus, the handling of this edge case still needs to be implemented.
String.fromCharCode(groups.column.charCodeAt(0) + 1),
groups.anchor,
groups.row
].join(''));
console.log('listOfIncrementedColumns : ', listOfIncrementedColumns);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I am trying to make a regex to matches all the combinations of a given string. For example of the string is "1234", answers would include:
"1"
"123"
"4321"
"4312"
Nonexamples would include:
"11"
"11234"
"44132"
If it matters, the programming language I am using is javascript.
Thank you for any help.
You may use this lookahead based assertions in your regex:
^(?!(?:[^1]*1){2})(?!(?:[^2]*2){2})(?!(?:[^3]*3){2})(?!(?:[^4]*4){2})[1234]+$
RegEx Demo
Here we have 4 lookahead assertions:
(?!(?:[^1]*1){2}): Assert that we don't have more than one instance of 1
(?!(?:[^2]*2){2}): Assert that we don't have more than one instance of 2
(?!(?:[^3]*3){2}): Assert that we don't have more than one instance of 3
(?!(?:[^4]*4){2}): Assert that we don't have more than one instance of 4
We use [1234]+ to match any string with these 4 characters.
A combination of group captures using character classes and negative look-ahead assertions using back-references would do the trick.
Let's begin with simply matching any combination of 1, 2, 3, and 4 using a character class,[1-4], and allowing any length from 1 to 4 characters. {1,4}.
const regex = /^[1-4]{1,4}$/;
// Create set of inputs from 0 to 4322
const inputs = Array.from(new Array(4323), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
When that code is run, it's easy to see that although only numbers consisting of some combination of 1, 2, 3, and 4 are matched, it also is matching numbers with repeating combinations (e.g. 11, 22, 33, 112, etc). Obviously, this was not what was desired.
To prevent repeating characters requires a reference to previously matched characters and then a negation of them from any following matched characters. Negative look-aheads, (?!...) using a back-reference, \1-9, can accomplish this.
Building on the previous example with a subset of the inputs (limiting to a max length of two characters for the moment) would now incorporate a group match surrounding the first character, ([1-4]), followed by a negative look-ahead with a back-reference to the first capture, (?!\1), and finally a second optional character class.
const regex = /^([1-4])(?!\1)[1-4]?$/;
// Create set of inputs from 0 to 44
const inputs = Array.from(new Array(45), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
This matches the desired characters with no repetition!
Expanding this pattern to include back-references for each of the previously matched characters up to the desired max length of 4 yields the following expression.
const regex = /^([1-4])((?!\1)[1-4])?((?!\1|\2)[1-4])?((?!\1|\2|\3)[1-4])?$/;
// Create set of inputs from 0 to 4322
const inputs = Array.from(new Array(4323), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
Hope this helps!
You don't need to use regex for this. The snippet below does the following:
Loop over possible combinations (a => s) (1, 123, 4321, etc.)
Copy the current combination so as not to overwrite it (s2 = s)
Loop over the characters of test string (x => ch) (1234 => 1, 2, 3, 4)
Replace common characters in the combination string shared with the test string (s2.replace)
For example in the combination 1, the 1 will be replaced when the loop gets to the character 1 in 1234 resulting in an empty string
If the combination string's length reaches 0 (s2.length == 0) write the result to the console and break out of the loop (no point in continuing to attempt to replace on an empty string)
const x = "1234"
const a = ["1","123","4321","4312","11","11234","44132"]
a.forEach(function(s) {
var s2 = s
for(var ch of x) {
s2 = s2.replace(ch, '')
if(s2.length == 0) {
console.log(s);
break;
}
}
})
Results:
1
123
4321
4312
I am using Javascript and currently looking for a way to match as many of my pattern's letters as possible, maintaining the original order..
For example a search pattern queued should return the march Queue/queue against the any of the following search strings:
queueTable
scheduledQueueTable
qScheduledQueueTable
As of now I've reached as far as this:
var myregex = new RegExp("([queued])", "i");
var result = myregex.exec('queueTable');
but it doesn't seem to work correctly as it highlights the single characters q,u,e,u,e and e at the end of the word Table.
Any ideas?
Generate the regex with optional non-capturing group part where regex pattern can be generate using Array#reduceRight method.
var myregex = new RegExp("queued"
.split('')
.reduceRight(function(str, s) {
return '(?:' + s + str + ')?';
}, ''), "i");
var result = myregex.exec('queueTable');
console.log(result)
The method generates regex : /(?:q(?:u(?:e(?:u(?:e(?:d?)?)?)?)?)?)?/
UPDATE : If you want to get the first longest match then use g modifier in regex and find out the largest using Array#reduce method.
var myregex = new RegExp(
"queued".split('')
.reduceRight(function(str, s) {
return '(?:' + s + str + ')?';
}, ''), "ig");
var result = 'qscheduledQueueTable'
.match(myregex)
.reduce(function(a, b) {
return a.length > b.length ? a : b;
});
console.log(result);
I think the logic would have to be something like:
Match as many of these letters as possible, in this order.
The only real answer that comes to mind is to get the match to continue if possible, but allow it to bail out. In this case...
myregex = /q(?:u(?:e(?:u(?:e(?:d|)|)|)|)|)/;
You can generate this, of course:
function matchAsMuchAsPossible(word) { // name me something sensible please!
return new RegExp(
word.split("").join("(?:")
+ (new Array(word.length).join("|)"))
);
}
You are using square brackets - which mean that it will match a single instance of any character listed inside.
There are a few ways of interpreting your intentions:
You want to match the word queue with an optional 'd' at the end:
var myregex = new RegExp("queued?", "i");
var result = myregex.exec('queueTable');
Note this can be shorter try this:
'queueTable'.match(/queued?/i);
I also removed the brackets as these were not adding anything here.
This link provides some good examples that may help you further: https://www.w3schools.com/js/js_regexp.asp
When you use [] in a regular expression, it means you want to match any of the characters inside the brackets.
Example: if I use [abc] it means "match a single character, and this character can be 'a', 'b' or 'c'"
So in your code [queued] means "match a single character, and this character can be 'q', 'u', 'e' or 'd'" - note that 'u' and 'e' appear twice so they are redundant in this case. That's why this expression matches just one single character.
If you want to match the whole string "queued", just remove the brackets. But in this case it won't match, because queueTable doesn't have 'd'. If you want 'd' to be optional, you can use queued? as already explained in previous answers.
Try something like the following :
var myregex = /queued?\B/g;
var result = myregex.exec('queueTable');
console.log(result);
I write currently a simple formatting function to replace some placeholders in a string.
var format = function (a, c) {
return a.replace(/{ *([^} ]+) *}/g, function (b, a) {
b = c;
a.replace(/[^.|\[\]]+/g, function (a) {
b = b[a];
});
return b;
});
};
The syntax uses currently curly-bracket notation {key}, I try now to modify the RegExp-pattern to work with one percent instead %key.
var pattern = /{ *([^} ]+) *}/g;
I tried to just replace the parentheses {} with a percent %, but this still doesn't work properly.
var pattern = /% *([^% ]+) */g;
The original pattern works with the following conditions as expected:
var data = {
obj: {
foo: 'Foo',
bar: 'Bar'
},
arr: ['Foo', 'Bar']
};
var objTest = '{obj.foo}, is not equal to {obj.bar}.'
format(objTest, data) // => 'Foo, is not equal to Bar.'
var arrTest = '{arr[0]}, is not equal to {arr[1]}.'
format(arrTest, data) // => 'Foo, is not equal to Bar.'
If we use my modified pattern it seems that the last character after each placeholder-replacement will be removed:
'%obj.foo, is not equal to %obj.bar.' // => 'undefined is not equal to Bar'
'%arr[0], is not equal to %arr[1]' // => 'undefined is not equal to Bar'
Any ideas how to modify the pattern to make it possible to use it with percentage % instead of curly-brackets {}?
You can use this pattern:
var regex = /%([^\b]+)/g;
which means a % sign followed by a complete word. This excludes whitespace characters, underscores, etc.
If you instead want to be able to use those characters as well, you can write:
var regex = /%(\S+)/g;
which is the equivalent of:
var regex = /%([^\s]+)/g;
The reason for this is that your modified regex pattern does not know when to stop matching. In the previous one it terminated at the following } symbol.
This is not an easy thing to fix as there is a myriad of possibilities that could be seen to end your format, here you have a piece of punctuation at the end of each replacement string, i.e. a full stop %arr[1]. or comma %obj.foo,.
So to make this work in your case toy could replace } in the original pattern with [\.,] i.e.
/% *([^% ]+) *[\.,]/g
This will work, but now your replacement pattern needs to always be terminated with either a full stop or comma which i suspect is not exactly what you want. Better to terminate with a know character such as % which would make your matching pattern /% *([^% ]+) *%/g and your format %obj.foo% and you can output a % by doubling up i.e. %obj.bar%%%