The :cascade[]
filter run prefix is super efficient for selecting tiddlers that match any of the filters given in the operand. However, I sometimes need to select tiddlers that match all the operand filters. I didn’t find an efficient solution to this problem yet, although I’m sure it is possible to write a recursive solution. Here is my use case below.
Say we’d like to maintain a recipes database in TiddlyWiki. Among the set of tools we want is a query builder that would help the database manager to create named queries from a list of conditions, without prior knowledge of the TiddlyWiki filter language. The list of conditions that make up a query may look like this to the database manager:
Let’s also say that we store the information for each condition in a dedicated tiddler. Then, its subfilter field may get updated with a corresponding filter whenever the user modifies a condition, using default values if needed:
-
subfilter value for condition 1:
[get[title]] :and[search:title<user.input>]
-
subfilter value for condition 2:
[get[Origin region]] :else[[World]] :intersection[list[World!!regions]]
-
subfilter value for condition 3:
[get[Preparation time]] :and[compare:integer:lt[1000]]
-
subfilter value for condition 4:
[apply.query[No meat]]
-
subfilter value for condition 5:
etc...
(Note that a previously defined query can also serve as a condition in another query.) The list of all the condition filters from a given query is easily built with this helper function:
\function condition.subfilters(queryName)
[<queryName>listed[]tag[conditions]]
:map[get[subfilter]]
\end
If the query is meant to be an OR query, we expect it to return a result if any of the query filters matches an input item. In order to apply this query to all the recipes in the database, we would use such a construct:
[all[tiddlers]tag[recipes]!is[draft]] :filter[suchAs.any<selected.query>]
Since our suchAs.any[]
function has to return a result if any of the filters from the given query matches the current tiddler, calling on the :cascade[]
filter run prefix is a suited wikitext solution. This works like a charm:
\function suchAs.any(queryName)
[all[]]
:cascade[condition.subfilters<queryName>]
:filter[!is[blank]]
\end
However, if we’d want to build an AND query, we’d rather need a suchAs.all[]
function that applies all of the condition filters from the selected query to the input list, likeso:
[all[tiddlers]tag[books]!is[draft]] :filter[suchAs.all<selected.query>]
This suchAs.all[]
function would need to pipe every filters of the query in turn and return a result only if they all match the input tiddler. Something functionally equivalent to:
\function suchAs.all(queryName)
[all[]]
:filter[condition.subfilters<queryName>first[]]
:filter[condition.subfilters<queryName>nth[2]]
:filter[condition.subfilters<queryName>nth[3]]
:filter[etc.]
:filter[condition.subfilters<queryName>last[]]
\end
This is where I’m stumbling.
This problem looks like it is begging for a recursive solution in wikitext, but it would have to recurse on both the input list and the filter list, which, after many attempts, seems beyond my reach. Can you suggest a method for such intricated recursions?
I did find rather convoluted and slow workarounds, but nowhere near as straightforward nor effective than a cascade:all[]
filter run prefix. It would apply in turn each filter defined in its operand to its input list, until it finds a filter that doesn’t match the input tiddler, in which case it would return the empty string, or the last filter result in case of success. If such filter run prefix existed, I could define my suchAs.all[]
function as simply as:
\function suchAs.all(queryName)
[all[]]
:cascade:all[condition.subfilters<queryName>]
:filter[!is[blank]]
\end
Needless to say, programming such a filter run prefix in JavaScript isn’t up my street either!