diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids index 14e353689..bc1091910 100644 --- a/core/language/en-GB/Misc.multids +++ b/core/language/en-GB/Misc.multids @@ -26,6 +26,7 @@ Error/Caption: Error Error/EditConflict: File changed on server Error/Filter: Filter error Error/FilterSyntax: Syntax error in filter expression +Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operator Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator Error/LoadingPluginLibrary: Error loading plugin library diff --git a/core/modules/filterrunprefixes/all.js b/core/modules/filterrunprefixes/all.js new file mode 100644 index 000000000..2e25bbc4d --- /dev/null +++ b/core/modules/filterrunprefixes/all.js @@ -0,0 +1,25 @@ +/*\ +title: $:/core/modules/filterrunprefixes/all.js +type: application/javascript +module-type: filterrunprefix + +Union of sets without de-duplication. +Equivalent to = filter run prefix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.all = function(operationSubFunction) { + return function(results,source,widget) { + Array.prototype.push.apply(results,operationSubFunction(source,widget)); + }; +}; + +})(); diff --git a/core/modules/filterrunprefixes/and.js b/core/modules/filterrunprefixes/and.js new file mode 100644 index 000000000..29ccf1619 --- /dev/null +++ b/core/modules/filterrunprefixes/and.js @@ -0,0 +1,28 @@ +/*\ +title: $:/core/modules/filterrunprefixes/and.js +type: application/javascript +module-type: filterrunprefix + +Intersection of sets. +Equivalent to + filter run prefix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.and = function(operationSubFunction) { + return function(results,source,widget) { + // This replaces all the elements of the array, but keeps the actual array so that references to it are preserved + source = $tw.wiki.makeTiddlerIterator(results); + results.splice(0,results.length); + $tw.utils.pushTop(results,operationSubFunction(source,widget)); + }; +}; + +})(); diff --git a/core/modules/filterrunprefixes/else.js b/core/modules/filterrunprefixes/else.js new file mode 100644 index 000000000..c39b9e8fe --- /dev/null +++ b/core/modules/filterrunprefixes/else.js @@ -0,0 +1,27 @@ +/*\ +title: $:/core/modules/filterrunprefixes/else.js +type: application/javascript +module-type: filterrunprefix + +Equivalent to ~ filter run prefix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.else = function(operationSubFunction) { + return function(results,source,widget) { + if(results.length === 0) { + // Main result so far is empty + $tw.utils.pushTop(results,operationSubFunction(source,widget)); + } + }; +}; + +})(); diff --git a/core/modules/filterrunprefixes/except.js b/core/modules/filterrunprefixes/except.js new file mode 100644 index 000000000..18d649627 --- /dev/null +++ b/core/modules/filterrunprefixes/except.js @@ -0,0 +1,25 @@ +/*\ +title: $:/core/modules/filterrunprefixes/except.js +type: application/javascript +module-type: filterrunprefix + +Difference of sets. +Equivalent to - filter run prefix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.except = function(operationSubFunction) { + return function(results,source,widget) { + $tw.utils.removeArrayEntries(results,operationSubFunction(source,widget)); + }; +}; + +})(); diff --git a/core/modules/filterrunprefixes/or.js b/core/modules/filterrunprefixes/or.js new file mode 100644 index 000000000..5192f490b --- /dev/null +++ b/core/modules/filterrunprefixes/or.js @@ -0,0 +1,24 @@ +/*\ +title: $:/core/modules/filterrunprefixes/or.js +type: application/javascript +module-type: filterrunprefix + +Equivalent to a filter run with no prefix. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.or = function(operationSubFunction) { + return function(results,source,widget) { + $tw.utils.pushTop(results,operationSubFunction(source,widget)); + }; +}; + +})(); diff --git a/core/modules/filters.js b/core/modules/filters.js index b00082bd1..6784efae1 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -119,7 +119,7 @@ exports.parseFilter = function(filterString) { p = 0, // Current position in the filter string match; var whitespaceRegExp = /(\s+)/mg, - operandRegExp = /((?:\+|\-|~|=)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; + operandRegExp = /((?:\+|\-|~|=|\:(\w+))?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; while(p < filterString.length) { // Skip any whitespace whitespaceRegExp.lastIndex = p; @@ -140,16 +140,19 @@ exports.parseFilter = function(filterString) { }; if(match[1]) { operation.prefix = match[1]; - p++; + p = p + operation.prefix.length; + if(match[2]) { + operation.namedPrefix = match[2]; + } } - if(match[2]) { // Opening square bracket + if(match[3]) { // Opening square bracket p = parseFilterOperation(operation.operators,filterString,p); } else { p = match.index + match[0].length; } - if(match[3] || match[4] || match[5]) { // Double quoted string, single quoted string or unquoted title + if(match[4] || match[5] || match[6]) { // Double quoted string, single quoted string or unquoted title operation.operators.push( - {operator: "title", operand: match[3] || match[4] || match[5]} + {operator: "title", operand: match[4] || match[5] || match[6]} ); } results.push(operation); @@ -166,6 +169,14 @@ exports.getFilterOperators = function() { return this.filterOperators; }; +exports.getFilterRunPrefixes = function() { + if(!this.filterPrefixes) { + $tw.Wiki.prototype.filterRunPrefixes = {}; + $tw.modules.applyMethods("filterrunprefix",this.filterRunPrefixes); + } + return this.filterRunPrefixes; +} + exports.filterTiddlers = function(filterString,widget,source) { var fn = this.compileFilter(filterString); return fn.call(this,source,widget); @@ -241,35 +252,29 @@ exports.compileFilter = function(filterString) { return resultArray; } }; + var filterRunPrefixes = self.getFilterRunPrefixes(); // Wrap the operator functions in a wrapper function that depends on the prefix operationFunctions.push((function() { switch(operation.prefix || "") { case "": // No prefix means that the operation is unioned into the result - return function(results,source,widget) { - $tw.utils.pushTop(results,operationSubFunction(source,widget)); - }; + return filterRunPrefixes["or"](operationSubFunction); case "=": // The results of the operation are pushed into the result without deduplication - return function(results,source,widget) { - Array.prototype.push.apply(results,operationSubFunction(source,widget)); - }; + return filterRunPrefixes["all"](operationSubFunction); case "-": // The results of this operation are removed from the main result - return function(results,source,widget) { - $tw.utils.removeArrayEntries(results,operationSubFunction(source,widget)); - }; + return filterRunPrefixes["except"](operationSubFunction); case "+": // This operation is applied to the main results so far - return function(results,source,widget) { - // This replaces all the elements of the array, but keeps the actual array so that references to it are preserved - source = self.makeTiddlerIterator(results); - results.splice(0,results.length); - $tw.utils.pushTop(results,operationSubFunction(source,widget)); - }; + return filterRunPrefixes["and"](operationSubFunction); case "~": // This operation is unioned into the result only if the main result so far is empty - return function(results,source,widget) { - if(results.length === 0) { - // Main result so far is empty - $tw.utils.pushTop(results,operationSubFunction(source,widget)); - } - }; + return filterRunPrefixes["else"](operationSubFunction); + default: + if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) { + return filterRunPrefixes[operation.namedPrefix](operationSubFunction); + } else { + return function(results,source,widget) { + results.splice(0,results.length); + results.push($tw.language.getString("Error/FilterRunPrefix")); + }; + } } })()); });