Emulating SQL LIKE in JavaScript

后端 未结 9 1900
有刺的猬
有刺的猬 2020-11-30 05:56

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

相关标签:
9条回答
  • 2020-11-30 06:39

    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
    
    0 讨论(0)
  • 2020-11-30 06:40

    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                                                                     
    0 讨论(0)
  • 2020-11-30 06:42

    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;
    
    0 讨论(0)
  • 2020-11-30 06:42

    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);
    }
    
    0 讨论(0)
  • 2020-11-30 06:54

    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.

    0 讨论(0)
  • 2020-11-30 06:54

    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

    0 讨论(0)
提交回复
热议问题