mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-01-25 12:23:42 +00:00
Compare commits
16 Commits
element-ma
...
default-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f41ed1c88c | ||
|
|
73cd48cada | ||
|
|
3b84a7042c | ||
|
|
3f81e3651e | ||
|
|
c4d0b61827 | ||
|
|
adf9853ce4 | ||
|
|
5f03d1e6ac | ||
|
|
0ffb4d988b | ||
|
|
f2fbcf60a2 | ||
|
|
8276f66edf | ||
|
|
b6859c5a2d | ||
|
|
beb6839a35 | ||
|
|
f3e9beb1e1 | ||
|
|
2a1c607450 | ||
|
|
9b56734451 | ||
|
|
2f8d53d3f7 |
@@ -30,6 +30,7 @@ Error/DeserializeOperator/MissingOperand: Filter Error: Missing operand for 'des
|
||||
Error/DeserializeOperator/UnknownDeserializer: Filter Error: Unknown deserializer provided as operand for the 'deserialize' operator
|
||||
Error/Filter: Filter error
|
||||
Error/FilterSyntax: Syntax error in filter expression
|
||||
Error/FilterPragma: Filter Error: Unknown filter pragma
|
||||
Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run
|
||||
Error/IsFilterOperator: Filter Error: Unknown parameter for the 'is' filter operator
|
||||
Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator
|
||||
|
||||
@@ -146,14 +146,16 @@ exports.parseFilter = function(filterString) {
|
||||
match;
|
||||
var whitespaceRegExp = /(\s+)/mg,
|
||||
// Groups:
|
||||
// 1 - entire filter run prefix
|
||||
// 2 - filter run prefix itself
|
||||
// 3 - filter run prefix suffixes
|
||||
// 4 - opening square bracket following filter run prefix
|
||||
// 5 - double quoted string following filter run prefix
|
||||
// 6 - single quoted string following filter run prefix
|
||||
// 7 - anything except for whitespace and square brackets
|
||||
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
|
||||
// 1 - pragma
|
||||
// 2 - pragma suffix
|
||||
// 3 - entire filter run prefix
|
||||
// 4 - filter run prefix name
|
||||
// 5 - filter run prefix suffixes
|
||||
// 6 - opening square bracket following filter run prefix
|
||||
// 7 - double quoted string following filter run prefix
|
||||
// 8 - single quoted string following filter run prefix
|
||||
// 9 - anything except for whitespace and square brackets
|
||||
operandRegExp = /(?:::(\w+)(?:\:(\w+))?(?=\s|$)|((?:\+|\-|~|(?:=>?)|:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+)))/mg;
|
||||
while(p < filterString.length) {
|
||||
// Skip any whitespace
|
||||
whitespaceRegExp.lastIndex = p;
|
||||
@@ -170,18 +172,23 @@ exports.parseFilter = function(filterString) {
|
||||
};
|
||||
match = operandRegExp.exec(filterString);
|
||||
if(match && match.index === p) {
|
||||
// If there is a filter run prefix
|
||||
if(match[1]) {
|
||||
operation.prefix = match[1];
|
||||
// If there is a filter pragma
|
||||
operation.pragma = match[1];
|
||||
operation.suffix = match[2];
|
||||
p = match.index + match[0].length;
|
||||
} else if(match[3]) {
|
||||
// If there is a filter run prefix
|
||||
operation.prefix = match[3];
|
||||
p = p + operation.prefix.length;
|
||||
// Name for named prefixes
|
||||
if(match[2]) {
|
||||
operation.namedPrefix = match[2];
|
||||
if(match[4]) {
|
||||
operation.namedPrefix = match[4];
|
||||
}
|
||||
// Suffixes for filter run prefix
|
||||
if(match[3]) {
|
||||
if(match[5]) {
|
||||
operation.suffixes = [];
|
||||
$tw.utils.each(match[3].split(":"),function(subsuffix) {
|
||||
$tw.utils.each(match[5].split(":"),function(subsuffix) {
|
||||
operation.suffixes.push([]);
|
||||
$tw.utils.each(subsuffix.split(","),function(entry) {
|
||||
entry = $tw.utils.trim(entry);
|
||||
@@ -193,7 +200,7 @@ exports.parseFilter = function(filterString) {
|
||||
}
|
||||
}
|
||||
// Opening square bracket
|
||||
if(match[4]) {
|
||||
if(match[6]) {
|
||||
p = parseFilterOperation(operation.operators,filterString,p);
|
||||
} else {
|
||||
p = match.index + match[0].length;
|
||||
@@ -203,9 +210,9 @@ exports.parseFilter = function(filterString) {
|
||||
p = parseFilterOperation(operation.operators,filterString,p);
|
||||
}
|
||||
// Quoted strings and unquoted title
|
||||
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
|
||||
if(match[7] || match[8] || match[9]) { // Double quoted string, single quoted string or unquoted title
|
||||
operation.operators.push(
|
||||
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
|
||||
{operator: "title", operands: [{text: match[7] || match[8] || match[9]}]}
|
||||
);
|
||||
}
|
||||
results.push(operation);
|
||||
@@ -230,8 +237,8 @@ exports.getFilterRunPrefixes = function() {
|
||||
return this.filterRunPrefixes;
|
||||
}
|
||||
|
||||
exports.filterTiddlers = function(filterString,widget,source) {
|
||||
var fn = this.compileFilter(filterString);
|
||||
exports.filterTiddlers = function(filterString,widget,source,options) {
|
||||
var fn = this.compileFilter(filterString,options);
|
||||
try {
|
||||
const fnResult = fn.call(this,source,widget);
|
||||
return fnResult;
|
||||
@@ -241,17 +248,21 @@ exports.filterTiddlers = function(filterString,widget,source) {
|
||||
};
|
||||
|
||||
/*
|
||||
Compile a filter into a function with the signature fn(source,widget) where:
|
||||
Compile a filter into a function with the signature fn(source,widget,options) where:
|
||||
source: an iterator function for the source tiddlers, called source(iterator), where iterator is called as iterator(tiddler,title)
|
||||
widget: an optional widget node for retrieving the current tiddler etc.
|
||||
options: optional hashmap of options
|
||||
options.defaultFilterRunPrefix: the default filter run prefix to use when none is specified
|
||||
*/
|
||||
exports.compileFilter = function(filterString) {
|
||||
exports.compileFilter = function(filterString,options) {
|
||||
var defaultFilterRunPrefix = (options || {}).defaultFilterRunPrefix || "or";
|
||||
var cacheKey = filterString + "|" + defaultFilterRunPrefix;
|
||||
if(!this.filterCache) {
|
||||
this.filterCache = Object.create(null);
|
||||
this.filterCacheCount = 0;
|
||||
}
|
||||
if(this.filterCache[filterString] !== undefined) {
|
||||
return this.filterCache[filterString];
|
||||
if(this.filterCache[cacheKey] !== undefined) {
|
||||
return this.filterCache[cacheKey];
|
||||
}
|
||||
var filterParseTree;
|
||||
try {
|
||||
@@ -330,7 +341,8 @@ exports.compileFilter = function(filterString) {
|
||||
regexp: operator.regexp
|
||||
},{
|
||||
wiki: self,
|
||||
widget: widget
|
||||
widget: widget,
|
||||
defaultFilterRunPrefix: defaultFilterRunPrefix
|
||||
});
|
||||
if($tw.utils.isArray(results)) {
|
||||
accumulator = self.makeTiddlerIterator(results);
|
||||
@@ -351,29 +363,45 @@ exports.compileFilter = function(filterString) {
|
||||
var filterRunPrefixes = self.getFilterRunPrefixes();
|
||||
// Wrap the operator functions in a wrapper function that depends on the prefix
|
||||
operationFunctions.push((function() {
|
||||
var options = {wiki: self, suffixes: operation.suffixes || []};
|
||||
switch(operation.prefix || "") {
|
||||
case "": // No prefix means that the operation is unioned into the result
|
||||
return filterRunPrefixes["or"](operationSubFunction, options);
|
||||
case "=": // The results of the operation are pushed into the result without deduplication
|
||||
return filterRunPrefixes["all"](operationSubFunction, options);
|
||||
case "-": // The results of this operation are removed from the main result
|
||||
return filterRunPrefixes["except"](operationSubFunction, options);
|
||||
case "+": // This operation is applied to the main results so far
|
||||
return filterRunPrefixes["and"](operationSubFunction, options);
|
||||
case "~": // This operation is unioned into the result only if the main result so far is empty
|
||||
return filterRunPrefixes["else"](operationSubFunction, options);
|
||||
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
|
||||
return filterRunPrefixes["let"](operationSubFunction, options);
|
||||
default:
|
||||
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
|
||||
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
|
||||
} else {
|
||||
if(operation.pragma) {
|
||||
switch(operation.pragma) {
|
||||
case "defaultprefix":
|
||||
defaultFilterRunPrefix = operation.suffix || "or";
|
||||
break;
|
||||
default:
|
||||
return function(results,source,widget) {
|
||||
results.clear();
|
||||
results.push($tw.language.getString("Error/FilterRunPrefix"));
|
||||
results.push($tw.language.getString("Error/FilterPragma"));
|
||||
};
|
||||
}
|
||||
}
|
||||
return function(results,source,widget) {
|
||||
// Dummy response
|
||||
};
|
||||
} else {
|
||||
var options = {wiki: self, suffixes: operation.suffixes || []};
|
||||
switch(operation.prefix || "") {
|
||||
case "": // Use the default filter run prefix if none is specified
|
||||
return filterRunPrefixes[defaultFilterRunPrefix](operationSubFunction, options);
|
||||
case "=": // The results of the operation are pushed into the result without deduplication
|
||||
return filterRunPrefixes["all"](operationSubFunction, options);
|
||||
case "-": // The results of this operation are removed from the main result
|
||||
return filterRunPrefixes["except"](operationSubFunction, options);
|
||||
case "+": // This operation is applied to the main results so far
|
||||
return filterRunPrefixes["and"](operationSubFunction, options);
|
||||
case "~": // This operation is unioned into the result only if the main result so far is empty
|
||||
return filterRunPrefixes["else"](operationSubFunction, options);
|
||||
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
|
||||
return filterRunPrefixes["let"](operationSubFunction, options);
|
||||
default:
|
||||
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
|
||||
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
|
||||
} else {
|
||||
return function(results,source,widget) {
|
||||
results.clear();
|
||||
results.push($tw.language.getString("Error/FilterRunPrefix"));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
})());
|
||||
});
|
||||
@@ -412,7 +440,7 @@ exports.compileFilter = function(filterString) {
|
||||
this.filterCache = Object.create(null);
|
||||
this.filterCacheCount = 0;
|
||||
}
|
||||
this.filterCache[filterString] = fnMeasured;
|
||||
this.filterCache[cacheKey] = fnMeasured;
|
||||
this.filterCacheCount++;
|
||||
return fnMeasured;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@ Filter operator returning those input titles that pass a subfilter
|
||||
Export our filter function
|
||||
*/
|
||||
exports.filter = function(source,operator,options) {
|
||||
var filterFn = options.wiki.compileFilter(operator.operand),
|
||||
var suffixes = operator.suffixes || [],
|
||||
defaultFilterRunPrefix = (suffixes[0] || [options.defaultFilterRunPrefix] || [])[0] || "or",
|
||||
filterFn = options.wiki.compileFilter(operator.operand,{defaultFilterRunPrefix}),
|
||||
results = [],
|
||||
target = operator.prefix !== "!";
|
||||
source(function(tiddler,title) {
|
||||
|
||||
@@ -13,7 +13,9 @@ Filter operator returning its operand evaluated as a filter
|
||||
Export our filter function
|
||||
*/
|
||||
exports.subfilter = function(source,operator,options) {
|
||||
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source);
|
||||
var suffixes = operator.suffixes || [],
|
||||
defaultFilterRunPrefix = (suffixes[0] || [options.defaultFilterRunPrefix] || [])[0] || "or";
|
||||
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source,{defaultFilterRunPrefix});
|
||||
if(operator.prefix === "!") {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
|
||||
@@ -62,8 +62,8 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
var paramObject = Object.create(null);
|
||||
// Add names/values pairs if present
|
||||
if(this.actionNames && this.actionValues) {
|
||||
var names = this.wiki.filterTiddlers(this.actionNames,this),
|
||||
values = this.wiki.filterTiddlers(this.actionValues,this);
|
||||
var names = this.wiki.filterTiddlers(this.actionNames,this,{defaultFilterRunPrefix: "all"}),
|
||||
values = this.wiki.filterTiddlers(this.actionValues,this,{defaultFilterRunPrefix: "all"});
|
||||
$tw.utils.each(names,function(name,index) {
|
||||
paramObject[name] = values[index] || "";
|
||||
});
|
||||
|
||||
@@ -56,10 +56,10 @@ Invoke the action associated with this widget
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
var tiddler = this.wiki.getTiddler(this.actionTiddler),
|
||||
names, values = this.wiki.filterTiddlers(this.actionValues,this);
|
||||
names, values = this.wiki.filterTiddlers(this.actionValues,this,{defaultFilterRunPrefix: "all"});
|
||||
if(this.actionFields) {
|
||||
var additions = {};
|
||||
names = this.wiki.filterTiddlers(this.actionFields,this);
|
||||
names = this.wiki.filterTiddlers(this.actionFields,this,{defaultFilterRunPrefix: "all"});
|
||||
$tw.utils.each(names,function(fieldname,index) {
|
||||
additions[fieldname] = values[index] || "";
|
||||
});
|
||||
@@ -68,7 +68,7 @@ SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event
|
||||
this.wiki.addTiddler(new $tw.Tiddler(creationFields,tiddler,{title: this.actionTiddler},modificationFields,additions));
|
||||
} else if(this.actionIndexes) {
|
||||
var data = this.wiki.getTiddlerData(this.actionTiddler,Object.create(null));
|
||||
names = this.wiki.filterTiddlers(this.actionIndexes,this);
|
||||
names = this.wiki.filterTiddlers(this.actionIndexes,this,{defaultFilterRunPrefix: "all"});
|
||||
$tw.utils.each(names,function(name,index) {
|
||||
data[name] = values[index] || "";
|
||||
});
|
||||
|
||||
@@ -72,8 +72,8 @@ GenesisWidget.prototype.execute = function() {
|
||||
this.attributeNames = [];
|
||||
this.attributeValues = [];
|
||||
if(this.genesisNames && this.genesisValues) {
|
||||
this.attributeNames = this.wiki.filterTiddlers(self.genesisNames,this);
|
||||
this.attributeValues = this.wiki.filterTiddlers(self.genesisValues,this);
|
||||
this.attributeNames = this.wiki.filterTiddlers(self.genesisNames,this,{defaultFilterRunPrefix: "all"});
|
||||
this.attributeValues = this.wiki.filterTiddlers(self.genesisValues,this,{defaultFilterRunPrefix: "all"});
|
||||
$tw.utils.each(this.attributeNames,function(varname,index) {
|
||||
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],varname,self.attributeValues[index] || "");
|
||||
});
|
||||
@@ -103,8 +103,8 @@ GenesisWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes(),
|
||||
filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values",""),
|
||||
attributeNames = this.wiki.filterTiddlers(filterNames,this),
|
||||
attributeValues = this.wiki.filterTiddlers(filterValues,this);
|
||||
attributeNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"}),
|
||||
attributeValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
|
||||
if($tw.utils.count(changedAttributes) > 0 || !$tw.utils.isArrayEqual(this.attributeNames,attributeNames) || !$tw.utils.isArrayEqual(this.attributeValues,attributeValues)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
|
||||
@@ -12,7 +12,7 @@ Widget to set multiple variables at once from a list of names and a list of valu
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var SetMultipleVariablesWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -24,52 +24,52 @@ SetMultipleVariablesWidget.prototype = new Widget();
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
this.renderChildren(parent,nextSibling);
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
this.renderChildren(parent,nextSibling);
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.execute = function() {
|
||||
// Setup our variables
|
||||
this.setVariables();
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets();
|
||||
// Setup our variables
|
||||
this.setVariables();
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
|
||||
SetMultipleVariablesWidget.prototype.setVariables = function() {
|
||||
// Set the variables
|
||||
var self = this,
|
||||
filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values","");
|
||||
this.variableNames = [];
|
||||
this.variableValues = [];
|
||||
if(filterNames && filterValues) {
|
||||
this.variableNames = this.wiki.filterTiddlers(filterNames,this);
|
||||
this.variableValues = this.wiki.filterTiddlers(filterValues,this);
|
||||
$tw.utils.each(this.variableNames,function(varname,index) {
|
||||
self.setVariable(varname,self.variableValues[index]);
|
||||
});
|
||||
}
|
||||
// Set the variables
|
||||
var self = this,
|
||||
filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values","");
|
||||
this.variableNames = [];
|
||||
this.variableValues = [];
|
||||
if(filterNames && filterValues) {
|
||||
this.variableNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"});
|
||||
this.variableValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
|
||||
$tw.utils.each(this.variableNames,function(varname,index) {
|
||||
self.setVariable(varname,self.variableValues[index]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values",""),
|
||||
variableNames = this.wiki.filterTiddlers(filterNames,this),
|
||||
variableValues = this.wiki.filterTiddlers(filterValues,this);
|
||||
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
var filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values",""),
|
||||
variableNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"}),
|
||||
variableValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
|
||||
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
exports["setmultiplevariables"] = SetMultipleVariablesWidget;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
title: Filters/DefaultFilterRunPrefixPragma
|
||||
description: Test Default Filter Run Prefix Pragma
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
tags: [[$:/tags/wiki-test-spec]]
|
||||
|
||||
title: Output
|
||||
|
||||
\whitespace trim
|
||||
|
||||
\procedure mysubfilter() 1 1 +[join[Y]]
|
||||
|
||||
(<$text text={{{ ::defaultprefix:all 1 1 [subfilter<mysubfilter>] +[join[X]] }}}/>)
|
||||
|
||||
(<$text text={{{ 1 1 ::defaultprefix:all 1 1 +[join[X]] }}}/>)
|
||||
|
||||
(<$text text={{{ ::nonexistent X }}}/>)
|
||||
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>(1X1X1Y1)</p><p>(1X1X1)</p><p>(Filter Error: Unknown filter pragma)</p>
|
||||
22
editions/test/tiddlers/tests/data/filters/Filter.tid
Normal file
22
editions/test/tiddlers/tests/data/filters/Filter.tid
Normal file
@@ -0,0 +1,22 @@
|
||||
title: Filters/Filter
|
||||
description: Test filter operator
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
tags: [[$:/tags/wiki-test-spec]]
|
||||
|
||||
title: Output
|
||||
|
||||
\whitespace trim
|
||||
|
||||
\procedure test-filter() 1 1 +[join[X]] +[!match<currentTiddler>]
|
||||
|
||||
(<$text text={{{ [filter<test-filter>] +[join[ ]] }}}/>)
|
||||
|
||||
(<$text text={{{ [filter:all<test-filter>] +[join[ ]] }}}/>)
|
||||
|
||||
+
|
||||
title: 1X1
|
||||
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>($:/core 1X1 ExpectedResult Output)</p><p>($:/core ExpectedResult Output)</p>
|
||||
20
editions/test/tiddlers/tests/data/filters/Subfilter.tid
Normal file
20
editions/test/tiddlers/tests/data/filters/Subfilter.tid
Normal file
@@ -0,0 +1,20 @@
|
||||
title: Filters/Subfilter
|
||||
description: Test subfilter operator
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
tags: [[$:/tags/wiki-test-spec]]
|
||||
|
||||
title: Output
|
||||
|
||||
\whitespace trim
|
||||
|
||||
\procedure test-data() 1 2 1 3 3 4
|
||||
|
||||
(<$text text={{{ [subfilter<test-data>] +[join[ ]] }}}/>)
|
||||
|
||||
(<$text text={{{ [subfilter:all<test-data>] +[join[ ]] }}}/>)
|
||||
|
||||
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>(2 1 3 4)</p><p>(1 2 1 3 3 4)</p>
|
||||
@@ -4,6 +4,8 @@ tags: Filters
|
||||
title: Dominant Append
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Description and link to new escape hatches
|
||||
|
||||
[[Filters]] manipulate [[sets of titles|Title Selection]] in which no title may appear more than once. Furthermore, they often need to append one such set to another.
|
||||
|
||||
This is done in such a way that, if a title would be duplicated, the earlier copy of that title is discarded. The titles being appended are dominant.
|
||||
|
||||
@@ -12,6 +12,8 @@ tags: [[Filter Operators]] [[Negatable Operators]]
|
||||
title: filter Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Description of new parameter
|
||||
|
||||
<<.from-version "5.1.23">> The <<.op filter>> operator runs a subfilter for each input title, and returns those input titles for which the subfilter returns a non-empty result (in other words the result is not an empty list). The results of the subfilter are thrown away.
|
||||
|
||||
Simple filter operations can be concatenated together directly (eg `[tag[HelloThere]search[po]]`) but this doesn't work when the filtering operations require intermediate results to be computed. The <<.op filter>> operator can be used to filter on an intermediate result which is discarded. To take the same example but to also filter by those tiddlers whose text field is longer than 1000 characters:
|
||||
|
||||
@@ -12,6 +12,8 @@ tags: [[Filter Operators]] [[Field Operators]] [[Selection Constructors]] [[Nega
|
||||
title: subfilter Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Description of new parameter
|
||||
|
||||
<<.from-version "5.1.18">> Note that the <<.op subfilter>> operator was introduced in version 5.1.18 and is not available in earlier versions.
|
||||
|
||||
<<.tip " Literal filter parameters cannot contain square brackets but you can work around the issue by using a variable:">>
|
||||
|
||||
@@ -4,6 +4,8 @@ tags: [[Filter Syntax]]
|
||||
title: Filter Expression
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Update railroad diagram
|
||||
|
||||
A <<.def "filter expression">> is the outermost level of the [[filter syntax|Filter Syntax]]. It consists of [[filter runs|Filter Run]] with optional [[filter run prefixes|Filter Run Prefix]]. Multiple filter runs are separated by [[whitespace|Filter Whitespace]].
|
||||
|
||||
<$railroad text="""
|
||||
|
||||
11
editions/tw5.com/tiddlers/filters/syntax/Filter Pragma.tid
Normal file
11
editions/tw5.com/tiddlers/filters/syntax/Filter Pragma.tid
Normal file
@@ -0,0 +1,11 @@
|
||||
created: 20260122212128206
|
||||
modified: 20260122212128206
|
||||
tags: [[Filter Expression]]
|
||||
title: Filter Pragma
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: High level pragma docs & railroad diagram
|
||||
|
||||
A <<.def "filter pragma">> is a special instruction embedded within a filter expression that affects the behavior of subsequent filter operations. Filter pragmas are similar to wikitext pragmas and are used to control aspects of filter evaluation.
|
||||
|
||||
The `::defaultprefix` pragma sets the default filter run prefix for the remainder of the filter expression. This allows users to control deduplication behavior without having to specify prefixes for each individual operation.
|
||||
@@ -4,6 +4,8 @@ tags: [[Filter Expression]]
|
||||
title: Filter Run Prefix
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Link to how the default filter run prefix can be specified
|
||||
|
||||
There are 2 types of filter run prefixes that are interchangeable; [[named prefixes|Named Filter Run Prefix]] and [[shortcut prefixes|Shortcut Filter Run Prefix]].
|
||||
|
||||
<$railroad text="""
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
created: 20260122212128206
|
||||
modified: 20260122212128206
|
||||
tags: [[Filter Pragma]]
|
||||
title: defaultprefix Filter Pragma
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TODO:docs-default-prefix-for-subfilter: Purpose and docs of new pragma
|
||||
|
||||
A <<.def "defaultprefix filter pragma">> is used to set the default [[Filter Run Prefix]] for the remainder of the filter expression. This allows users to control deduplication behavior without having to specify prefixes for each individual operation.
|
||||
|
||||
Any of the [[named prefixes|Named Filter Run Prefix]] can be specified as the default prefix, but `all` is the most commonly used value to disable deduplication.
|
||||
|
||||
|
||||
34
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9595.tid
Normal file
34
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9595.tid
Normal file
@@ -0,0 +1,34 @@
|
||||
title: $:/changenotes/5.4.0/#9595
|
||||
description: Easier avoidance of deduplication in filters
|
||||
release: 5.4.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: enhancement
|
||||
change-category: filters
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9595
|
||||
github-contributors: Jermolene
|
||||
|
||||
Resolves a long-standing issue with filters where there has been no easy way to avoid deduplication of results during filter evaluation. There are two distinct new capabilities.
|
||||
|
||||
First, there is a new filter pragma `::defaultprefix` that sets the default filter run prefix for the remainder of the filter expression. Filter pragmas are a new concept akin to wikitext pragmas, allowing special instructions to be embedded in filter expressions.
|
||||
|
||||
The `::defaultprefix` pragma takes as its first parameter the desired default filter run prefix, followed by any number of filter operations to which it applies. The default prefix `all` can be used to disable deduplication for the subsequent operations (`or` is the default prefix that performs deduplication).
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
::defaultprefix:all 1 2 2 1 4 +[join[ ]]
|
||||
```
|
||||
|
||||
Returns `1 2 2 1 4`.
|
||||
|
||||
Contrast to the result without the pragma which returns `2 1 4`:
|
||||
|
||||
```
|
||||
1 2 2 1 4 +[join[ ]]
|
||||
```
|
||||
|
||||
Second, there is a new parameter to the `subfilter` and `filter` operators to specify the default filter run prefix to use when the filter is evaluated. This overrides any default set with the `::defaultprefix` pragma.
|
||||
|
||||
```
|
||||
[subfilter:all<my-subfilter>]
|
||||
```
|
||||
Reference in New Issue
Block a user