1

I'm trying to write a simple text replacer in React.js using a regular expression but I can't wrap my head around it.

So I've got a list of tokens and their corresponding replacement text. I also have a text area with text written by the user. Whenever a word is wrapped around { } the text will be replaced with the replacement text of the corresponding token.

For example if I have a {example} somewhere in my textarea I will have to check my tokenlist and see if I have the value example in the list and replace {example} with the value of the replacement value of the list.

What I'm doing right now is to check if I have any matches in my textarea using:

let regEx = /\{[a-zA-Z_][a-zA-Z0-9_]*\}/;
inputText.match(regEx);

As a result I'm getting an index and the input but how can I replace the matched text with the replacement text? I tried using the replace function but somehow I couldn't understand how to use it.

Here is my filter function for you to check out:

this.filterText = () => {
  //check if we have text in Input text area
  if (this.state.inputText) {
    let regEx = /\{[a-zA-Z_][a-zA-Z0-9_]*\}/;
    let inputText = this.state.inputText;
    this.state.data.forEach((token, index) => {
      let match = inputText.match(regEx);
      if (match) {
        console.log('match:', match);
        //should replace matched text with replacement text
        inputText.replace(regEx, this.state.data[index].replacementText);
      }
    });
  }
}
captain
  • 1,747
  • 5
  • 20
  • 32

3 Answers3

1

Here is a simple vanilla js solution. Try for example to type I like to {make} stuffs anywhere in textarea. {make} should be replaced with create

Edit

Changes have been made to support recursive replacements.

It's now possible to include some {token} inside replacement string.

Code also prevents circular calls.

Try to type {hello} or {circular} to ensure it works the way you want.

Snippet

document.addEventListener("DOMContentLoaded", function() {
  buildResult();
});

let tokens = {
  "civility": "Mr",
  "dummy": "really dummy",
  "foo": "bar",
  "firstName": "Marty",
  "lastName": "McFly",
  "hello": "Hello {firstName} {lastName}",
  "circular": "Hello {circular} {firstName} {lastName}",
  "make": "create",

}

function buildResult() {
  let text = document.getElementById('userInput').value;
  let result = replaceTokens(text);
  document.getElementById('result').innerHTML = result;
}

function replaceTokens(text, replacementStack) {
  // const re = /{([^}]+)}/g; // match anything but a `}` between braces
  const re = /{[\w]*\}/g; // match initial regex

  let result = text;
  let textTokens = text.match(re);
  replacementStack = replacementStack || [];

  textTokens && textTokens.forEach(m => {
    let token = m.replace(/{|}/g, '');
    // Prevent circular replacement, token should not have already replaced
    if (replacementStack.indexOf(token) === -1) {
      // add token to replacement stack
      replacementStack.push(token);
      let replacement = tokens[token];
      if (replacement) {
        replacement = replaceTokens(replacement, replacementStack);
        result = result.replace(m, replacement);
      }
    }
  });

  return result;
}
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <script src="script.js"></script>
  <style>
    label { display: block; font-weight: bold; }
    textarea { width: 600px; height: 150px; }
  </style>
</head>
<body>

  <label>Enter text</label>
  <textarea id="userInput" onkeyup="buildResult()">Lorem Ipsum is simply {dummy} text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to {make} a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</textarea>

  <label>Result</label>
  <div id="result"></div>

</body>
</html>
Stephane Janicaud
  • 3,531
  • 1
  • 12
  • 18
  • I like your solutions, with some tweaks I could make my own example work. I have one question though is there any difference between my regular expression and yours? Would it be difficult to make this example also work recursively? if I have {test} in the replacement text then I will parse it again and check if test is in the token list and replace it in the text. Thank you – captain May 06 '17 at 10:31
  • Done ! I think it cover your needs. – Stephane Janicaud May 06 '17 at 12:33
  • About your regular expression, I just match anything between braces but a `}` but if your only need to match letters, digits or underscore then you can simplify yours like this : `{[\w]*\}`. `\w` matches any letter, digit or underscore. Equivalent to [a-zA-Z0-9_]. Braces don't need to be escaped. – Stephane Janicaud May 06 '17 at 12:58
  • Thank you very much, I couldn't ask for more – captain May 07 '17 at 09:32
0

It's not working because regEx inside the replace function has to be exactly what you are trying to replace (the token itself in this case).

Try this: https://jsfiddle.net/KlaussU/f4mxa3vw/2/

<button onclick="replaceText('tokenX replaced: {tokenX}')">Try it</button>
<p id="demo"></p>

<script>
var replacementText = {
    "tokenX": "tokenXReplaced",
    "tokenY": "tokenYReplaced",
    "tokenZ": "tokenZReplaced"
};

function replaceText(text) {
    for(var rt in replacementText) {
        text = text.replace(new RegExp("{" + rt + "}"), replacementText[rt]);
    }
    document.getElementById("demo").innerHTML = text;
}
</script>

P.S You might find the accepted answer here also useful: Javascript Regex: How to put a variable inside a regular expression?

Community
  • 1
  • 1
KlaussU
  • 101
  • 1
  • 1
  • 5
0

The code you posted never assigns the result of the replace. I'm not sure about the data structure, but the following should somewhat correspond with the layout.

function foo(){
 this.state = {inputText:'this is a test that should {verb} both the first {noun} between {} and also the following {noun}s, but not {other} tags'};
  this.data = {verb:'replace', noun:'element'};
  this.filterText = () => !this.state.inputText || Object.keys(this.data).reduce((t,k)=>
   t.replace(new RegExp('{' + k + '}','g'),this.data[k])
   ,this.state.inputText
  );
}

let f = new foo();console.log(f.filterText());

The idea is to revers the logic, not to find all {} tags, but to use the tokens as the source (which is the same as Klaus' answer I see now). The regex wouldn't even be needed here for the replacement, but it is for the global flag

Me.Name
  • 12,259
  • 3
  • 31
  • 48