ignacio.munizaga
ignacio.munizaga

Reputation: 1603

Replace text but keep case

I have a set of strings that I need to replace, but I need to keep the case of letters.

Both the input words and output words are of the same length.

For example, if I need to replace "abcd" with "qwer", then the following should happen:

"AbcD" translates to "QweR"
"abCd" translates to "qwEr"

and so on.

Right now I'm using JavaScript's replace, but capital letters are lost on translation.

r = new RegExp( "(" + 'asdf' + ")" , 'gi' );
"oooAsdFoooo".replace(r, "qwer");

Any help would be appreciated.

Upvotes: 29

Views: 16554

Answers (12)

craig
craig

Reputation: 161

I really like the answer from Stephen Quan but found it didn't handle cases where the replacement string is a different length from the match string. Here's my update to his answer

const replaceCase = (str, pattern, newStr) => {
  const rx = new RegExp(pattern, 'ig');
  const replacer = (c, i) => (c.match(/[A-Z]/) ? newStr[i].toUpperCase() : newStr[i] ?? '');
  const [match] = str.match(rx) ?? [];
  return str.replace(rx, (oldStr) => oldStr.replace(/./g, replacer)) + newStr.slice(match?.length ?? 0);
};

And in TypeScript for those who prefer

const replaceCase = (str: string, pattern: RegExp, newStr: string) => {
  const rx = new RegExp(pattern, 'ig');
  const replacer = (c: string, i: number) =>
    c.match(/[A-Z]/) ? newStr[i].toUpperCase() : newStr[i] ?? '';
  const [match] = str.match(rx) ?? [];
  return (
    str.replace(rx, (oldStr) => oldStr.replace(/./g, replacer)) + newStr.slice(match?.length ?? 0)
  );
};

Upvotes: 0

Rafael Sanabria
Rafael Sanabria

Reputation: 309

Here is a replaceAllCaseSensitive function. If your want, you can change replaceAll by replace.

const replaceAllCaseSensitive = (
  text, // Original string
  pattern, // RegExp with the pattern you want match. It must include the g (global) and i (case-insensitive) flags.
  replacement // string with the replacement
) => {
  return text.replaceAll(pattern, (match) => {
    return replacement
      .split("")
      .map((char, i) =>
        match[i] === match[i].toUpperCase() ? char.toUpperCase() : char
      )
      .join("");
  });
};

console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwer"));
// outputs "QweR qwEr"

console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwe"));
// outputs "Qwe qwE"

The function works even if replacement is shorter than match.

Upvotes: 0

tklodd
tklodd

Reputation: 1079

Thank you for asking this. I had the same problem when I wanted to search text and replace certain words with links, which was a slightly more specific situation because it is replacing text strings with html strings. I'll put my solution here in case anyone who finds this is doing anything similar.

let elmt = document.getElementById('the-element');
let a = document.createElement('a');
a.href = "https://www.example.com";
let re = new RegExp('the string to find', 'gi');
elmt.innerHTML = elmt.innerHTML.replaceAll(re, function (match) {
    a.innerText = match;
    return a.outerHTML;
});

So the regular expression ensures that it searches for case-insensitive matches, and the function as the second argument of the replaceAll function specifies that it is supposed to set the innerText of the new tag equal to the old string verbatim, before then returning the outerHTML of the whole tag.

Upvotes: 0

Silvio
Silvio

Reputation: 6031

I'll leave this here for reference.

Scenario: case-insensitive search box on list of items, partial match on string should be displayed highlighted but keeping original case.

highlight() {
  const re = new RegExp(this.searchValue, 'gi'); // global, insensitive
  const newText = name.replace(re, `<b>$&</b>`);
  return newText;
}

the $& is the matched text with case

Upvotes: 16

Pedro
Pedro

Reputation: 1

I think this could work

function formatItem(text, searchText){
    const search = new RegExp(escapeRegExp(searchText), 'iu')
    return text?.toString().replace(search, (m) => `<b>${m}</b>`)
}

function escapeRegExp(text) {
  return text?.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') ?? '';
}

Upvotes: 0

Eray Chumak
Eray Chumak

Reputation: 226

I had a sentence where I had to replace each word with another word and that word can be longer/shorter than the word its replacing so its similar to the question but instead of a fixed length, they're dynamic.

My solution

For simplicity, I am focusing on a single word.

const oldWord = "tEsT";
const newWord = "testing";

Split both words so that I can iterate over each individual letters.

const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");

Now, I would iterate over the newWord letters and use its index to then get the corresponding oldWord letter in the same position. Then I would check if the old letter is capital and if it is then make the new letter in the same position capital as well.

for (const [i, letter] of newWordLetters.entries()) {
  const oldLetter = oldWordLetters[i];

  // stop iterating if oldWord is shorter (not enough letters to copy case).
  if (!oldLetter) {
    break;
  }

  const isCapital = oldLetter === oldLetter.toUpperCase();

  // make the new letter in the same position as the old letter capital
  if (isCapital) {
    newWordLetters[i] = letter.toUpperCase();
  }
}

The final world would be tEsTing after joining the letters again.

const finalWord = newWordLetters.join("");

console.log(finalWord); // "tEsTing"

Full code

const oldWord = "tEsT";
const newWord = "testing";

const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");

for (const [i, letter] of newWordLetters.entries()) {
  const oldLetter = oldWordLetters[i];

  // stop iterating if oldWord is shorter (not enough letters to copy case).
  if (!oldLetter) {
    break;
  }

  const isCapital = oldLetter === oldLetter.toUpperCase();

  // make the new letter in the same position as the old letter capital
  if (isCapital) {
    newWordLetters[i] = letter.toUpperCase();
  }
}

const finalWord = newWordLetters.join("");

console.log(finalWord);

Upvotes: 1

Stephen Quan
Stephen Quan

Reputation: 26379

Here's a replaceCase function:

  1. We turn the input pattern into a regular expression
  2. We have a nested replacer function which iterates through every character
  3. We use regular expression /[A-Z]/ to identify capital letters, otherwise we assume everything is in lowercase

function replaceCase(str, pattern, newStr) {
  const rx = new RegExp(pattern, "ig")
  const replacer = (c, i) => c.match(/[A-Z]/) ? newStr[i].toUpperCase() : newStr[i]
  return str.replace(rx, (oldStr) => oldStr.replace(/./g, replacer) )
}

let out = replaceCase("This is my test string: AbcD", "abcd", "qwer")
console.log(out) // This is my test string: QweR
out = replaceCase("This is my test string: abCd", "abcd", "qwer")
console.log(out) // This is my test string: qwEr

Upvotes: 2

Karan Jariwala
Karan Jariwala

Reputation: 727

This should replace while preserving the case. Please let me know if anyone finds any flaws in this solution. I hope this helps. Thank-you!

function myReplace(str, before, after) {

      var match=function(before,after){
        after=after.split('');
        for(var i=0;i<before.length;i++)
          {

            if(before.charAt(i)==before[i].toUpperCase())
              {
                after[i]=after[i].toUpperCase();
              }
            else  if(before.charAt(i)==before[i].toLowerCase())
              {
                after[i]=after[i].toLowerCase();
              }
            return after.join('');
          }

      };
           console.log(before,match(before,after));
          str =str.replace(before,match(before,after)); 


      return str;
    }

    myReplace("A quick brown fox jumped over the lazy dog", "jumped", "leaped");

Upvotes: 1

Barry G
Barry G

Reputation: 221

Expanding on Ryan O'Hara's answer, the below solution avoids using charCodes and the issues that maybe encountered in using them. It also ensures the replacement is complete when the strings are of different lengths.

function enforceLength(text, pattern, result) {
  if (text.length > result.length) {  
    result = result.concat(text.substring(result.length, text.length));
  }

  if (pattern.length > text.length) {
    result = result.substring(0, text.length);
  }

  return result;
}

function matchCase(text, pattern){
  var result = '';

  for (var i =0; i < pattern.length; i++){
    var c = text.charAt(i);
    var p = pattern.charAt(i);

    if(p === p.toUpperCase()) {
       result += c.toUpperCase();
    } else {
       result += c.toLowerCase();
    }
  }  
  return enforceLength(text, pattern, result);
}

Upvotes: 1

TommyBs
TommyBs

Reputation: 9658

You could create your own replace function such as

 if(!String.prototype.myreplace){
String.prototype.myreplace = (function(obj){
    return this.replace(/[a-z]{1,1}/gi,function(a,b){
       var r = obj[a.toLowerCase()] || a;
        return a.charCodeAt(0) > 96? r.toLowerCase() : r.toUpperCase();
    });
});
}

This takes in a object that maps different letters. and it can be called such as follows

  var obj = {a:'q',b:'t',c:'w'};

  var s = 'AbCdea';
  var n = s.myreplace(obj);
  console.log(n);

This means you could potentially pass different objects in with different mappings if need be. Here's a simple fiddle showing an example (note the object is all lowercase but the function itself looks at case of the string as well)

Upvotes: 1

Ry-
Ry-

Reputation: 225291

Here’s a helper:

function matchCase(text, pattern) {
    var result = '';

    for(var i = 0; i < text.length; i++) {
        var c = text.charAt(i);
        var p = pattern.charCodeAt(i);

        if(p >= 65 && p < 65 + 26) {
            result += c.toUpperCase();
        } else {
            result += c.toLowerCase();
        }
    }

    return result;
}

Then you can just:

"oooAsdFoooo".replace(r, function(match) {
    return matchCase("qwer", match);
});

Upvotes: 12

Tomalak
Tomalak

Reputation: 338426

String.prototype.translateCaseSensitive = function (fromAlphabet, toAlphabet) {
    var fromAlphabet = fromAlphabet.toLowerCase(),
        toAlphabet = toAlphabet.toLowerCase(),
        re = new RegExp("[" + fromAlphabet + "]", "gi");

    return this.replace(re, function (char) {
        var charLower = char.toLowerCase(),
            idx = fromAlphabet.indexOf(charLower);

        if (idx > -1) {
            if (char === charLower) {
                return toAlphabet[idx];
            } else {
                return toAlphabet[idx].toUpperCase();
            }
        } else {
            return char;
        }
    });
};

and

"AbcD".translateCaseSensitive("abcdefg", "qwertyu")

will return:

"QweR"

Upvotes: 2

Related Questions