How can I emulate the SQL keyword LIKE
in JavaScript?
For those of you who don\'t know what LIKE
is, it\'s a very simple regex which only s
I wanted something that also handles escaping the wildcards %
and _
using \%
and \_
.
Here is my solution using negative lookbehind:
// escapes RegExp special characters
const escapePattern = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
// converts ILIKE pattern to a RegExp object
const ilikeToRegExp = pattern =>
new RegExp(
`^${escapePattern(pattern)}$`
// convert ILIKE wildcards, don't match escaped
.replace(/(?<![\\])%/g, '.*')
.replace(/(?<![\\])_/g, '.')
// replace ILIKE escapes
.replace(/\\%/g, '%')
.replace(/\\_/g, '_'),
'i'
);
Usage:
ilikeToRegExp('%eLlo WoR%').test('hello world')
// true
ilikeToRegExp('ello wor').test('hello world')
// false
ilikeToRegExp('%90\%%').test('...90%...')
// true
I needed this, with escaping and working in Safari (no negative lookbehinds). Here is what I came up with:
/**
* Quotes a string following the same rules as https://www.php.net/manual/function.preg-quote.php
*
* Sourced from https://locutus.io/php/preg_quote/
*
* @param {string} str String to quote.
* @param {?string} [delimiter] Delimiter to also quote.
* @returns {string} The quoted string.
*/
function regexQuote(str, delimiter) {
return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
}
/**
* Removes the diacritical marks from a string.
*
* Diacritical marks: {@link https://unicode-table.com/blocks/combining-diacritical-marks/}
*
* @param {string} str The string from which to strip the diacritical marks.
* @returns {string} Stripped string.
*/
function stripDiacriticalMarks(str) {
return unorm.nfkd(str).replaceAll(/[\u0300-\u036f]+/g, '');
}
/**
* Checks if the string `haystack` is like `needle`, `needle` can contain '%' and '_'
* characters which will behave as if used in a SQL LIKE condition. Character escaping
* is supported with '\'.
*
* @param {string} haystack The string to check if it is like `needle`.
* @param {string} needle The string used to check if `haystack` is like it.
* @param {boolean} [ai] Whether to check likeness in an accent-insensitive manner.
* @param {boolean} [ci] Whether to check likeness in a case-insensitive manner.
* @returns {boolean} True if `haystack` is like `needle`, otherwise, false.
*/
function strLike(haystack, needle, ai = true, ci = true) {
if (ai) {
haystack = stripDiacriticalMarks(haystack);
needle = stripDiacriticalMarks(needle);
}
needle = regexQuote(needle, '/');
let tokens = [];
for (let i = 0; i < needle.length; ) {
if (needle[i] === '\\') {
i += 2;
if (i < needle.length) {
if (needle[i] === '\\') {
tokens.push('\\\\');
i += 2;
} else {
tokens.push(needle[i]);
++i;
}
} else {
tokens.push('\\\\');
}
} else {
switch (needle[i]) {
case '_':
tokens.push('.')
break;
case '%':
tokens.push('.*')
break;
default:
tokens.push(needle[i]);
break;
}
++i;
}
}
return new RegExp(`^${tokens.join('')}$`, `u${ci ? 'i' : ''}`).test(haystack);
}
/**
* Escapes a string in a way that `strLike` will match it as-is, thus '%' and '_'
* would match a literal '%' and '_' respectively (and not behave as in a SQL LIKE
* condition).
*
* @param {string} str The string to escape.
* @returns {string} The escaped string.
*/
function escapeStrLike(str) {
let tokens = [];
for (let i = 0; i < str.length; i++) {
switch (str[i]) {
case '\\':
tokens.push('\\\\');
break;
case '%':
tokens.push('\\%')
break;
case '_':
tokens.push('\\_')
break;
default:
tokens.push(str[i]);
}
}
return tokens.join('');
}
The code above is dependant on unorm, and is unicode aware to be able to catch cases like:
strLike('Hello
What you have will work as long as you first escape the regex characters in your pattern. Below is one example from Simon Willison’s blog:
RegExp.escape = function(text) {
if (!arguments.callee.sRE) {
var specials = [
'/', '.', '*', '+', '?', '|',
'(', ')', '[', ']', '{', '}', '\\'
];
arguments.callee.sRE = new RegExp(
'(\\' + specials.join('|\\') + ')', 'g'
);
}
return text.replace(arguments.callee.sRE, '\\$1');
}
You could then implement your code as:
likeExpr = RegExp.escape(likeExpr);
var match = new RegEx(likeExpr.replace("%", ".*").replace("_", ".")).exec(str) != null;
Johnny come lately here but this works for me I use it for my spa pages to avoid certain pages showing results after the default page:
function like(haystack,needle){
needle = needle.split(',');
var str = haystack.toLowerCase();
var n = -1;
for(var i=0;i<needle.length;i++){
n = str.search(needle[i]);
if(n > -1){
return n;
}
}
return n;
}
usage is - here I want to not show any results on the tools,contact or home pages - results() is a function I do not show here:
var n = like($data,'tools,contact,home');
//~ alert(n);
if(n < 0){// does not match anything in the above string
results($data);
}
I was looking for an answer the same question and came up with this after reading Kip's reply:
String.prototype.like = function(search) {
if (typeof search !== 'string' || this === null) {return false; }
// Remove special chars
search = search.replace(new RegExp("([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-])", "g"), "\\$1");
// Replace % and _ with equivalent regex
search = search.replace(/%/g, '.*').replace(/_/g, '.');
// Check matches
return RegExp('^' + search + '$', 'gi').test(this);
}
You can then use it as follows (note that it ignores UPPER/lower case):
var url = 'http://www.mydomain.com/page1.aspx';
console.log(url.like('%mydomain.com/page_.asp%')); // true
NOTE 29/11/2013: Updated with RegExp.test()
performance improvement as per Lucios comment below.
In Chris Van Opstal's answer you should use replaceAll instead of replace to replace all occurrances of '%' and '_'. Reference to how to do replaceAll - here