From f4fff7a33037ba9dd537379bcb44a52a280868d6 Mon Sep 17 00:00:00 2001 From: Jermolene Date: Tue, 9 Sep 2014 15:57:41 +0100 Subject: [PATCH] Add new "regexp" filter operator Fixes #762 --- core/modules/filters/regexp.js | 68 +++++++++++++++++++ editions/test/tiddlers/tests/test-filters.js | 8 +++ .../dev/TiddlerFilter Formal Grammar.tid | 12 +--- .../filters/FilterOperator regexp.tid | 21 ++++++ .../filters/Introduction to Filters.tid | 10 +-- 5 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 core/modules/filters/regexp.js create mode 100644 editions/tw5.com/tiddlers/filters/FilterOperator regexp.tid diff --git a/core/modules/filters/regexp.js b/core/modules/filters/regexp.js new file mode 100644 index 000000000..d6ab27303 --- /dev/null +++ b/core/modules/filters/regexp.js @@ -0,0 +1,68 @@ +/*\ +title: $:/core/modules/filters/regexp.js +type: application/javascript +module-type: filteroperator + +Filter operator for regexp matching + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.regexp = function(source,operator,options) { + var results = [], + fieldname = (operator.suffix || "title").toLowerCase(), + regexpString, regexp, flags = "", match, + getFieldString = function(tiddler,title) { + if(tiddler) { + return tiddler.getFieldString(fieldname); + } else if(fieldname === "title") { + return title; + } else { + return null; + } + }; + // Process flags and construct regexp + regexpString = operator.operand; + match = /^\(\?([gim]+)\)/.exec(regexpString); + if(match) { + flags = match[1]; + regexpString = regexpString.substr(match[0].length); + } else { + match = /\(\?([gim]+)\)$/.exec(regexpString); + if(match) { + flags = match[1]; + regexpString = regexpString.substr(0,regexpString.length - match[0].length); + } + } + regexp = new RegExp(regexpString,flags); + // Process the incoming tiddlers + if(operator.prefix === "!") { + source(function(tiddler,title) { + var text = getFieldString(tiddler,title); + if(text !== null) { + if(!regexp.exec(text)) { + results.push(title); + } + } + }); + } else { + source(function(tiddler,title) { + var text = getFieldString(tiddler,title); + if(text !== null) { + if(!!regexp.exec(text)) { + results.push(title); + } + } + }); + } + return results; +}; + +})(); diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js index a523fe5fe..7946b79b0 100644 --- a/editions/test/tiddlers/tests/test-filters.js +++ b/editions/test/tiddlers/tests/test-filters.js @@ -97,6 +97,14 @@ describe("Filter tests", function() { expect(wiki.filterTiddlers("[!is[system]!field:modifier[JoeBloggs]]").join(",")).toBe("Tiddler Three,a fourth tiddler,one"); }); + it("should handle the regexp operator", function() { + expect(wiki.filterTiddlers("[regexp[id]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three,a fourth tiddler"); + expect(wiki.filterTiddlers("[!regexp[id]]").join(",")).toBe("one"); + expect(wiki.filterTiddlers("[regexp[Tid]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three"); + expect(wiki.filterTiddlers("[regexp[(?i)Tid]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three,a fourth tiddler"); + expect(wiki.filterTiddlers("[!regexp[Tid(?i)]]").join(",")).toBe("one"); + }); + it("should handle the field operator with a regular expression operand", function() { expect(wiki.filterTiddlers("[modifier/JoeBloggs/]").join(",")).toBe("TiddlerOne"); expect(wiki.filterTiddlers("[modifier/Jo/]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three,a fourth tiddler,one"); diff --git a/editions/tw5.com/tiddlers/dev/TiddlerFilter Formal Grammar.tid b/editions/tw5.com/tiddlers/dev/TiddlerFilter Formal Grammar.tid index 8f29fc39d..1673a52f9 100644 --- a/editions/tw5.com/tiddlers/dev/TiddlerFilter Formal Grammar.tid +++ b/editions/tw5.com/tiddlers/dev/TiddlerFilter Formal Grammar.tid @@ -1,5 +1,5 @@ created: 20140210141217955 -modified: 20140725091312363 +modified: 20140909134102102 tags: dev title: TiddlerFilter Formal Grammar type: text/vnd.tiddlywiki @@ -34,15 +34,7 @@ Whitespace is matched with javascript "\s+", which matches space, tab, carriage ''<opt-operator-suffix>'' ::= ''<string>'' | "" -''<operand>'' ::= "[" ''<search-string>'' "]" | "{" ''<indirect-search-string>'' "}" | "<" ''<variable-name-string>'' ">" | ''<regex>'' - -''<regex>'' ::= "/" ''<string>'' "/" ''<opt-regex-args>'' - -''<opt-regex-args>'' ::= "(" ''<regex-args>'' ")" | "" - -''<regex-args>'' ::= ''<regex-arg>'' | ''<regex-arg>'' ''<regex-args>'' - -''<regex-arg>'' ::= "m" | "y" | "g" | "i" +''<operand>'' ::= "[" ''<search-string>'' "]" | "{" ''<indirect-search-string>'' "}" | "<" ''<variable-name-string>'' ">" ''<string>'' ::= ''<string-type-1>'' | ''<string-type-2>'' | ... diff --git a/editions/tw5.com/tiddlers/filters/FilterOperator regexp.tid b/editions/tw5.com/tiddlers/filters/FilterOperator regexp.tid new file mode 100644 index 000000000..611fbba40 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/FilterOperator regexp.tid @@ -0,0 +1,21 @@ +created: 20140909134102102 +modified: 20140909134102102 +tags: filters +title: FilterOperator: regexp +type: text/vnd.tiddlywiki + +The ''regexp'' filter operator filters tiddlers that match a regular expression. + +Regular expressions are a way of encoding complex string matching criteria. The format is defined fully in [[this Mozilla reference|https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions]]. + +The regexp filter operator takes a fieldname as the optional suffix (defaulting to `title`). + +The regular expression itself can start or end with an optional string to specify flags in the format `(?flags)` where flags can be "g", "m" or "i". Only the "i" flag is generally useful: it forces the match to be case-insensitive (ie upper and lower case letters are considered the same). + +For example: + +|!Filter String |!Description | +|`[all[tiddlers]regexp[EggCup]]` |Selects all tiddlers that have the CamelCase string "EggCup" in their title | +|`[all[tiddlers]regexp[eggcup(?i)]]` |Selects all tiddlers that have the string "eggcup" in their title (ignoring case) | +|`[all[tiddlers]regexp:modified[eggcup(^2014)]]` |Selects all tiddlers that have the string "eggcup" in their title (ignoring case) | + diff --git a/editions/tw5.com/tiddlers/filters/Introduction to Filters.tid b/editions/tw5.com/tiddlers/filters/Introduction to Filters.tid index 7f69f571f..ecae50f8b 100644 --- a/editions/tw5.com/tiddlers/filters/Introduction to Filters.tid +++ b/editions/tw5.com/tiddlers/filters/Introduction to Filters.tid @@ -1,5 +1,5 @@ created: 20140410101941871 -modified: 20140725103229640 +modified: 20140909134102102 tags: introduction title: Introduction to Filters type: text/vnd.tiddlywiki @@ -77,14 +77,6 @@ If a filter operator is written with angle brackets around the operand then it i (Note that the `currentTiddler` variable is used to track the current tiddler). -! Regular Expression Operands - -The "field" filter also accepts [[regular expressions|http://en.wikipedia.org/wiki/Regular_expression]] with the syntax `/regexp/(modifier)`. For example: - -* `[field:title/example/]`: searches for all tiddlers having "example" in their title -* `[field:title/example$/]`: `$` is an "anchor" for the end of the text so that "example" has to be at the end of the title -* `[field:text/summer|winter/(i)]`: Searches for tiddlers containing "summer" or "winter", ignoring case - ! ORing Multiple Filter Operators You can use multiple filter operations at once. This example selects all tiddlers that are either tagged "introduction" or "demo":