Reputation: 54959
I'm looking to do replacements in unknown third-party inputs in strings that sometimes have quotes among them. I want to replace a wholeword whereever it occurs if it's in braces {}
or marked with {replaceinside}{/replaceinside} unless it's in double or single-quotes, and unless the quote is escaped.
Regex so far largely comes from this question: Regex replace word in string that are not in quotes Thanks Wiktor Stribiżew and anubhava who helped me out there.
I thought after asking that question it would be easy to integrate the final braces requirement but guess not.
Here's my test string and the output:
`name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[FOO]}
statement = {FOO} + " is the owner of the house."
{replaceinside}FOO "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"FOO\"{/replaceinside}`
I would like to replace FOO with BAR. Expected Output:
`name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[BAR]}
statement = {BAR} + " is the owner of the house."
{replaceinside}BAR "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"BAR\"{/replaceinside}`
Ultimately I would like to use the text as a template and create an array of strings with mapped replacements. Like so:
var v = "BAR", arr = ["BAR", "BAZ"], finalArr = [], swappedFromValue = "FOO"
const text = String.raw`
name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[FOO]}
statement = {FOO} + " is the owner of the house."
{replaceinside}FOO "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"FOO\"{/replaceinside}
`
var replacementRegex = new RegExp('{(.*?)((?:[^\\\\]|^)(?:\\\\{2})*(?:"[^"\\\\]*(?:\\\\[^][^"\\\\]*)*"|\'[^\'\\\\]*(?:\\\\[^][^\'\\\\]*)*\'))|\\b' + swappedFromValue + '\\b(.*?)}', 'g') // single line
var replacementInsideRegex = new RegExp('{replaceinside}(.*?)((?:[^\\\\]|^)(?:\\\\{2})*(?:"[^"\\\\]*(?:\\\\[^][^"\\\\]*)*"|\'[^\'\\\\]*(?:\\\\[^][^\'\\\\]*)*\'))|\\b' + swappedFromValue + '\\b(.*?){/replaceinside}', 'gs') // multi line
function replaceFunc (v, k) {
var replacedText = text.replace(replacementRegex, function (match, group) {
return group || v;
})
replacedText = replacedText.replace(replacementInsideRegex, function (match, group) {
return group || v;
})
finalArr.push(replacedText)
}
arr.forEach(replaceFunc)
document.body.innerHTML = finalArr.join("<br><br>")
Expected output:
`name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[BAR]}
statement = {BAR} + " is the owner of the house."
{replaceinside}BAR "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"BAR\"{/replaceinside}
name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[BAZ]}
statement = {BAZ} + " is the owner of the house."
{replaceinside}BAZ "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"BAZ\"{/replaceinside}`
fiddle: https://jsfiddle.net/x7016sv4/1/
Upvotes: 1
Views: 52
Reputation: 350365
As the solution should also deal well with nested braces, and JavaScript regular expression have no recursion capability, I would suggest to move some of the logic out of the regular expression into JavaScript code. A regular expression can still be used to identify the individual parts, but some code will be needed to:
{ aa {replaceinside} bb } cc {/replaceinside} dd
. For instance, braces could be thought of as ineffective inside tags.function solution(text, source, target) {
const regex = RegExp(String.raw`(?:\\["'])+|\{\/?replaceinside\}|[{}]|(['"])(?:\\.|.)*?\1|\b${source}\b|.`, "gs");
let stack = [];
return input.replace(regex, function (token) {
if (token === "{replaceinside}") {
stack.push("{/replaceinside}");
} else if (token === "{" && stack.at(-1) !== "{/replaceinside}") { // Braces inside tags have no meaning
stack.push("}");
} else if (token === stack.at(-1)) { // Pair balanced braces/tags
stack.pop();
} else if (token === "\n") { // Auto-close all braces
stack = stack.filter(item => item !== "}");
} else if (token === source && stack.length) { // Only replace within braces/tags
return target;
}
return token;
});
}
let input = String.raw`name: FOO
{favoriteQuote: "I am my own FOO."}
children: 'FOO\'s children'
{cars: ownersList[FOO]}
statement = {FOO} + " is the owner of the house."
{replaceinside}FOO "FOO" {'FOO' " 1 + FOO + 2 " ABCFOOXYZ} " str1\"FOO\"str3'FOO'\'\'" ' str1\'FOOstr3"FOO"\"\"' \"FOO\"{/replaceinside}`;
console.log(solution(input, "FOO", "BAR"));
Upvotes: 1