Test if a title or string contains another string

Folks,

I would like to Test if a title or string contains another string.

For example if the following contain a #

https://tiddlywiki.com/#contains%20Operator
  • If so pass the whole string through
  • If not return nothing.

I am confident this can be done, but something about the documentation makes it hard to find and a number of filter operators have ambiguous names like search, contains etc… which are about tiddlers not strings.

  • You can use split[#], split[#]first[], split[#]last[], splitbefore[#] there is no splitafter[#]
  • However in the above it appears if there is no # the whole string is passed through

I and ChatGPT created a splitafter operator filters_splitafter.js.json (1.1 KB) which does not pass anything if the # is missing. Not the same behaviour as splitbefore.

However since ChatGPT was so helpful I want on to create filters_contains-string.js.json (1.2 KB) which tests for the string in the title and only passes it on if it is present.

So I have my answers, or a work around,

but how can we move to do this in the core?

Tobi Beer had a split plugin (predating the core split operator) which defaults to the behavior you’re looking for—i.e., no results returned if no split takes place. It’s obviously not compatible with the modern core, as written, but I’ve renamed his operator for use in my own wikis because he did include a lot of variant behaviors whose absence I feel in the core. Of course, with the advent of functions, it’s not too hard to replicate most of the plugin’s features as custom operators, but I mention this in case anyone else is interested!

Here’s the sort of function I’d use for TW 5.3.0+:

\function split.after(value) [search:title:literal,casesensitive<value>split<value>butfirst[]join<value>]

{{{ [[https://tiddlywiki.com/#contains%20Operator]split.after[#]] }}}

I included join<value> to mirror the behavior of splitbefore, which only splits once - so a string with more than one <value> will return everything after the first. You could of course omit it or replace butfirst[]join<value> with last[], depending on your desired behavior.

  • I’ve also lamented the lack of splitafter before, but writing this out, I realize that this ambiguity (what should it do, exactly?) might be one of the reasons why we don’t already have it.
1 Like

Thanks @etardiff it is important to have the way to do this with the current vanilla release. I just had a block on this.

  • Do have a look at the ones I created.
  • There may be other ways to introduce a more fundamental set of operators that help make string manipulations easier that would reduce the complexity.

If I am not wrong your solution will only work if you are searching existing tiddlers, because the exist with titles that you can search, my problem is I want to search arbitrary strings which only exist within the filter, not out in tiddler land.

[Post script]
Often we want to split something and use the first value along with the second value. This is not so strait forward in filters because there is only really one variable handled within, and output, the current title.

However the new SetMultipleVariablesWidget is a way to provide two filters, one for the names, one for the values,

The $parameters widget $params variable allows parameters handled as an array.

  • I wonder if we could craft some functions to output an array?

@Charlie_Veniot that is good if the string is valid regular expression, but what if it is not or contains the regexp you need to escape.

  • But cute never the less :nerd_face:

{{{ [[https://tiddlywiki.com/#contains%20Operator]regexp[#]] }}} ?

2 Likes

My experience suggests this isn’t true (if you test my code at tiddlywiki.com, for instance, the example I gave does work). In a search:... context, I think title is probably more accurately understood as “current string”, just as in :filter[{!!title}]–or, for that matter, <currentTiddler>, which obviously doesn’t need to be an extant tiddler. But Charlie’s regex suggestion is more elegant and I’d probably use it instead (assuming that your users understand the use of \ to escape special characters).

1 Like

I think the best solution we have right now—certainly, the one I use the most—is adding +[format:titlelist[]join[ ]] at the end of the filter and then enlist<variable>nth[X]to retrieve positional values where they’re needed. It’s not terribly elegant, but it generally works.

I think this would be considerably easier with an interleave operator, as you essentially noted in the thread where it was proposed. But, as a brain game, I tried it out:

\define filter() [all[tiddlers]tag[HelloThere]]

\function make.array(filter)
[subfilter<filter>count[]] :map:flat[range{!!title}]
:map[subfilter<filter>nth{!!title}jsonstringify[]addprefix[": "]addprefix{!!title}addprefix["]addsuffix["]]
+[join[, ]addprefix[{ ]addsuffix[ }]]
\end

{{{ [make.array<filter>] }}}

Results:
image

Certainly not elegant (and it’d be even messier if I weren’t using numbers as names), and I’m not convinced it’s very useful. But possible, yes.

Edit: I gave it a bit more thought, and it’s actually not too hard to interleave sets of titles—though it would still be nice to have a dedicated operator!

\define labels() a b c d e f g h i j k l
\define values() [all[tiddlers]tag[HelloThere]]

\define labels.and.values() [subfilter<labels>nth{!!title}] [subfilter<values>nth{!!title}] +[join[) ]]

\function interleave.with()
[subfilter<values>count[]] :map:flat[range{!!title}] :map[subfilter<labels.and.values>]
\end

{{{ [interleave.with<labels>,<values>] }}}

image

You’d have to modify the join and add the appropriate prefixes as suffixes to yield a JSON object, but it’s feasible:

\define labels() a b c d e f g h i j k l
\define values() [all[tiddlers]tag[HelloThere]]

\define labels.and.values() [subfilter<labels>nth{!!title}] [subfilter<values>nth{!!title}] +[join[": "]]

\function make.array()
[subfilter<values>count[]] :map:flat[range{!!title}] :map[subfilter<labels.and.values>]
+[addprefix["]addsuffix["]]
+[join[, ]addprefix[{ ]addsuffix[ }]]
\end

{{{ [make.array<labels>,<values>] }}}
2 Likes
  • I certainly use this to put lists inside functions to then filter or provide as an attribute value.
  • Interesting idea
  • But of course we can also use any filter such as zth[2] first[] [last[] sort[] reverse[]

Look at this example;

\function labels.list() a b c d e f g h
\function values.list() [all[tiddlers]tag[HelloThere]]

<$setmultiplevariables $names="[labels.list[]]" $values="[values.list[]]">

{<$list filter="[labels.list[]]" variable=var>
   "<<var>>":"<$text text={{{ [<var>getvariable[]] }}}/>",
</$list>}

Result;

{ "a":"A Gentle Guide to TiddlyWiki", "b":"Discover TiddlyWiki", "c":"Some of the things you can do with TiddlyWiki", "d":"Ten reasons to switch to TiddlyWiki", "e":"Examples", "f":"What happened to the original TiddlyWiki?", "g":"Funding TiddlyWiki", "h":"Open Collective", }
  • I expect it can be simplified further.
  • perhaps some counting and matching could be done to align the labels with values.

This is more in line with what I’d normally do myself - though, of course, you’d have to wikify the output if you wanted to use the array as a variable, which was the challenge I was responding to:

The advantage of a single function like my <<make.array>> is that you don’t need to wikify the results, so in theory, you should be able to use it as-is to set parameters. (I haven’t tested as I haven’t yet discovered a scenario in which I need/want to use $parameters instead of $let or $setmultiplevariables… but if I’m understanding the docs, I believe it ought to work.)

As a side note…

Using the comma inside the list widget will produce an extra comma at the end of the array. If you prefer the wikified-list approach, I’d recommend using the new join list attribute: join=", ".

One option I have used in the past is :filter[split[#]count[]compare:number:gt[1]] (or an equivalent using a separately defined filter as a variable + the filter operator

This tests if running the split operator results in a list with more than one item in it (i.e. if the split has found a #. If it has, then the original string is returned and the intermediate result is discarded.

  • Too true, oversight on my part
  • I will try and place in another function
  • Yes, thanks.
  • using join[,] correctly will do this as well.