diff --git a/core/modules/filters.js b/core/modules/filters.js index 6784efae1..9a18eb2af 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -62,43 +62,61 @@ function parseFilterOperation(operators,filterString,p) { else if(operator.operator === "") { operator.operator = "title"; } + operator.operands = []; + function parseOperand(bracketType) { + var operand = {}; + switch (bracketType) { + case "{": // Curly brackets + operand.indirect = true; + nextBracketPos = filterString.indexOf("}",p); + break; + case "[": // Square brackets + nextBracketPos = filterString.indexOf("]",p); + break; + case "<": // Angle brackets + operand.variable = true; + nextBracketPos = filterString.indexOf(">",p); + break; + case "/": // regexp brackets + var rex = /^((?:[^\\\/]*|\\.)*)\/(?:\(([mygi]+)\))?/g, + rexMatch = rex.exec(filterString.substring(p)); + if(rexMatch) { + operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); + // DEPRECATION WARNING + console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp); + nextBracketPos = p + rex.lastIndex - 1; + } + else { + throw "Unterminated regular expression in filter expression"; + } + break; + } + if(nextBracketPos === -1) { + throw "Missing closing bracket in filter expression"; + } + if(!operator.regexp) { + operand.text = filterString.substring(p,nextBracketPos); + operator.operands.push(operand); + } + p = nextBracketPos + 1; + } + p = nextBracketPos + 1; - switch (bracket) { - case "{": // Curly brackets - operator.indirect = true; - nextBracketPos = filterString.indexOf("}",p); - break; - case "[": // Square brackets - nextBracketPos = filterString.indexOf("]",p); - break; - case "<": // Angle brackets - operator.variable = true; - nextBracketPos = filterString.indexOf(">",p); - break; - case "/": // regexp brackets - var rex = /^((?:[^\\\/]*|\\.)*)\/(?:\(([mygi]+)\))?/g, - rexMatch = rex.exec(filterString.substring(p)); - if(rexMatch) { - operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); -// DEPRECATION WARNING -console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp); - nextBracketPos = p + rex.lastIndex - 1; - } - else { - throw "Unterminated regular expression in filter expression"; - } - break; + parseOperand(bracket); + + // Check for multiple operands + while(filterString.charAt(p) === ",") { + p++; + if(/^[\[\{<\/]/.test(filterString.substring(p))) { + nextBracketPos = p; + p++; + parseOperand(filterString.charAt(nextBracketPos)); + } else { + throw "Missing [ in filter expression"; + } } - - if(nextBracketPos === -1) { - throw "Missing closing bracket in filter expression"; - } - if(!operator.regexp) { - operator.operand = filterString.substring(p,nextBracketPos); - } - p = nextBracketPos + 1; - + // Push this operator operators.push(operator); } while(filterString.charAt(p) !== "]"); @@ -152,7 +170,7 @@ exports.parseFilter = function(filterString) { } if(match[4] || match[5] || match[6]) { // Double quoted string, single quoted string or unquoted title operation.operators.push( - {operator: "title", operand: match[4] || match[5] || match[6]} + {operator: "title", operands: [{text: match[4] || match[5] || match[6]}]} ); } results.push(operation); @@ -209,7 +227,7 @@ exports.compileFilter = function(filterString) { results = [], currTiddlerTitle = widget && widget.getVariable("currentTiddler"); $tw.utils.each(operation.operators,function(operator) { - var operand = operator.operand, + var operands = [], operatorFunction; if(!operator.operator) { operatorFunction = filterOperators.title; @@ -218,16 +236,23 @@ exports.compileFilter = function(filterString) { } else { operatorFunction = filterOperators[operator.operator]; } - if(operator.indirect) { - operand = self.getTextReference(operator.operand,"",currTiddlerTitle); - } - if(operator.variable) { - operand = widget.getVariable(operator.operand,{defaultValue: ""}); - } + + $tw.utils.each(operator.operands,function(operand) { + if(operand.indirect) { + operand.value = self.getTextReference(operand.text,"",currTiddlerTitle); + } else if(operand.variable) { + operand.value = widget.getVariable(operand.text,{defaultValue: ""}); + } else { + operand.value = operand.text; + } + operands.push(operand.value); + }); + // Invoke the appropriate filteroperator module results = operatorFunction(accumulator,{ operator: operator.operator, - operand: operand, + operand: operands.length > 0 ? operands[0] : undefined, + operands: operands, prefix: operator.prefix, suffix: operator.suffix, suffixes: operator.suffixes, diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js index aa25a1121..cac120c20 100644 --- a/editions/test/tiddlers/tests/test-filters.js +++ b/editions/test/tiddlers/tests/test-filters.js @@ -19,19 +19,41 @@ describe("Filter tests", function() { // Test filter parsing it("should parse new-style rich operator suffixes", function() { expect($tw.wiki.parseFilter("[search:: four, , five,, six [operand]]")).toEqual( - [ { prefix : '', operators : [ { operator : 'search', suffix : ': four, , five,, six ', suffixes : [ [ ], [ 'four', 'five', 'six' ] ], operand : 'operand' } ] } ] + [ { prefix : '', operators : [ { operator : 'search', suffix : ': four, , five,, six ', suffixes : [ [ ], [ 'four', 'five', 'six' ] ], operands: [ { text:'operand' } ] } ] } ] ); expect($tw.wiki.parseFilter("[search: one, two ,three :[operand]]")).toEqual( - [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three :', suffixes : [ [ 'one', 'two', 'three' ], [ ] ], operand : 'operand' } ] } ] + [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three :', suffixes : [ [ 'one', 'two', 'three' ], [ ] ], operands: [ { text:'operand' } ] } ] } ] ); expect($tw.wiki.parseFilter("[search: one, two ,three :[operand]]")).toEqual( - [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three :', suffixes : [ [ 'one', 'two', 'three' ], [ ] ], operand : 'operand' } ] } ] + [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three :', suffixes : [ [ 'one', 'two', 'three' ], [ ] ], operands: [ { text:'operand' } ] } ] } ] ); expect($tw.wiki.parseFilter("[search: one, two ,three : four, , five,, six [operand]]")).toEqual( - [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three : four, , five,, six ', suffixes : [ [ 'one', 'two', 'three' ], [ 'four', 'five', 'six' ] ], operand : 'operand' } ] } ] + [ { prefix : '', operators : [ { operator : 'search', suffix : ' one, two ,three : four, , five,, six ', suffixes : [ [ 'one', 'two', 'three' ], [ 'four', 'five', 'six' ] ], operands: [ { text:'operand' } ] } ] } ] ); expect($tw.wiki.parseFilter("[search: , : [operand]]")).toEqual( - [ { prefix : '', operators : [ { operator : 'search', suffix : ' , : ', suffixes : [ [ ], [ ] ], operand : 'operand' } ] } ] + [ { prefix : '', operators : [ { operator : 'search', suffix : ' , : ', suffixes : [ [ ], [ ] ], operands: [ { text:'operand' } ] } ] } ] + ); + }); + + + it("should parse multiple operands for operators", function() { + expect($tw.wiki.parseFilter("[search: , : [operand],[operand2]]")).toEqual( + [ { prefix : '', operators : [ { operator : 'search', suffix : ' , : ', suffixes : [ [ ], [ ] ], operands: [ { text:'operand' }, { text:'operand2' } ] } ] } ] + ); + expect($tw.wiki.parseFilter("[search: , : [oper,and],[operand2]]")).toEqual( + [ { prefix : '', operators : [ { operator : 'search', suffix : ' , : ', suffixes : [ [ ], [ ] ], operands: [ { text:'oper,and' }, { text:'operand2' } ] } ] } ] + ); + expect($tw.wiki.parseFilter("[[GettingStarted]replace:[operand],[operand2]]")).toEqual( + [ { prefix : '', operators : [ { operator : 'title', operands: [ { text:'GettingStarted' } ] }, { operator : 'replace', suffix : '', suffixes : [[]], operands: [ { text:'operand' }, { text:'operand2' } ] } ] } ] + ); + expect($tw.wiki.parseFilter("[[GettingStarted]replace[operand],[operand2]split[-]]")).toEqual( + [ { prefix : '', operators : [ { operator : 'title', operands: [{ text:'GettingStarted' }] }, { operator : 'replace', operands: [{ text:'operand' }, { text:'operand2' }] }, { operator : 'split', operands: [ { text:'-' } ] } ] } ] + ); + expect($tw.wiki.parseFilter("[[GettingStarted]replace[operand],[operand2]split[-]split2[a],[b]]")).toEqual( + [ { prefix : '', operators : [ { operator : 'title', operands: [{ text:'GettingStarted' }] }, { operator : 'replace', operands: [ { text:'operand' }, { text:'operand2' } ] }, { operator : 'split', operands: [ {text:'-'} ] }, { operator : 'split2', operands: [ { text:'a' }, { text: 'b' }] } ] } ] + ); + expect($tw.wiki.parseFilter("[[GettingStarted]replace[operand],[operand2]split[-]split2[a],,{c}]")).toEqual( + [ { prefix : '', operators : [ { operator : 'title', operands: [{ text:'GettingStarted' }] }, { operator : 'replace', operands: [ { text:'operand' }, { text:'operand2' } ] }, { operator : 'split', operands: [ {text:'-'} ] }, { operator : 'split2', operands: [ { text:'a' }, { variable: true, text: 'b' }, { indirect: true, text: 'c' }] } ] } ] ); }); @@ -730,7 +752,6 @@ function runTests(wiki) { expect(wiki.filterTiddlers("[!sortsub:string]",anchorWidget).join(",")).toBe("filter regexp test,$:/TiddlerTwo,Tiddler Three,a fourth tiddler,$:/ShadowPlugin,has filter,hasList,TiddlerOne,one"); expect(wiki.filterTiddlers("[[TiddlerOne]] [[$:/TiddlerTwo]] [[Tiddler Three]] [[a fourth tiddler]] +[!sortsub:number]",anchorWidget).join(",")).toBe("$:/TiddlerTwo,Tiddler Three,TiddlerOne,a fourth tiddler"); }); - } });