2012-05-05 12:15:44 +00:00
/ * \
2012-06-06 11:17:08 +00:00
title : $ : / c o r e / m o d u l e s / f i l t e r s . j s
2012-05-05 12:15:44 +00:00
type : application / javascript
module - type : wikimethod
2012-05-08 14:11:53 +00:00
Adds tiddler filtering to the $tw . Wiki object .
2012-05-05 12:15:44 +00:00
\ * /
( function ( ) {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict" ;
2012-09-12 14:32:26 +00:00
exports . filterTiddlers = function ( filterString , currTiddlerTitle , tiddlerList ) {
2012-05-08 14:11:53 +00:00
var fn = this . compileFilter ( filterString ) ;
2012-09-12 14:32:26 +00:00
return fn . call ( this , tiddlerList || this . tiddlers , currTiddlerTitle ) ;
2012-05-08 14:11:53 +00:00
} ;
/ *
2012-05-08 15:02:24 +00:00
Compiling a filter gives a JavaScript function that is invoked as ` this.filter(source) ` , where ` source ` is a hashmap of source tiddler titles ( the values don ' t matter , so it is possible to use a store or a changes object ) . It returns an array of tiddler titles that satisfy the filter
2012-05-08 14:11:53 +00:00
* /
exports . compileFilter = function ( filterString ) {
var filter = this . parseFilter ( filterString ) ,
output = [ ] ,
t , operation , operationInfo , type , p , operator , operatorInfo , fn ;
output . push ( this . filterFragments . prologue ) ;
for ( t = 0 ; t < filter . length ; t ++ ) {
operation = filter [ t ] ;
operationInfo = this . filterFragments . operation . prefix [ operation . prefix || "" ] ;
output . push ( operationInfo . prologue ) ;
type = "selector" ;
if ( operation . prefix === "+" ) {
type = "filter" ;
}
for ( p = 0 ; p < operation . operators . length ; p ++ ) {
operator = operation . operators [ p ] ;
operatorInfo = this . operators [ operator . operator ] ;
if ( ! operatorInfo ) { // Check for it being a field operator
2012-05-19 14:13:17 +00:00
operatorInfo = this . operators . field ;
2012-05-08 14:11:53 +00:00
}
output . push ( operatorInfo [ type ] ( operator ) ) ;
type = "filter" ;
}
output . push ( operationInfo . epilogue ) ;
}
output . push ( this . filterFragments . epilogue ) ;
try {
/*jslint evil: true */
2012-05-08 15:02:24 +00:00
fn = eval ( output . join ( "\n" ) ) ;
2012-05-08 14:11:53 +00:00
} catch ( ex ) {
throw "Error in filter expression: " + ex ;
}
return fn ;
} ;
exports . filterFragments = {
2012-06-19 07:55:56 +00:00
prologue : "(function(source,currTiddlerTitle) {\nvar results = [], subResults, subResultsTemp, title, r, t;" ,
2012-05-08 14:11:53 +00:00
epilogue : "return results;\n})" ,
operation : {
prefix : {
"" : {
2012-05-08 15:02:24 +00:00
prologue : "subResults = [];" ,
epilogue : "$tw.utils.pushTop(results,subResults);"
2012-05-08 14:11:53 +00:00
} ,
"+" : {
2012-05-08 15:02:24 +00:00
prologue : "subResults = results.slice(0);\nresults.splice(0,results.length);" ,
epilogue : "$tw.utils.pushTop(results,subResults);"
2012-05-08 14:11:53 +00:00
} ,
"-" : {
2012-05-08 15:02:24 +00:00
prologue : "subResults = [];" ,
epilogue : "$tw.utils.removeArrayEntries(results,subResults);"
2012-05-08 14:11:53 +00:00
}
}
}
} ;
exports . operators = {
2012-10-16 10:25:14 +00:00
"title" : { // Filter by title
2012-05-08 14:11:53 +00:00
selector : function ( operator ) {
2012-06-19 09:39:44 +00:00
return "$tw.utils.pushTop(subResults,\"" + $tw . utils . stringify ( operator . operand ) + "\");" ;
2012-05-08 14:11:53 +00:00
} ,
filter : function ( operator ) {
2012-05-08 15:02:24 +00:00
return "if(subResults.indexOf(\"" + $tw . utils . stringify ( operator . operand ) + "\") !== -1) {subResults = [\"" + $tw . utils . stringify ( operator . operand ) + "\"];} else {subResults = [];}" ;
2012-05-05 12:15:44 +00:00
}
} ,
2012-10-16 10:25:14 +00:00
"prefix" : { // Filter by title prefix
2012-05-29 21:01:07 +00:00
selector : function ( operator ) {
2012-06-06 11:13:31 +00:00
var op = operator . prefix === "!" ? "!" : "=" ;
2012-06-19 07:55:56 +00:00
return "for(title in source) {if(title.substr(0," + operator . operand . length + ")" + op + "==\"" + $tw . utils . stringify ( operator . operand ) + "\") {$tw.utils.pushTop(subResults,title);}}" ;
2012-05-29 21:01:07 +00:00
} ,
filter : function ( operator ) {
2012-06-06 11:13:31 +00:00
var op = operator . prefix === "!" ? "=" : "!" ;
2012-06-19 07:55:56 +00:00
return "for(r=subResults.length-1; r>=0; r--) {if(title.substr(0," + operator . operand . length + ")" + op + "==\"" + $tw . utils . stringify ( operator . operand ) + "\") {subResults.splice(r,1);}}" ;
2012-05-29 21:01:07 +00:00
}
} ,
2012-10-16 10:25:14 +00:00
"is" : { // Filter by status
2012-05-08 14:11:53 +00:00
selector : function ( operator ) {
2012-06-06 12:21:20 +00:00
var op = operator . prefix === "!" ? "!" : "" ;
2012-06-19 07:55:56 +00:00
switch ( operator . operand ) {
case "current" :
if ( operator . prefix === "!" ) {
return "for(title in source) {if(title !== currTiddlerTitle) {$tw.utils.pushTop(subResults,title);}}" ;
} else {
2012-06-19 09:39:44 +00:00
return "$tw.utils.pushTop(subResults,currTiddlerTitle);" ;
2012-06-19 07:55:56 +00:00
}
break ;
case "shadow" :
return "for(title in source) {if(" + op + "this.getTiddler(title).isShadow) {$tw.utils.pushTop(subResults,title);}}" ;
default :
throw "Unknown operand for 'is' filter operator" ;
2012-05-08 14:11:53 +00:00
}
} ,
filter : function ( operator ) {
2012-06-06 12:21:20 +00:00
var op = operator . prefix === "!" ? "" : "!" ;
2012-06-19 07:55:56 +00:00
switch ( operator . operand ) {
case "current" :
if ( operator . prefix === "!" ) {
return "for(r=subResults.length-1; r>=0; r--) {if(subResults[r] === currTiddlerTitle) {subResults.splice(r,1);}}" ;
} else {
return "r = subResults.indexOf(currTiddlerTitle);\nif(r !== -1) {subResults = [currTiddlerTitle];} else {subResults = [];}" ;
}
break ;
case "shadow" :
return "for(r=subResults.length-1; r>=0; r--) {if(" + op + "this.getTiddler(subResults[r]).isShadow) {subResults.splice(r,1);}}" ;
default :
throw "Unknown operand for 'is' filter operator" ;
2012-05-08 14:11:53 +00:00
}
}
2012-05-05 12:15:44 +00:00
} ,
2012-10-16 10:25:14 +00:00
"tag" : { // Filter by tag
2012-05-08 14:11:53 +00:00
selector : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "" ;
2012-06-19 07:55:56 +00:00
return "for(title in source) {if(" + op + "this.getTiddler(title).hasTag(\"" + $tw . utils . stringify ( operator . operand ) + "\")) {$tw.utils.pushTop(subResults,title);}}" ;
2012-05-08 14:11:53 +00:00
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "" : "!" ;
2012-06-19 07:55:56 +00:00
return "for(r=subResults.length-1; r>=0; r--) {if(" + op + "this.getTiddler(subResults[r]).hasTag(\"" + $tw . utils . stringify ( operator . operand ) + "\")) {subResults.splice(r,1);}}" ;
}
} ,
2012-10-16 10:25:14 +00:00
"tags" : { // Return all tags used on selected tiddlers
2012-06-19 07:55:56 +00:00
selector : function ( operator ) {
return "for(title in source) {r = this.getTiddler(title); if(r && r.fields.tags) {$tw.utils.pushTop(subResults,r.fields.tags);}}" ;
} ,
filter : function ( operator ) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {r = this.getTiddler(subResultsTemp[t]); if(r && r.fields.tags) {$tw.utils.pushTop(subResults,r.fields.tags);}}" ;
2012-05-08 14:11:53 +00:00
}
2012-05-05 12:15:44 +00:00
} ,
2012-10-16 10:25:14 +00:00
"tagging" : { // Return all tiddlers tagged with any of the selected tags
2012-06-19 09:39:44 +00:00
selector : function ( operator ) {
return "for(title in source) {$tw.utils.pushTop(subResults,this.getTiddlersWithTag(title));}" ;
} ,
filter : function ( operator ) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {$tw.utils.pushTop(subResults,this.getTiddlersWithTag(subResultsTemp[t]));}" ;
}
} ,
2012-10-16 10:25:14 +00:00
"has" : { // Filter by presence of a particular field
2012-05-08 14:11:53 +00:00
selector : function ( operator ) {
var op = operator . prefix === "!" ? "=" : "!" ;
2012-06-19 07:55:56 +00:00
return "for(title in source) {if(this.getTiddler(title).fields[\"" + $tw . utils . stringify ( operator . operand ) + "\"] " + op + "== undefined) {$tw.utils.pushTop(subResults,title);}}" ;
2012-05-08 14:11:53 +00:00
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "=" ;
2012-06-19 07:55:56 +00:00
return "for(r=subResults.length-1; r>=0; r--) {if(this.getTiddler(subResults[r]).fields[\"" + $tw . utils . stringify ( operator . operand ) + "\"] " + op + "== undefined) {subResults.splice(r,1);}}" ;
2012-05-08 14:11:53 +00:00
}
2012-05-05 12:15:44 +00:00
} ,
2012-10-16 10:25:14 +00:00
"sort" : { // Sort selected tiddlers
2012-05-08 15:02:24 +00:00
selector : function ( operator ) {
2012-05-08 16:44:32 +00:00
throw "Cannot use sort operator at the start of a filter operation" ;
2012-05-08 15:02:24 +00:00
} ,
filter : function ( operator ) {
var desc = operator . prefix === "!" ? "true" : "false" ;
2012-05-08 16:42:49 +00:00
return "this.sortTiddlers(subResults,\"" + $tw . utils . stringify ( operator . operand ) + "\"," + desc + ");" ;
2012-05-08 15:02:24 +00:00
}
2012-10-16 10:25:14 +00:00
} , // Case insensitive sort of selected tiddlers
2012-06-14 10:35:54 +00:00
"sort-case-sensitive" : {
selector : function ( operator ) {
throw "Cannot use sort operator at the start of a filter operation" ;
} ,
filter : function ( operator ) {
var desc = operator . prefix === "!" ? "true" : "false" ;
return "this.sortTiddlers(subResults,\"" + $tw . utils . stringify ( operator . operand ) + "\"," + desc + ",true);" ;
}
} ,
2012-10-16 10:25:14 +00:00
"limit" : { // Limit number of members of selection
2012-05-09 08:40:10 +00:00
selector : function ( operator ) {
throw "Cannot use limit operator at the start of a filter operation" ;
} ,
filter : function ( operator ) {
var limit = parseInt ( operator . operand , 10 ) ,
base = operator . prefix === "!" ? 0 : limit ;
return "if(subResults.length > " + limit + ") {subResults.splice(" + base + ",subResults.length-" + limit + ");}" ;
}
} ,
2012-10-16 10:25:14 +00:00
"list" : { // Select all tiddlers that are listed (or not listed) in the specified tiddler
selector : function ( operator ) {
if ( operator . prefix === "!" ) {
2012-10-25 13:58:32 +00:00
return "var list = this.getTiddlerList(\"" + $tw . utils . stringify ( operator . operand ) + "\");" +
2012-10-16 10:25:14 +00:00
"for(title in source) {if(list.indexOf(title) === -1) {$tw.utils.pushTop(subResults,title);}}" ;
} else {
2012-10-25 13:58:32 +00:00
return "$tw.utils.pushTop(subResults,this.getTiddlerList(\"" + $tw . utils . stringify ( operator . operand ) + "\"));" ;
2012-10-16 10:25:14 +00:00
}
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "!==" : "===" ;
2012-10-25 13:58:32 +00:00
return "var list = this.getTiddlerList(\"" + $tw . utils . stringify ( operator . operand ) + "\");" +
2012-10-16 10:25:14 +00:00
"for(r=subResults.length-1; r>=0; r--) {if(list.indexOf(title) " + op + " -1) {subResults.splice(r,1);}}" ;
}
} ,
2012-10-17 13:34:34 +00:00
"searchVia" : { // Search for the string in the operand tiddler
selector : function ( operator ) {
var op = operator . prefix === "!" ? "true" : "false" ;
return "var term = this.getTiddler(\"" + $tw . utils . stringify ( operator . operand ) + "\").fields.text;" +
2012-10-17 13:57:13 +00:00
"$tw.utils.pushTop(subResults,this.search(term,{titles: source, invert: " + op + ", exclude: [\"" + $tw . utils . stringify ( operator . operand ) + "\"]}));" ;
2012-10-17 13:34:34 +00:00
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "true" : "false" ;
return "var term = this.getTiddler(\"" + $tw . utils . stringify ( operator . operand ) + "\").fields.text;" +
2012-11-06 17:21:56 +00:00
"subResults = this.search(term,{titles: subResults, invert: " + op + ", exclude: [\"" + $tw . utils . stringify ( operator . operand ) + "\"]});" ;
2012-10-17 13:34:34 +00:00
}
} ,
2012-10-16 10:25:14 +00:00
"field" : { // Special handler for field comparisons
2012-05-08 14:11:53 +00:00
selector : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "=" ;
2012-06-19 07:55:56 +00:00
return "for(title in source) {if(this.getTiddler(title).fields[\"" + $tw . utils . stringify ( operator . operator ) + "\"] " + op + "== \"" + operator . operand + "\") {$tw.utils.pushTop(subResults,title);}}" ;
2012-05-08 14:11:53 +00:00
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "=" : "!" ;
2012-06-19 07:55:56 +00:00
return "for(r=subResults.length-1; r>=0; r--) {if(this.getTiddler(subResults[r]).fields[\"" + $tw . utils . stringify ( operator . operator ) + "\"] " + op + "== \"" + operator . operand + "\") {subResults.splice(r,1);}}" ;
2012-05-05 12:15:44 +00:00
}
}
} ;
2012-05-08 14:11:53 +00:00
/ *
Parses an operation within a filter string
results : Array of array of operator nodes into which results should be inserted
filterString : filter string
p : start position within the string
Returns the new start position , after the parsed operation
* /
function parseFilterOperation ( operators , filterString , p ) {
var operator , operand , bracketPos ;
// Skip the starting square bracket
if ( filterString [ p ++ ] !== "[" ) {
throw "Missing [ in filter expression" ;
}
// Process each operator in turn
do {
operator = { } ;
// Check for an operator prefix
if ( filterString [ p ] === "!" ) {
operator . prefix = filterString [ p ++ ] ;
}
// Get the operator name
bracketPos = filterString . indexOf ( "[" , p ) ;
if ( bracketPos === - 1 ) {
throw "Missing [ in filter expression" ;
}
operator . operator = filterString . substring ( p , bracketPos ) ;
if ( operator . operator === "" ) {
operator . operator = "title" ;
}
p = bracketPos + 1 ;
// Get the operand
bracketPos = filterString . indexOf ( "]" , p ) ;
if ( bracketPos === - 1 ) {
throw "Missing ] in filter expression" ;
}
operator . operand = filterString . substring ( p , bracketPos ) ;
p = bracketPos + 1 ;
// Push this operator
operators . push ( operator ) ;
} while ( filterString [ p ] !== "]" ) ;
// Skip the ending square bracket
if ( filterString [ p ++ ] !== "]" ) {
throw "Missing ] in filter expression" ;
}
// Return the parsing position
return p ;
}
/ *
Parse a filter string
* /
exports . parseFilter = function ( filterString ) {
filterString = filterString || "" ;
var results = [ ] , // Array of arrays of operator nodes {operator:,operand:}
p = 0 , // Current position in the filter string
match ;
var whitespaceRegExp = /(\s+)/mg ,
operandRegExp = /((?:\+|\-)?)(?:(\[)|("(?:[^"])*")|('(?:[^'])*')|([^\s\[\]]+))/mg ;
while ( p < filterString . length ) {
// Skip any whitespace
whitespaceRegExp . lastIndex = p ;
match = whitespaceRegExp . exec ( filterString ) ;
if ( match && match . index === p ) {
p = p + match [ 0 ] . length ;
}
// Match the start of the operation
if ( p < filterString . length ) {
operandRegExp . lastIndex = p ;
match = operandRegExp . exec ( filterString ) ;
if ( ! match || match . index !== p ) {
throw "Syntax error in filter expression" ;
}
var operation = {
prefix : "" ,
operators : [ ]
} ;
if ( match [ 1 ] ) {
operation . prefix = match [ 1 ] ;
p ++ ;
}
if ( match [ 2 ] ) { // 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
operation . operators . push (
{ operator : "title" , operand : match [ 3 ] || match [ 4 ] || match [ 5 ] }
) ;
}
results . push ( operation ) ;
2012-05-05 12:15:44 +00:00
}
}
return results ;
} ;
} ) ( ) ;