From 8ef520ef37801986a03757f4f53c17e0ccf4c263 Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Fri, 10 Jan 2014 10:32:49 +0100 Subject: [PATCH 1/7] Created regexp search for milestone 5.1 --- core/modules/filters.js | 26 ++++++++++++++++++++------ core/modules/filters/field.js | 8 +++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index 592cb7b97..386a60df6 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -52,12 +52,25 @@ function parseFilterOperation(operators,filterString,p) { operator.operator = "title"; } // Get the operand - bracketPos = filterString.indexOf(operator.indirect ? "}" : "]",p); - if(bracketPos === -1) { - throw "Missing closing bracket in filter expression"; + if(!operator.indirect && filterString.charAt(p) === "/") { + // regexp + var rex = /^\/((?:[^\\\/]*|\\.))*\/([igm]*)\]/g; + var rexMatch = rex.exec(filterString.substring(p)); + if(!rexMatch) { + throw "Incomplete regular expression in filter expression"; + } + operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); + operator.operand = filterString.substr(p,rex.lastIndex-1); + p += rex.lastIndex; + } + else { + bracketPos = filterString.indexOf(operator.indirect ? "}" : "]",p); + if(bracketPos === -1) { + throw "Missing closing bracket in filter expression"; + } + operator.operand = filterString.substring(p,bracketPos); + p = bracketPos + 1; } - operator.operand = filterString.substring(p,bracketPos); - p = bracketPos + 1; // Push this operator operators.push(operator); } while(filterString.charAt(p) !== "]"); @@ -161,7 +174,8 @@ exports.compileFilter = function(filterString) { results = operatorFunction(accumulator,{ operator: operator.operator, operand: operand, - prefix: operator.prefix + prefix: operator.prefix, + regexp: operator.regexp },{ wiki: self, currTiddlerTitle: currTiddlerTitle diff --git a/core/modules/filters/field.js b/core/modules/filters/field.js index 8fff9114b..7657f8441 100644 --- a/core/modules/filters/field.js +++ b/core/modules/filters/field.js @@ -21,7 +21,13 @@ exports.field = function(source,operator,options) { function checkTiddler(title) { var tiddler = options.wiki.getTiddler(title); if(tiddler) { - var match = tiddler.getFieldString(operator.operator) === operator.operand; + var match; + if(operator.regexp) { + match = !! operator.regexp.exec(tiddler.getFieldString(operator.operator)); + } + else { + match = tiddler.getFieldString(operator.operator) === operator.operand; + } if(operator.prefix === "!") { match = !match; } From a3384d101e7242ad648370d1e9cdeb005f833e0d Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Fri, 10 Jan 2014 13:23:26 +0100 Subject: [PATCH 2/7] Extended the filter documentation --- .../tiddlers/concepts/TiddlerFilters.tid | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid index 1df0da137..942e6f8b1 100644 --- a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid +++ b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid @@ -1,9 +1,3 @@ -created: 20130827080000000 -modified: 20140107114229585 -tags: concepts -title: TiddlerFilters -type: text/vnd.tiddlywiki - Filters are used in TiddlyWiki to choose tiddlers by specifying simple match criteria. ! Examples @@ -44,7 +38,7 @@ A filter string consists of one or more runs of filter operators that each look * ''prefix'': tests whether a tiddlers title starts with the prefix specified in the operand * ''limit'': limits the number of subresults to the integer specified in the operand * ''tag'': tests whether a given tag is (`[tag[mytag]]`) or is not (`[!tag[mytag]]`) present on the tiddler -* ''{field}'': tests whether a tiddler field has a specified value (`[modifier[Jeremy]]`) or not (`[!modifier[Jeremy]]`) +* ''{field}:'': tests whether a tiddler field has a specified value (`[modifier:[Jeremy]]`) or not (`[!modifier:[Jeremy]]`) * ''tags'': selects the tags on the currently selected tiddlers * ''tagging'': selects the tiddlers tagged with the currently selected tiddlers * ''untagged'': selects the any of the selected tiddlers that do not have at least one tag @@ -69,7 +63,7 @@ The operands available with the `is` operator are: * ''tiddler'': selects all tiddlers excluding shadows, whether or not they are SystemTiddlers * ''system'': selects all SystemTiddlers * ''shadow'': selects all ShadowTiddlers -* ''current'': selects the CurrentTiddler +* ''current'': selects the current ContextTiddler * ''missing'': selects all MissingTiddlers * ''orphan'': selects all OrphanTiddlers @@ -79,6 +73,16 @@ If a filter operator is written with curly brackets around the operand then it i ''[search{$:/temp/search}]'': selects all tiddlers containing the string contained in the tiddler titled ''$:/temp/search''. +! Regular Expression Filters + +The field-filter also accepts regular expressions in the form `/regexp/modifier`. Please refer to you favourite JavaScript documentation to learn more about regular expressions and modifiers. + +In the easiest form, regular expressions allow you do do a search on substrings for every field: + +* `title:[/example/]`: searches for all tiddlers having "example" in its title. +* `title:[/example$/]`: `$`is an "anchor" for the end of the text. So "example" has to be the end of the title. +* `text:[/jeremy|ruston/i]`: Searches for tiddlers containing Jeremy's first or last name, ignoring the case. + ! Runs Operators are combined into runs that function as logically ANDed expressions by bashing them together and merging the square brackets: From 9444ef095f2b22be7dea71215a8253fbc2aa3fc1 Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Sun, 12 Jan 2014 23:37:11 +0100 Subject: [PATCH 3/7] I feel ashamed :( Somehow this slipped me :( Sorry! --- core/modules/filters/field.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/modules/filters/field.js b/core/modules/filters/field.js index 7657f8441..efbcd50df 100644 --- a/core/modules/filters/field.js +++ b/core/modules/filters/field.js @@ -21,12 +21,13 @@ exports.field = function(source,operator,options) { function checkTiddler(title) { var tiddler = options.wiki.getTiddler(title); if(tiddler) { - var match; + var match, + text = tiddler.getFieldString(operator.operator.replace(/:$/, "")); if(operator.regexp) { - match = !! operator.regexp.exec(tiddler.getFieldString(operator.operator)); + match = !! operator.regexp.exec(text); } else { - match = tiddler.getFieldString(operator.operator) === operator.operand; + match = text === operator.operand; } if(operator.prefix === "!") { match = !match; From a5d75db8d268e1d45ee0d998a407fc9f47d3bb00 Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Mon, 13 Jan 2014 08:44:53 +0100 Subject: [PATCH 4/7] Thinking about your mail led me to this new push -> Mail follows --- core/modules/filters.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index 386a60df6..3aeebcaaa 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -51,19 +51,24 @@ function parseFilterOperation(operators,filterString,p) { if(operator.operator === "") { operator.operator = "title"; } - // Get the operand + var rexMatch; + // regexp? if(!operator.indirect && filterString.charAt(p) === "/") { - // regexp var rex = /^\/((?:[^\\\/]*|\\.))*\/([igm]*)\]/g; - var rexMatch = rex.exec(filterString.substring(p)); - if(!rexMatch) { - throw "Incomplete regular expression in filter expression"; + rexMatch = rex.exec(filterString.substring(p)); + if(rexMatch) { + try { + operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); + } + catch(e) { + // an error in the regexp -> Will do string matching + } + operator.operand = filterString.substr(p,rex.lastIndex-1); + p += rex.lastIndex; } - operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); - operator.operand = filterString.substr(p,rex.lastIndex-1); - p += rex.lastIndex; } - else { + // Get the operand + if(!rexMatch) { bracketPos = filterString.indexOf(operator.indirect ? "}" : "]",p); if(bracketPos === -1) { throw "Missing closing bracket in filter expression"; From 0338c3661017bb64f3b75f68444f976a210ad75b Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Tue, 14 Jan 2014 16:19:34 +0100 Subject: [PATCH 5/7] implemented the field: syntax --- core/modules/filters.js | 6 ++++++ core/modules/filters/field.js | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index 3aeebcaaa..2b7f87fa4 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -48,6 +48,11 @@ function parseFilterOperation(operators,filterString,p) { operator.operator = filterString.substring(p,bracketPos); p = bracketPos + 1; } + var colon = operator.operator.indexOf(':'); + if(colon > -1) { + operator.field = operator.operator.substring(colon+1); + operator.operator = operator.operator.substring(0,colon); + } if(operator.operator === "") { operator.operator = "title"; } @@ -180,6 +185,7 @@ exports.compileFilter = function(filterString) { operator: operator.operator, operand: operand, prefix: operator.prefix, + field: operator.field, regexp: operator.regexp },{ wiki: self, diff --git a/core/modules/filters/field.js b/core/modules/filters/field.js index efbcd50df..b3b2d2787 100644 --- a/core/modules/filters/field.js +++ b/core/modules/filters/field.js @@ -22,7 +22,7 @@ exports.field = function(source,operator,options) { var tiddler = options.wiki.getTiddler(title); if(tiddler) { var match, - text = tiddler.getFieldString(operator.operator.replace(/:$/, "")); + text = tiddler.getFieldString(operator.field); if(operator.regexp) { match = !! operator.regexp.exec(text); } @@ -38,6 +38,10 @@ exports.field = function(source,operator,options) { } } // Iterate through the source tiddlers + if(!operator.field) { + operator.field = operator.operator; + } + operator.field.toLowerCase(); if($tw.utils.isArray(source)) { $tw.utils.each(source,function(title) { checkTiddler(title); From 81de74342d47e1b1a51490205f0f0926abb628c2 Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Tue, 14 Jan 2014 22:07:20 +0100 Subject: [PATCH 6/7] implemented the new regexp syntax --- .../tiddlers/concepts/TiddlerFilters.tid | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid index 942e6f8b1..1df0da137 100644 --- a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid +++ b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid @@ -1,3 +1,9 @@ +created: 20130827080000000 +modified: 20140107114229585 +tags: concepts +title: TiddlerFilters +type: text/vnd.tiddlywiki + Filters are used in TiddlyWiki to choose tiddlers by specifying simple match criteria. ! Examples @@ -38,7 +44,7 @@ A filter string consists of one or more runs of filter operators that each look * ''prefix'': tests whether a tiddlers title starts with the prefix specified in the operand * ''limit'': limits the number of subresults to the integer specified in the operand * ''tag'': tests whether a given tag is (`[tag[mytag]]`) or is not (`[!tag[mytag]]`) present on the tiddler -* ''{field}:'': tests whether a tiddler field has a specified value (`[modifier:[Jeremy]]`) or not (`[!modifier:[Jeremy]]`) +* ''{field}'': tests whether a tiddler field has a specified value (`[modifier[Jeremy]]`) or not (`[!modifier[Jeremy]]`) * ''tags'': selects the tags on the currently selected tiddlers * ''tagging'': selects the tiddlers tagged with the currently selected tiddlers * ''untagged'': selects the any of the selected tiddlers that do not have at least one tag @@ -63,7 +69,7 @@ The operands available with the `is` operator are: * ''tiddler'': selects all tiddlers excluding shadows, whether or not they are SystemTiddlers * ''system'': selects all SystemTiddlers * ''shadow'': selects all ShadowTiddlers -* ''current'': selects the current ContextTiddler +* ''current'': selects the CurrentTiddler * ''missing'': selects all MissingTiddlers * ''orphan'': selects all OrphanTiddlers @@ -73,16 +79,6 @@ If a filter operator is written with curly brackets around the operand then it i ''[search{$:/temp/search}]'': selects all tiddlers containing the string contained in the tiddler titled ''$:/temp/search''. -! Regular Expression Filters - -The field-filter also accepts regular expressions in the form `/regexp/modifier`. Please refer to you favourite JavaScript documentation to learn more about regular expressions and modifiers. - -In the easiest form, regular expressions allow you do do a search on substrings for every field: - -* `title:[/example/]`: searches for all tiddlers having "example" in its title. -* `title:[/example$/]`: `$`is an "anchor" for the end of the text. So "example" has to be the end of the title. -* `text:[/jeremy|ruston/i]`: Searches for tiddlers containing Jeremy's first or last name, ignoring the case. - ! Runs Operators are combined into runs that function as logically ANDed expressions by bashing them together and merging the square brackets: From 14ca91a94941a7653fb1e544afa61147f5bbd04c Mon Sep 17 00:00:00 2001 From: Stephan Hradek Date: Tue, 14 Jan 2014 22:08:05 +0100 Subject: [PATCH 7/7] implemented the new regexp syntax --- core/modules/filters.js | 74 ++++++++++--------- .../tiddlers/concepts/TiddlerFilters.tid | 13 +++- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index 2b7f87fa4..831ae5780 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -32,55 +32,57 @@ function parseFilterOperation(operators,filterString,p) { if(filterString.charAt(p) === "!") { operator.prefix = filterString.charAt(p++); } + // Get the operator name - bracketPos = filterString.indexOf("[",p); - curlyBracketPos = filterString.indexOf("{",p); - if((bracketPos === -1) && (curlyBracketPos === -1)) { + var nextBracketPos = filterString.substring(p).search(/[\[\{\/]/); + if(nextBracketPos === -1) { throw "Missing [ in filter expression"; } - if(bracketPos === -1 || (curlyBracketPos !== -1 && curlyBracketPos < bracketPos)) { - // Curly brackets - operator.indirect = true; - operator.operator = filterString.substring(p,curlyBracketPos); - p = curlyBracketPos + 1; - } else { - // Square brackets - operator.operator = filterString.substring(p,bracketPos); - p = bracketPos + 1; - } + nextBracketPos += p; + var bracket = filterString.charAt(nextBracketPos); + operator.operator = filterString.substring(p,nextBracketPos); + + // Any suffix? var colon = operator.operator.indexOf(':'); if(colon > -1) { operator.field = operator.operator.substring(colon+1); - operator.operator = operator.operator.substring(0,colon); + operator.operator = operator.operator.substring(0,colon) || "field"; } - if(operator.operator === "") { + // Empty operator means: title + else if(operator.operator === "") { operator.operator = "title"; } - var rexMatch; - // regexp? - if(!operator.indirect && filterString.charAt(p) === "/") { - var rex = /^\/((?:[^\\\/]*|\\.))*\/([igm]*)\]/g; - rexMatch = rex.exec(filterString.substring(p)); + + 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 '/': // regexp brackets + var rex = /^((?:[^\\\/]*|\\.))*\/(?:\(([mygi]+)\))?/g, + rexMatch = rex.exec(filterString.substring(p)); if(rexMatch) { - try { - operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); - } - catch(e) { - // an error in the regexp -> Will do string matching - } - operator.operand = filterString.substr(p,rex.lastIndex-1); - p += rex.lastIndex; + operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); + nextBracketPos = p + rex.lastIndex - 1; } - } - // Get the operand - if(!rexMatch) { - bracketPos = filterString.indexOf(operator.indirect ? "}" : "]",p); - if(bracketPos === -1) { - throw "Missing closing bracket in filter expression"; + else { + throw "Unterminated regular expression in filter expression"; } - operator.operand = filterString.substring(p,bracketPos); - p = bracketPos + 1; + break; } + + 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) !== "]"); diff --git a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid index 1df0da137..6813c5c08 100644 --- a/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid +++ b/editions/tw5.com/tiddlers/concepts/TiddlerFilters.tid @@ -44,7 +44,8 @@ A filter string consists of one or more runs of filter operators that each look * ''prefix'': tests whether a tiddlers title starts with the prefix specified in the operand * ''limit'': limits the number of subresults to the integer specified in the operand * ''tag'': tests whether a given tag is (`[tag[mytag]]`) or is not (`[!tag[mytag]]`) present on the tiddler -* ''{field}'': tests whether a tiddler field has a specified value (`[modifier[Jeremy]]`) or not (`[!modifier[Jeremy]]`) +* ''field:{field}'': or +* ''{field}'': tests whether a tiddler field has a specified value (`[modifier[Jeremy]]` or `[field:modifier[Jeremy]]`) or not (`[!modifier[Jeremy]]`) * ''tags'': selects the tags on the currently selected tiddlers * ''tagging'': selects the tiddlers tagged with the currently selected tiddlers * ''untagged'': selects the any of the selected tiddlers that do not have at least one tag @@ -79,6 +80,16 @@ If a filter operator is written with curly brackets around the operand then it i ''[search{$:/temp/search}]'': selects all tiddlers containing the string contained in the tiddler titled ''$:/temp/search''. +! Regular Expression Filters + +The field-filter also accepts regular expressions in the form `/regexp/(modifier)`. Please refer to you favourite JavaScript documentation to learn more about regular expressions and modifiers. + +In the easiest form, regular expressions allow you do do a search on substrings for every field: + +* `field:title/example/`: searches for all tiddlers having "example" in its title. +* `field:title:/example$/`: `$` is an "anchor" for the end of the text. So "example" has to be the end of the title. +* `field:text/jeremy|ruston/(i)`: Searches for tiddlers containing Jeremy's first or last name, ignoring the case. + ! Runs Operators are combined into runs that function as logically ANDed expressions by bashing them together and merging the square brackets: