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:
// 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.