mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-26 17:06:51 +00:00
Added properly compiled tiddler filters
This commit is contained in:
parent
d92dfa3d81
commit
8b0703b694
@ -11,17 +11,19 @@ module-type: macro
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "^",
|
||||
name: "include",
|
||||
params: {
|
||||
filter: {byPos: 0, type: "filter"},
|
||||
as: {byPos: 1, as: "text"}
|
||||
as: {byPos: 1, as: "text"},
|
||||
shadow: {byPos: 2, as: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var as = this.params.as || "text/plain";
|
||||
var as = this.params.as || "text/plain",
|
||||
wiki = this.hasParameter("shadow") ? this.wiki.shadows : this.wiki;
|
||||
if(this.hasParameter("filter")) {
|
||||
var titles = this.wiki.filterTiddlers(this.params.filter),
|
||||
var titles = wiki.filterTiddlers(this.params.filter),
|
||||
result = [];
|
||||
for(var t=0; t<titles.length; t++) {
|
||||
result.push(this.wiki.serializeTiddler(titles[t],as));
|
||||
|
@ -3,7 +3,58 @@ title: $:/core/modules/wiki.filters.js
|
||||
type: application/javascript
|
||||
module-type: wikimethod
|
||||
|
||||
Filter method for the $tw.Wiki object
|
||||
Adds tiddler filtering to the $tw.Wiki object.
|
||||
|
||||
TiddlyWiki has a special syntax for expressing filters. They can be used to select tiddlers for an operation, or to filter a set of tiddlers to add or remove members.
|
||||
|
||||
The mechanism is easiest to understand by first presenting some example filter strings:
|
||||
|
||||
|!Filter |!Results |
|
||||
|`HelloThere` |The single tiddler titled `HelloThere` (if it exists) |
|
||||
|`[[A Title With Several Words]]` |The single tiddler titled `A Title With Several Words` (if it exists) |
|
||||
|`[title[MyTiddler]]` |The single tiddler titled `MyTiddler` (if it exists) |
|
||||
|`HelloThere Introduction` |The tiddlers titled `HelloThere` and `Introduction` (if they exist) |
|
||||
|`[tag[important]]` |Any tiddlers with the tag `important` |
|
||||
|`[!tag[important]]` |Any tiddlers not with the tag `important` |
|
||||
|`[tag[important]sort[title]]` |Any tiddlers with the tag `important` sorted by title |
|
||||
|`[tag[important]!sort[title]]` |Any tiddlers with the tag `important` reverse sorted by title |
|
||||
|`[[one][two][three]tag[tom]]` |Any of the tiddlers called `one`, `two` or `three` that exist and are tagged with `tom` |
|
||||
|`[[one][two][three]] [tag[tom]]` |Any of the tiddlers called `one`, `two` or `three` that exist, along with all of the source tiddlers that are tagged with `tom` |
|
||||
|`[tag[tom]] [tag[harry]] -[[one][two][three]]` |All tiddlers tagged either `tom` or `harry`, but excluding `one`, `two` and `three` |
|
||||
|
||||
[[one]] [[two]] [tag[three]] -[[four]] +[sort[title]]
|
||||
[tag[important]] -[[one][two]] -[[three]] +[sort[-modified]limit[20]]
|
||||
|
||||
A filter string consists of one or more filter operations, each comprising one or more filter operators with associated operands.
|
||||
|
||||
The operators look like `[operator[operand]]`, where `operator` is one of:
|
||||
* ''title'': selects the tiddler with the title given in the operand
|
||||
* ''is'': tests whether a tiddler is a member of the system defined set named in the operand (see below)
|
||||
* ''has'': tests whether a tiddler has a specified field
|
||||
* ''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]]`)
|
||||
|
||||
An operator can be negated with by preceding it with `!`, for example `[!tag[Tommy]]` selects the tiddlers that are not tagged with `Tommy`.
|
||||
|
||||
The operator defaults to `title` if omitted, so `[[HelloThere]]` is equivalent to `[title[HelloThere]]`. If there are no spaces in the title, then the double square brackets can also be omitted: `HelloThere`.
|
||||
|
||||
The operands available with the `is` operator are:
|
||||
* ''tiddler'': selects all ordinary (non-shadow) tiddlers
|
||||
|
||||
Operators are combined into logically ANDed expressions by bashing them together and merging the square brackets:
|
||||
{{{
|
||||
[tag[one]] [tag[two]] ---> [tag[one]tag[two]]
|
||||
}}}
|
||||
|
||||
Operations can be preceded with `-` to negate their action, removing the selected tiddlers from the results. For example, `[tag[Tommy]] -HelloThere -[[Another One]]` selects all tiddlers tagged with `Tommy` except those titled `HelloThere` or `Another One`.
|
||||
|
||||
Operations can be preceded with `+` in order to make them apply to all of the current results, rather than the original source. For example, `[tag[Jeremy]] [tag[Tommy]] +[sort[title]]` selects the tiddlers tagged `Tommy` or `Jeremy`, and sorts them by the `title` field.
|
||||
|
||||
Filters are processed with the following elements:
|
||||
* a string of filter operations, each made up of one or more filter operators
|
||||
* the incoming source tiddlers
|
||||
* the overall result stack
|
||||
* the subresult stack of the tiddlers selected by the current operation
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
@ -12,77 +63,229 @@ Filter method for the $tw.Wiki object
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
//# Extensible filter functions
|
||||
exports.filters = {
|
||||
tiddler: function(results,match) {
|
||||
var title = match[1] || match[4];
|
||||
if(results.indexOf(title) === -1) {
|
||||
results.push(title);
|
||||
exports.filterTiddlers = function(filterString) {
|
||||
var fn = this.compileFilter(filterString);
|
||||
return fn.call(this,this.tiddlers);
|
||||
};
|
||||
|
||||
/*
|
||||
Compiling a filter gives a JavaScript function that is invoked as `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
|
||||
*/
|
||||
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";
|
||||
}
|
||||
},
|
||||
tag: function(results,match) {
|
||||
},
|
||||
sort: function(results,match) {
|
||||
},
|
||||
limit: function(results,match) {
|
||||
return results.slice(0,parseInt(match[3],10));
|
||||
},
|
||||
field: function(results,match) {
|
||||
},
|
||||
is: function(results,match) {
|
||||
switch(match[3]) {
|
||||
case "shadowStyle":
|
||||
this.shadows.forEachTiddler(function(title,tiddler) {
|
||||
if(tiddler.fields.type === "text/css") {
|
||||
if(results.indexOf(title) === -1) {
|
||||
results.push(title);
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "shadowModule":
|
||||
this.shadows.forEachTiddler(function(title,tiddler) {
|
||||
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"]) {
|
||||
if(results.indexOf(title) === -1) {
|
||||
results.push(title);
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "shadowPlain":
|
||||
this.shadows.forEachTiddler(function(title,tiddler) {
|
||||
if((tiddler.fields.type !== "application/javascript" || !tiddler.fields["module-type"]) &&
|
||||
tiddler.fields.type !== "text/css") {
|
||||
if(results.indexOf(title) === -1) {
|
||||
results.push(title);
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "tiddler":
|
||||
this.forEachTiddler(function(title,tiddler) {
|
||||
if(results.indexOf(title) === -1) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
break;
|
||||
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
|
||||
operatorInfo = this.operators["field"];
|
||||
}
|
||||
output.push(operatorInfo[type](operator));
|
||||
type = "filter";
|
||||
}
|
||||
output.push(operationInfo.epilogue);
|
||||
}
|
||||
output.push(this.filterFragments.epilogue);
|
||||
try {
|
||||
/*jslint evil: true */
|
||||
fn = eval(output.join(""));
|
||||
} catch(ex) {
|
||||
throw "Error in filter expression: " + ex;
|
||||
}
|
||||
return fn;
|
||||
};
|
||||
|
||||
exports.filterFragments = {
|
||||
prologue: "(function(source) {\nvar results = [], subResults;\n",
|
||||
epilogue: "return results;\n})",
|
||||
operation: {
|
||||
prefix: {
|
||||
"": {
|
||||
prologue: "subResults = [];\n",
|
||||
epilogue: "$tw.utils.pushTop(results,subResults);\n"
|
||||
},
|
||||
"+": {
|
||||
prologue: "subResults = results.slice(0);\nresults.splice(0,results.length);\n",
|
||||
epilogue: "$tw.utils.pushTop(results,subResults);\n"
|
||||
},
|
||||
"-": {
|
||||
prologue: "subResults = [];\n",
|
||||
epilogue: "$tw.utils.removeArrayEntries(results,subResults);\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return the tiddler titles from the store that match a filter expression
|
||||
// filter - filter expression (eg "tiddlertitle [[multi word tiddler title]] [tag[systemConfig]]")
|
||||
// Returns an array of tiddler titles that match the filter expression
|
||||
exports.filterTiddlers = function(filter) {
|
||||
// text or [foo[bar]] or [[tiddler title]]
|
||||
var re = /([^\s\[\]]+)|(?:\[([ \w\.\-]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
|
||||
var results = [];
|
||||
if(filter) {
|
||||
var match = re.exec(filter);
|
||||
while(match) {
|
||||
var handler = (match[1]||match[4]) ? 'tiddler' : (this.filters[match[2]] ? match[2] : 'field');
|
||||
this.filters[handler].call(this,results,match);
|
||||
match = re.exec(filter);
|
||||
exports.operators = {
|
||||
"title": {
|
||||
selector: function(operator) {
|
||||
return "if($tw.utils.hop(source,\"" + $tw.utils.stringify(operator.operand) + "\")) {$tw.utils.pushTop(subResults,\"" + $tw.utils.stringify(operator.operand) + "\");}\n";
|
||||
},
|
||||
filter: function(operator) {
|
||||
return "if(subResults.indexOf(\"" + $tw.utils.stringify(operator.operand) + "\") !== -1) {subResults = [\"" + $tw.utils.stringify(operator.operand) + "\"];} else {subResults = [];}\n";
|
||||
}
|
||||
},
|
||||
"is": {
|
||||
selector: function(operator) {
|
||||
switch(operator.operand) {
|
||||
case "tiddler":
|
||||
if(operator.prefix === "!") {
|
||||
return "subResults = [];";
|
||||
} else {
|
||||
return "for(var title in source) {$tw.utils.pushTop(subResults,title);}";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw "Unknown operand for 'is' filter operator";
|
||||
}
|
||||
},
|
||||
filter: function(operator) {
|
||||
switch(operator.operand) {
|
||||
case "tiddler":
|
||||
if(operator.prefix === "!") {
|
||||
return "subResults = [];";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw "Unknown operand for 'is' filter operator";
|
||||
}
|
||||
}
|
||||
},
|
||||
"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);}}";
|
||||
}
|
||||
},
|
||||
"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);}}";
|
||||
}
|
||||
},
|
||||
"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);}}";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
@ -6,21 +6,21 @@ type: text/x-tiddlywiki-html
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<meta name="application-name" content="TiddlyWiki" />
|
||||
<meta name="generator" content="TiddlyWiki" />
|
||||
<meta name="tiddlywiki-version" content="<<^"$:/core/version.txt" text/plain>>" />
|
||||
<meta name="tiddlywiki-version" content="<<include "$:/core/version.txt" text/plain shadow:yes>>" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="copyright" content="
|
||||
<<^"$:/core/copyright.txt" text/plain>>
|
||||
<<include "$:/core/copyright.txt" text/plain shadow:yes>>
|
||||
" />
|
||||
<title><<tiddler target:$:/shadows/title>></title>
|
||||
<!----------- This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ----------->
|
||||
<div id="styleArea">
|
||||
<<^"[is[shadowStyle]]" application/x-tiddler-css>>
|
||||
<<include "[type[text/css]]" application/x-tiddler-css shadow:yes>>
|
||||
</div>
|
||||
</head>
|
||||
<body>
|
||||
<<^"PageTemplate" text/html>>
|
||||
<<include "PageTemplate" text/html shadow:yes>>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,42 +6,42 @@ type: text/x-tiddlywiki-html
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<meta name="application-name" content="TiddlyWiki" />
|
||||
<meta name="generator" content="TiddlyWiki" />
|
||||
<meta name="tiddlywiki-version" content="<<^"$:/core/version.txt" text/plain>>" />
|
||||
<meta name="tiddlywiki-version" content="<<include "$:/core/version.txt" text/plain shadow:yes>>" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="copyright" content="
|
||||
<<^"$:/core/copyright.txt" text/plain>>
|
||||
<<include "$:/core/copyright.txt" text/plain shadow:yes>>
|
||||
" />
|
||||
<title><<tiddler target:$:/shadows/title>></title>
|
||||
<title><<include "$:/wiki/title" text/plain>></title>
|
||||
<!----------- This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ----------->
|
||||
<div id="styleArea">
|
||||
<<^"[is[shadowStyle]]" application/x-tiddler-css>>
|
||||
<<include "[type[text/css]]" application/x-tiddler-css shadow:yes>>
|
||||
</div>
|
||||
</head>
|
||||
<body>
|
||||
<!----------- Static content for Google and browsers without JavaScript ----------->
|
||||
<noscript>
|
||||
<div id="splashArea" style="display:none;">
|
||||
<<^"$:/wiki/splash" text/html>>
|
||||
<<include "$:/wiki/splash" text/html shadow:yes>>
|
||||
</div>
|
||||
</noscript>
|
||||
<!----------- Shadow tiddlers ----------->
|
||||
<div id="shadowArea" style="display:none;">
|
||||
<<^"[is[shadowPlain]]" application/x-tiddler-html-div>>
|
||||
<<include "[!type[text/css]] -[type[application/javascript]has[module-type]] -[[$:/core/boot.js]] -[[$:/core/bootprefix.js]]" application/x-tiddler-html-div shadow:yes>>
|
||||
</div>
|
||||
<!----------- Ordinary tiddlers ----------->
|
||||
<div id="storeArea" style="display:none;">
|
||||
<<^"[is[tiddler]]" application/x-tiddler-html-div>>
|
||||
<<include "[is[tiddler]]" application/x-tiddler-html-div>>
|
||||
</div>
|
||||
<!----------- Boot kernel prologue ----------->
|
||||
<<^"$:/core/bootprefix.js" application/javascript>>
|
||||
<<include "$:/core/bootprefix.js" application/javascript shadow:yes>>
|
||||
<!----------- Plugin modules ----------->
|
||||
<div id="pluginModules" style="display:none;">
|
||||
<<^"[is[shadowModule]]" application/x-tiddler-module>>
|
||||
<<include "[type[application/javascript]has[module-type]]" application/x-tiddler-module shadow:yes>>
|
||||
</div>
|
||||
<!----------- Boot kernel ----------->
|
||||
<<^"$:/core/boot.js" application/javascript>>
|
||||
<<include "$:/core/boot.js" application/javascript shadow:yes>>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user