2012-05-05 12:15:44 +00:00
/ * \
title : $ : / c o r e / m o d u l e s / w i k i . f i l t e r s . j s
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-05-08 14:11:53 +00:00
exports . filterTiddlers = function ( filterString ) {
var fn = this . compileFilter ( filterString ) ;
return fn . call ( this , this . tiddlers ) ;
} ;
/ *
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-05-08 15:02:24 +00:00
prologue : "(function(source) {\nvar results = [], subResults;" ,
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 = {
"title" : {
selector : function ( operator ) {
2012-05-08 15:02:24 +00:00
return "if($tw.utils.hop(source,\"" + $tw . utils . stringify ( operator . operand ) + "\")) {$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-05-08 14:11:53 +00:00
"is" : {
selector : function ( operator ) {
2012-05-19 14:13:17 +00:00
if ( operator . operand === "tiddler" ) {
if ( operator . prefix === "!" ) {
return "subResults = [];" ;
} else {
return "for(var title in source) {$tw.utils.pushTop(subResults,title);}" ;
}
} else {
throw "Unknown operand for 'is' filter operator" ;
2012-05-08 14:11:53 +00:00
}
} ,
filter : function ( operator ) {
2012-05-19 14:13:17 +00:00
if ( operator . operand === "tiddler" ) {
if ( operator . prefix === "!" ) {
return "subResults = [];" ;
} else {
return "" ;
}
} else {
throw "Unknown operand for 'is' filter operator" ;
2012-05-08 14:11:53 +00:00
}
}
2012-05-05 12:15:44 +00:00
} ,
2012-05-08 14:11:53 +00:00
"tag" : {
selector : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "" ;
return "for(var title in source) {if(" + op + "this.getTiddler(title).hasTag(\"" + $tw . utils . stringify ( operator . operand ) + "\")) {$tw.utils.pushTop(subResults,title);}}" ;
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "" : "!" ;
return "for(var r=subResults.length-1; r>=0; r--) {if(" + op + "this.getTiddler(subResults[r]).hasTag(\"" + $tw . utils . stringify ( operator . operand ) + "\")) {subResults.splice(r,1);}}" ;
}
2012-05-05 12:15:44 +00:00
} ,
2012-05-08 14:11:53 +00:00
"has" : {
selector : function ( operator ) {
var op = operator . prefix === "!" ? "=" : "!" ;
return "for(var title in source) {if(this.getTiddler(title).fields[\"" + $tw . utils . stringify ( operator . operand ) + "\"] " + op + "== undefined) {$tw.utils.pushTop(subResults,title);}}" ;
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "=" ;
return "for(var 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-05 12:15:44 +00:00
} ,
2012-05-08 15:02:24 +00:00
"sort" : {
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-05-09 08:40:10 +00:00
"limit" : {
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-05-08 14:11:53 +00:00
"field" : {
selector : function ( operator ) {
var op = operator . prefix === "!" ? "!" : "=" ;
return "for(var title in source) {if(this.getTiddler(title).fields[\"" + $tw . utils . stringify ( operator . operator ) + "\"] " + op + "== \"" + operator . operand + "\") {$tw.utils.pushTop(subResults,title);}}" ;
} ,
filter : function ( operator ) {
var op = operator . prefix === "!" ? "=" : "!" ;
return "for(var 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 ;
} ;
} ) ( ) ;