2012-05-05 12:15:44 +00:00
|
|
|
/*\
|
2012-06-06 11:17:08 +00:00
|
|
|
title: $:/core/modules/filters.js
|
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-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) {
|
2013-05-27 16:57:37 +00:00
|
|
|
var operator, operand, bracketPos, curlyBracketPos;
|
2012-05-08 14:11:53 +00:00
|
|
|
// Skip the starting square bracket
|
2013-02-05 19:12:05 +00:00
|
|
|
if(filterString.charAt(p++) !== "[") {
|
2012-05-08 14:11:53 +00:00
|
|
|
throw "Missing [ in filter expression";
|
|
|
|
}
|
|
|
|
// Process each operator in turn
|
|
|
|
do {
|
|
|
|
operator = {};
|
|
|
|
// Check for an operator prefix
|
2013-02-05 19:12:05 +00:00
|
|
|
if(filterString.charAt(p) === "!") {
|
|
|
|
operator.prefix = filterString.charAt(p++);
|
2012-05-08 14:11:53 +00:00
|
|
|
}
|
|
|
|
// Get the operator name
|
|
|
|
bracketPos = filterString.indexOf("[",p);
|
2013-05-27 16:57:37 +00:00
|
|
|
curlyBracketPos = filterString.indexOf("{",p);
|
|
|
|
if((bracketPos === -1) && (curlyBracketPos === -1)) {
|
2012-05-08 14:11:53 +00:00
|
|
|
throw "Missing [ in filter expression";
|
|
|
|
}
|
2013-05-27 16:57:37 +00:00
|
|
|
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;
|
|
|
|
}
|
2012-05-08 14:11:53 +00:00
|
|
|
if(operator.operator === "") {
|
|
|
|
operator.operator = "title";
|
|
|
|
}
|
|
|
|
// Get the operand
|
2013-05-27 16:57:37 +00:00
|
|
|
bracketPos = filterString.indexOf(operator.indirect ? "}" : "]",p);
|
2012-05-08 14:11:53 +00:00
|
|
|
if(bracketPos === -1) {
|
2013-05-27 16:57:37 +00:00
|
|
|
throw "Missing closing bracket in filter expression";
|
2012-05-08 14:11:53 +00:00
|
|
|
}
|
|
|
|
operator.operand = filterString.substring(p,bracketPos);
|
|
|
|
p = bracketPos + 1;
|
|
|
|
// Push this operator
|
|
|
|
operators.push(operator);
|
2013-02-05 19:12:05 +00:00
|
|
|
} while(filterString.charAt(p) !== "]");
|
2012-05-08 14:11:53 +00:00
|
|
|
// Skip the ending square bracket
|
2013-02-05 19:12:05 +00:00
|
|
|
if(filterString.charAt(p++) !== "]") {
|
2012-05-08 14:11:53 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2013-05-25 16:26:22 +00:00
|
|
|
exports.getFilterOperators = function() {
|
|
|
|
if(!this.filterOperators) {
|
|
|
|
$tw.Wiki.prototype.filterOperators = {};
|
|
|
|
$tw.modules.applyMethods("filteroperator",this.filterOperators);
|
|
|
|
}
|
|
|
|
return this.filterOperators;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.filterTiddlers = function(filterString,currTiddlerTitle,tiddlerList) {
|
|
|
|
var fn = this.compileFilter(filterString);
|
|
|
|
return fn.call(this,tiddlerList || this.tiddlers,currTiddlerTitle);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.compileFilter = function(filterString) {
|
|
|
|
var filterParseTree = this.parseFilter(filterString);
|
|
|
|
// Get the hashmap of filter operator functions
|
|
|
|
var filterOperators = this.getFilterOperators();
|
|
|
|
// Assemble array of functions, one for each operation
|
|
|
|
var operationFunctions = [];
|
|
|
|
// Step through the operations
|
|
|
|
var self = this;
|
|
|
|
$tw.utils.each(filterParseTree,function(operation) {
|
|
|
|
// Create a function for the chain of operators in the operation
|
|
|
|
var operationSubFunction = function(source,currTiddlerTitle) {
|
|
|
|
var accumulator = source,
|
|
|
|
results = [];
|
|
|
|
$tw.utils.each(operation.operators,function(operator) {
|
|
|
|
var operatorFunction = filterOperators[operator.operator] || filterOperators.field || function(source,operator,operations) {
|
2013-05-27 16:57:37 +00:00
|
|
|
return ["Filter Error: unknown operator '" + operator.operator + "'"];
|
|
|
|
},
|
|
|
|
operand = operator.operand;
|
|
|
|
if(operator.indirect) {
|
|
|
|
operand = self.getTextReference(operator.operand,"",currTiddlerTitle);
|
|
|
|
}
|
|
|
|
results = operatorFunction(accumulator,{
|
|
|
|
operator: operator.operator,
|
|
|
|
operand: operand,
|
|
|
|
prefix: operator.prefix
|
|
|
|
},{
|
|
|
|
wiki: self,
|
|
|
|
currTiddlerTitle: currTiddlerTitle
|
|
|
|
});
|
2013-05-25 16:26:22 +00:00
|
|
|
accumulator = results;
|
|
|
|
});
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
// 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,currTiddlerTitle) {
|
|
|
|
$tw.utils.pushTop(results,operationSubFunction(source,currTiddlerTitle));
|
|
|
|
};
|
|
|
|
case "-": // The results of this operation are removed from the main result
|
|
|
|
return function(results,source,currTiddlerTitle) {
|
|
|
|
$tw.utils.removeArrayEntries(results,operationSubFunction(source,currTiddlerTitle));
|
|
|
|
};
|
|
|
|
case "+": // This operation is applied to the main results so far
|
|
|
|
return function(results,source,currTiddlerTitle) {
|
|
|
|
// This replaces all the elements of the array, but keeps the actual array so that references to it are preserved
|
|
|
|
source = results.slice(0);
|
|
|
|
results.splice(0,results.length);
|
|
|
|
$tw.utils.pushTop(results,operationSubFunction(source,currTiddlerTitle));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
})());
|
|
|
|
});
|
|
|
|
// Return a function that applies the operations to a source array/hashmap of tiddler titles
|
|
|
|
return function(source,currTiddlerTitle) {
|
|
|
|
var results = [];
|
|
|
|
$tw.utils.each(operationFunctions,function(operationFunction) {
|
|
|
|
operationFunction(results,source,currTiddlerTitle);
|
|
|
|
});
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2012-05-05 12:15:44 +00:00
|
|
|
})();
|