I would like to do a case-insensitive string replace-all in JavaScript without using a regex (or regex-style strings in calls to the replace method). I could not find a question or answer for this, but please link it if I missed it.
e.g., replace 'abc' with 'x' in:
Find aBc&def stuff ABCabc
becomes Find x&def stuff xx
The result should retain the original case for the portions not replaced.
The string may have special characters in it, so this is why I'm avoiding the regex. My particular problem may be solvable with a regex, but I'm interested in avoiding it completely.
There are several questions and answers that use a regex, and include handling of the special characters. In particular, bobince's answer here https://stackoverflow.com/a/280837/292060 describes how it may be impossible without knowing or acting on specific conditions in the original string.
I'm thinking it will involve a loop and indexOf, and walking through the original string, building a result.
For the sake of this question, let's say performance is not a primary concern. e.g., looping characters is ok to do.
There are some existing questions that include regex for all the answers:
EDIT:
From some of the answers, some clarifications — I didn't spec these originally, but they are typical search/replace behavior:
Can replace with same string, e.g., replace 'abc' with 'Abc', say to fix the title case for a name.
The replacement shouldn't be re-checked, e.g., replacing 'ab' with 'abc' should work. e.g., replacing 'abc' with 'ab' in abcc
becomes abc
not ab
.
I think these boil down to the replacement should be done, then move on in the string, without "looking back".
EDIT: Here are some test cases just for the record. I didn't get into empty strings, etc., which probably should get tested too. https://jsfiddle.net/k364st09/1/
("Find aBc&def abc", "abc", "xy") - Find xy&def xy - general test
("Find aBc&def abc", "abc", "ABC") - Find ABC&def ABC - replace same test, avoid infinite loop
("Find aBcc&def abc", "abc", "ab") - Find abc&def ab - "move on" avoid double checking (fails if abcc becomes ab)
("abc def", "abc", "xy") - xy def - Don't drop last characters.
("abcc def", "abc", "xy") - xyc def - Just a mix of "move on" and "don't drop last".
Just for fun I've created an interactive version where you can see the results of both a regex and indexOf, to see if escaping a regex breaks anything. The method used to escape the regex I took from jQuery UI. If you have it included on the page it can be found with $.ui.autocomplete.escapeRegex
. Otherwise, it's a pretty small function.
Here's the non-regex function, but since the interactive section adds a lot more code I have the full code snippet hidden by default.
function insensitiveReplaceAll(original, find, replace) {
var str = "",
remainder = original,
lowFind = find.toLowerCase(),
idx;
while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
str += remainder.substr(0, idx) + replace;
remainder = remainder.substr(idx + find.length);
}
return str + remainder;
}
// example call:
insensitiveReplaceAll("Find aBcc&def stuff ABCabc", "abc", "ab");
function insensitiveReplaceAll(original, find, replace) {
var str = "",
remainder = original,
lowFind = find.toLowerCase(),
idx;
while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
str += remainder.substr(0, idx) + replace;
remainder = remainder.substr(idx + find.length);
}
return str + remainder;
}
function escapeRegex(value) {
return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
function updateResult() {
var original = document.getElementById("original").value || "",
find = document.getElementById("find").value || "",
replace = document.getElementById("replace").value || "",
resultEl = document.getElementById("result"),
regexEl = document.getElementById("regex");
if (original && find && replace) {
regexEl.value = original.replace(new RegExp(escapeRegex(find), "gi"), replace);
resultEl.value = insensitiveReplaceAll(original, find, replace);
} else {
regexEl.value = "";
resultEl.value = "";
}
}
document.addEventListener("input", updateResult);
window.addEventListener("load", updateResult);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<div class="input-group input-group-sm">
<span class="input-group-addon">Original</span>
<input class="form-control" id="original" value="Find aBcc&def stuff ABCabc" />
</div>
<div class="input-group input-group-sm">
<span class="input-group-addon">Find</span>
<input class="form-control" id="find" value="abc" />
</div>
<div class="input-group input-group-sm">
<span class="input-group-addon">Replace</span>
<input class="form-control" id="replace" value="ab" />
</div>
<div class="input-group input-group-sm">
<span class="input-group-addon">Result w/o regex</span>
<input disabled class="form-control" id="result" />
</div>
<div class="input-group input-group-sm">
<span class="input-group-addon">Result w/ regex</span>
<input disabled class="form-control" id="regex" />
</div>