Finding tiddlers with only one tag

Is there a way to write a filter to find all tiddlers having one specific tag and no others? So if Tiddler A has tags [[A]] and Tiddler B has tags [[A]] [[B]] only tiddler A would match?

While the tags field is normally used to hold a space-separated, bracketed list of one or more tags, it can also be treated as just another regular field containing a text value. For your purposes, you can simply compare the entire tags field value with the desired value.

For example, at https://TiddlyWiki.com, put this in a tiddler:

<$list filter="[field:tags[Welcome]]"><$link/><br></$list>

The result does NOT list “GettingStarted” and “Community”, since those tiddlers each have more than just the “Welcome” tag. However, you might notice that the tiddlers are not listed in the same order as when using the [tag[Welcome]] filter to show all tiddlers that have at least the “Welcome” tag and possibly other tags.

This is because the [tag[...]] filter operator uses a more complex set of rules to determine the order of the results. See Order of Tagged Tiddlers for details.

To achieve the same order as the [tag[Welcome] filter, but only have that one tag, you can write this:

<$list filter="[tag[Welcome]] :filter[field:tags[Welcome]]"><$link/><br></$list>

This will first get all tiddlers tagged with Welcome, using the Order of Tagged Tiddlers rules, and then filter that result to retain those tiddlers that have only that one specific tag, while preserving the same tiddler order.

Another note: if the tag value contains spaces, then the value stored in the tags field will include doubled square brackets surrounding the tag value. However, since literal filter operand values cannot contain square brackets (because they are used by the filter syntax itself), a workaround is needed by using a variable to hold the desired tag value, like this:

<$let tagvalue="Tag With Spaces">
<$list filter="[field:tags<tagvalue>]"><$link/><br></$list>
</$let>

or, for the second syntax example, we can change the :filter[...] filter run without needing a variable, like this:

<$list filter="[tag[Tag With Spaces]] :filter[enlist{!!tags}count[]match[1]]"><$link/><br></$list>

enjoy,
-e

8 Likes

Thanks, this really helped. The last syntax is what I was after because I was plugging the filter into Advanced Search and was looking for a tag with spaces in the name, making the other forms a little more awkward.

Sorry to necro this , but I’ve run into a small complication that I think fits this topic.

If you wanted to filter for a tiddler that has specifically two tags that have multiple words that are separated by spaces or other whitespace, how would you achieve this?

I’ve tried below but unfortunately it doesn’t really work…

<$let tagvalue="[[Meeting Minutes]] [[Instruction Tiddlers]]">

<$list filter="[field:tags<tagvalue>]"/>

</$let>

OR

<$let tagvalueA="[[Meeting Minutes]]" tagvalueB="[[Instruction Tiddlers]]" tagvalue="[<tagvalueA><tagvalueB>]" >

<$list filter="[field:tags<tagvalue>]"/>

</$let>

Neither of the above work, however the following does (I didn’t think it would but wanted to cover my bases.)

<$let tagvalue="[[Meeting Minutes]] Welcome">

<$list filter="[field:tags<tagvalue>]"/>

</$let>

In theory, your first attempt might have worked except for the fact that it depends on the order in which the tags are stored in each tiddler’s tags field; i.e.

[[Meeting Minutes]] [[Instruction Tiddlers]]

vs.

[[Instruction Tiddlers]] [[Meeting Minutes]]

Unfortunately, the order of those tags is not guaranteed to be the same, depending upon how those tags were added to the tiddlers:

  • If you set tags one tag at a time using the tiddler editor’s tag controls (above the tiddler text input) they are automatically stored in ascending alphabetic order.
  • However, if you enter both tags at once by using the “add new field” controls below the text input to directly set the tags field value to “[[Meeting Minutes]] [[Instruction Tiddlers]]” – the tiddler’s tags are NOT automatically sorted alphabetically and are stored exactly as entered.
  • This is also the case if you use your own wikitext $button to set the tags field value, like this:
<$button>set tags
<$action-setfield tags="[[Meeting Minutes]] [[Instruction Tiddlers]]">
</$button>

Note that, regardless of how you entered/set the tags, they are always displayed in ascending alphabetic order when you view or edit the tiddler.

Since the stored tag order is not guaranteed to be the same for all tiddlers:

  • Your target tagvalue should list the tiddlers in ascending alphabetic order
    (i.e., [[Instruction Tiddlers]] [[Meeting Minutes]])
  • Then, you will need to sort the tiddler’s tags before comparing them with the desired tagvalue text in order for your filter to work reliably.

Something like this will do the job:

<$list filter="[all[tiddlers]] :filter[enlist{!!tags}sort[]format:titlelist[]join[ ]match<tagvalue>]"/>

Notes:

  • First, we get all tiddlers
  • Next, we use a :filter “filter run prefix” to find the desired matching tiddlers. This filter run take each input tiddler in turn, and internally sets the currentTiddler to that tiddler’s title. Then:
    • enlist{!!tags} gets that tiddler’s tags and splits it into a list of separate tag values
    • sort[] sorts those tag values in ascending alphabetic order
    • format:titlelist[] puts double square brackets around any tag value that contains spaces
    • join[ ] assembles the now sorted list of tags back into a single text string
    • match<tagvalue> does the comparison with the “target” tags
    • Only when the match<tagvalue> test succeeds will the :filter[...] filter run return that tiddler’s title

enjoy,
-e

2 Likes

Awesome!

I’ll have to make a mental note to keep the tags in alphabetical order when filtering for the specific tiddlers.

I’m a little confused by this, and was watching the conversation to see what I’m missing. Do you simply want to filter for tiddlers that have both Meeting Minutes and Instruction Tiddlers? Or are those merely example values?

I ask because if it’s the former, then [tag[Meeting Minutes]tag[Instruction Tiddlers]] should just work.

If it’s the latter, I might just generate that same filter from our input, and call it with subfilter, like this:

\define ta() tag[
\define cl() ]
\define op() [
\function multitag() [enlist<tagvalue>addprefix<ta>addsuffix<cl>join[]] +[addprefix<op>addsuffix<cl>] 

<$let tagvalue="[[Meeting Minutes]] [[Instruction Tiddlers]]">
<$list filter="[subfilter<multitag>]"/>
</$let>

It was to filter for a tiddler or tiddlers that have only those two tags, no other or not one or the other. Using the normal tags way would include tiddler that have both in addition to other tags :blush:

Here’s yet another way to achieve the results you want, without worrying about the tag sort order:

<$list filter="[tag[Meeting Minutes]tag[Instruction Tiddlers]]
   :filter[enlist{!!tags}count[]match[2]]"/>

Notes:

  • The first filter run finds all tiddlers with both desired tags… and possibly other tags… in any order.
  • The :filter[...] filter run then only keeps those tiddlers that have exactly 2 tags.

-e

6 Likes

Ok, then Eric’s response seems to be enough.

Here’s yet another way to achieve the results you want, without worrying about the tag sort order:

<$list filter="[tag[Meeting Minutes]tag[Instruction Tiddlers]]
   :filter[enlist{!!tags}count[]match[2]]"/>

Notes:

  • The first filter run finds all tiddlers with both desired tags… and possibly other tags… in any order.
  • The :filter[...] filter run then only keeps those tiddlers that have exactly 2 tags.

-e

I’m a bit curious about that, as I’ve seen it used elsewhere, what prevents it from filtering for tiddlers who do have both of those tags, but lets say a 3rd tag it has is alphabetically between them, would the filter see “[Instruction Tiddlers] [Journal] [Meeting Minutes]”, limit it to two tags, and thus not choose that one because it would have “[Instruction Tiddlers] [Journal]” ?

1 Like

The :filter[...] counts the total number of tags. Thus, while your example ([[Instruction Tiddlers]] Journal [[Meeting Minutes]]) has both desired tags, the total tag count is 3, and would be rejected from the final output.

-e

1 Like

Ahh okay, I think I follow it now, cool :grin:

This is another solution.

\procedure list-by-numberof-tags(n:"1", scope:"[!is[system]]", second-filter:"[all[]]")
\procedure show-tags()
<!--for demo purpose: shows all tags for current tiddler -->
<$list filter="[<currentTiddler>tags[]]" variable=thisTag>
<$transclude $variable=tag-pill tag=<<thisTag>> />
</$list>
\end show-tags

<$list filter="[subfilter<scope>] :filter[tags[]count[]match<n>] :filter[subfilter<second-filter>]">
<$link/> - Tags: <<show-tags>> <br>
</$list>
\end list-by-numberof-tags

!! Example
Display all non-system tiddlers along with their tags, if they have exactly five tags:


<<list-by-numberof-tags n:"5">>

This code defines a scope to search and a specific number of tags n. For instance, you may want to list all non-system tiddlers with exactly 5 tags.

You can also apply a second filter to refine the output further. For example, to list all non-system tiddlers with exactly 4 tags, and also tagged with Apple and Orange, you can write:

<<list-by-numberof-tags n:"4" second-filter:"[tag[Apple]tag[Orange]]">>

I drafted this code quickly, and it could be optimized into one or two functions for better performance and adherence to best practices. :blush: