diff --git a/core/modules/filters.js b/core/modules/filters.js index 7c6cc3768..7799b23d6 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -137,7 +137,7 @@ exports.parseFilter = function(filterString) { p = 0, // Current position in the filter string match; var whitespaceRegExp = /(\s+)/mg, - operandRegExp = /((?:\+|\-|~|=|\:(\w+))?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; + operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; while(p < filterString.length) { // Skip any whitespace whitespaceRegExp.lastIndex = p; @@ -162,15 +162,27 @@ exports.parseFilter = function(filterString) { if(match[2]) { operation.namedPrefix = match[2]; } + if(match[3]) { + operation.suffixes = []; + $tw.utils.each(match[3].split(":"),function(subsuffix) { + operation.suffixes.push([]); + $tw.utils.each(subsuffix.split(","),function(entry) { + entry = $tw.utils.trim(entry); + if(entry) { + operation.suffixes[operation.suffixes.length -1].push(entry); + } + }); + }); + } } - if(match[3]) { // Opening square bracket + if(match[4]) { // Opening square bracket p = parseFilterOperation(operation.operators,filterString,p); } else { p = match.index + match[0].length; } - if(match[4] || match[5] || match[6]) { // Double quoted string, single quoted string or unquoted title + if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title operation.operators.push( - {operator: "title", operands: [{text: match[4] || match[5] || match[6]}]} + {operator: "title", operands: [{text: match[5] || match[6] || match[7]}]} ); } results.push(operation); @@ -280,7 +292,7 @@ exports.compileFilter = function(filterString) { var filterRunPrefixes = self.getFilterRunPrefixes(); // Wrap the operator functions in a wrapper function that depends on the prefix operationFunctions.push((function() { - var options = {wiki: self}; + var options = {wiki: self, suffixes: operation.suffixes || []}; switch(operation.prefix || "") { case "": // No prefix means that the operation is unioned into the result return filterRunPrefixes["or"](operationSubFunction, options); diff --git a/editions/test/tiddlers/tests/test-prefixes-filter.js b/editions/test/tiddlers/tests/test-prefixes-filter.js index 62f51263f..c089734b5 100644 --- a/editions/test/tiddlers/tests/test-prefixes-filter.js +++ b/editions/test/tiddlers/tests/test-prefixes-filter.js @@ -18,6 +18,199 @@ describe("general filter prefix tests", function() { var results = wiki.filterTiddlers("[tag[A]] :nonexistent[tag[B]]"); expect(results).toEqual(["Filter Error: Unknown prefix for filter run"]); }); + + // Test filter run prefix parsing + it("should parse filter run prefix suffixes", function() { + + // two runs, one with a named prefix but no suffix + expect($tw.wiki.parseFilter("[[Sparkling water]tags[]] :intersection[[Red wine]tags[]]")).toEqual( + [ + { + "prefix": "", + "operators": [ + { + "operator": "title", + "operands": [ + { + "text": "Sparkling water" + } + ] + }, + { + "operator": "tags", + "operands": [ + { + "text": "" + } + ] + } + ] + }, + { + "prefix": ":intersection", + "operators": [ + { + "operator": "title", + "operands": [ + { + "text": "Red wine" + } + ] + }, + { + "operator": "tags", + "operands": [ + { + "text": "" + } + ] + } + ], + "namedPrefix": "intersection" + } + ] + ); + + // named prefix with no suffix + expect($tw.wiki.parseFilter(":reduce[multiply]")).toEqual( + [ + { + "prefix": ":reduce", + "operators": [ + { + "operator": "multiply", + "operands": [ + { + "variable": true, + "text": "accumulator" + } + ] + } + ], + "namedPrefix": "reduce" + } + ] + ); + + //named prefix with one simple suffix + expect($tw.wiki.parseFilter(":reduce:1[multiply]")).toEqual( + [ + { + "prefix": ":reduce:1", + "operators": [ + { + "operator": "multiply", + "operands": [ + { + "variable": true, + "text": "accumulator" + } + ] + } + ], + "namedPrefix": "reduce", + "suffixes": [ + [ + "1" + ] + ] + } + ] + ); + + //named prefix with two simple suffixes + expect($tw.wiki.parseFilter(":reduce:1:hello[multiply]")).toEqual( + [ + { + "prefix": ":reduce:1:hello", + "operators": [ + { + "operator": "multiply", + "operands": [ + { + "variable": true, + "text": "accumulator" + } + ] + } + ], + "namedPrefix": "reduce", + "suffixes": [ + [ + "1" + ], + [ + "hello", + ] + ] + } + ] + ); + + //named prefix with two rich (comma separated) suffixes + expect($tw.wiki.parseFilter(":reduce:1,one:hello,there[multiply]")).toEqual( + [ + { + "prefix": ":reduce:1,one:hello,there", + "operators": [ + { + "operator": "multiply", + "operands": [ + { + "variable": true, + "text": "accumulator" + } + ] + } + ], + "namedPrefix": "reduce", + "suffixes": [ + [ + "1", + "one" + ], + [ + "hello", + "there" + ] + ] + } + ] + ); + + // suffixes with spaces + expect($tw.wiki.parseFilter(":reduce: 1, one:hello, there [multiply]")).toEqual( + [ + { + "prefix": ":reduce: 1, one:hello, there ", + "operators": [ + { + "operator": "multiply", + "operands": [ + { + "variable": true, + "text": "accumulator" + } + ] + } + ], + "namedPrefix": "reduce", + "suffixes": [ + [ + "1", + "one" + ], + [ + "hello", + "there" + ] + ] + } + ] + ); + + }); + }); describe("'reduce' and 'intersection' filter prefix tests", function() { @@ -80,6 +273,10 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { // Empty input should become empty output expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add]").length).toBe(0); expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add] :else[[0]]").join(",")).toBe("0"); + + expect(wiki.filterTiddlers("[tag[non-existent]] :reduce:11,22[get[price]multiply{!!quantity}add] :else[[0]]").join(",")).toBe("0"); + + expect(wiki.filterTiddlers("[tag[non-existent]] :reduce:11[get[price]multiply{!!quantity}add] :else[[0]]").join(",")).toBe("0"); }); it("should handle the reduce operator", function() {