简体   繁体   中英

How to create a search language?

I would like to create a simple filtering language in Javascript.

I want to search inside a collection of products, say:

product=[
   {price:10,name:"T-Shirt",category:"Clothing",published_at:"07-08-2014",size:"10x30",color:"#0000FF"},
   {price:20,name:"Chair",category:"Furniture",published_at:"09-03-2013",size:"30x30",color:"#00FF00"},
   {price:30,name:"iPhone",category:"Phones",published_at:"17-03-2014",size:"40x30",color:"#FF00FF"},
   {price:40,name:"Samsung Galaxy",category:"Phones",published_at:"12-01-2012",size:"10x60",color:"#00BBBB"},
];

With only one input of text, I would like to be able to query inside this array, for example:

  • cat:Clothing => gives back the tshirt
  • price:>15 => gives back Chair, iPhone, and Samsung Galaxy
  • name:iP => Filters trough the names and gives back iPhone
  • price:>15&&size:>35x25 => Filters trough the names and gives back iPhone

I know they are some language parsers like

but I don't know which one to choose (and why) and if it is a good idea to use one ? Any ideas ?

I think this is simple enough to do yourself without a library.

Here's my crack at it:

http://jsfiddle.net/qrz48/2/

// polyfill Array.prototype.forEach if you need to support older browsers...
// there's one at: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

var product = [
    {price:10,name:"T-Shirt",category:"Clothing",published_at:"07-08-2014",size:"10x30",color:"#0000FF"},
    {price:20,name:"Chair",category:"Furniture",published_at:"09-03-2013",size:"30x30",color:"#00FF00"},
    {price:30,name:"iPhone",category:"Phones",published_at:"17-03-2014",size:"40x30",color:"#FF00FF"},
    {price:40,name:"Samsung Galaxy",category:"Phones",published_at:"12-01-2012",size:"10x60",color:"#00BBBB"}
];

// extend this as you require new search operators.
var operators = {
    "==": function(a, b) {return a == b;},
    "===": function(a, b) {return a === b;},
    ">": function(a, b) {return a > b;},
    ">=": function(a, b) {return a >= b;},
    "<": function(a, b) {return a < b;},
    "<=": function(a, b) {return a <= b;},
    "*": function(a, b) {return a.indexOf(b) > -1;}
};

// usage: find("category", "===", "Clothing")
function find(key, operator, condition, searchIn) {
    if( ! searchIn) {
        searchIn = product;
    }
    var result = [];
    searchIn.forEach(function(item) {
        if(operators[operator](item[key], condition)) {
            result.push(item);
        }
    });
    return result;
}

// usage: query("category:===:Clothing");
function query(str) {
    var conditions = str.split("&&");
    var result = [];
    conditions.forEach(function(condition, index) {
        var parts = condition.split(":");
        var key = parts[0];
        var operator = parts[1];
        var condition = parts[2];
        var searchIn = (conditions.length > 1 && index > 0) ? result : null;
        result = find(key, operator, condition, searchIn);
    });
    return result;
}


// usage
console.log(query("category:===:Clothing"));
console.log(query("price:>:20"));
console.log(query("name:*:iP"));
console.log(query("price:>:20&&name:*:Galaxy"));

You can pass a search string to the query function above, as requested, with the exception that a colon is required both before and after the condition operator. For example, to find all products where the price is greater than 20, run:

query("price:>:20");

You can also combine search conditions using the format in your question:

query("price:>:20&&category:===:Clothing");

I couldn't think how to do your size comparison without splitting the size data out into separate values, eg sizeX and sizeY , which would be easier to compare using something like

query("sizeX:>:30&&sizeY:>:20");

If anything this was just quite fun to write. What do you think?

I have decided to test with JISON (Similar to BISON)

/* description: Parses end executes mathematical expressions. */

/* lexical grammar */
%lex
%%

\s+                   /* skip whitespace */
[0-9]+("."[0-9]+)?\b  return 'NUMBER'
">"                     return '>'
"price:"              return 'PRICE'
<<EOF>>               return 'EOF'
"name:"               return 'NAME'
[a-z]+                return 'STRING'
","                   return 'COMMA'


/lex

/* operator associations and precedence */

%left 'COMMA'
%left 'PRICE' 'NAME'


%start expressions

%% /* language grammar */

expressions
    : e EOF
        { typeof console !== 'undefined' ? console.log($1) : print($1);
          return $1; }
    ;

e
    : 'PRICE' '>' e
        {$$ = {price:{gt:$3}};}
    | 'NAME' e
        {$$ = {name:{contains:$2}};}
    | NUMBER
        {$$ = Number(yytext);}
    | STRING
        {$$ = String(yytext);}
    | e 'COMMA' e
        { for (var attrname in $1) { $3[attrname]=$1[attrname]; $$ = $3; }}
    ;

The result of the parser is the following:

price:>30 => { price: { gt: 30 } }

name:blabla,price:>10 => { price: { gt: 10 }, name: { contains: 'blabla' } }

name:test => { name: { contains: 'test' } }

This solution seems a bit more portable to me, because it doesn't directly deal with the parsing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM