Tinderbox Forum

Peform a .replace in the descendants of note on a specific attribute

I found a repeated spelling error across multiple notes in a specific attribute, $CMFlowName. I would like to run a stamp that will go through each descendant and replace the error, change “verifiation” to “verification”.

I tried variations of this $CMFlowName=collect(descendants("test"),$CMFlowName.replace("verification","verification"));

Could not get it to work. Does anyone have thoughts on how to do this?


Sorry if I’m missing something, but I don’t see what collect adds here. Is there any reason why you wouldn’t simply use an agent to collect the notes:

(descendedFrom(/Test) | $Name=="Test")  & $CMFlowName.icontains("verifiation")

and the use the AgentAction


In fact, even simpler than that — presumably you don’t want “verifiation” anywhere in the project at all, ever. So why not just search for it and replace it wherever it’s found in CMFlowName, no matter which note it is.

E.g with an agent searching for $CMFlowNote.icontains(“verifiation”) or by highlighting every note in the project and using the stamp

if ($CMFlowName.icontains("verifiation")){$CMFlowName=$CMFlowName.replace("verifiation","verification")}

Strictly speaking, you don’t even need the if statement as the attribute won’t be changed if verifiation isn’t there to be replaced anyway.

Does that work, or am I missing something?


Wonderful! Nope, you’re not missing anything…I just could not see this tree/angle through the forest that is Tinderbox. :slight_smile:

Did find a few things. As $CMFlowName is a set, i needed to use the following for it to work.


I needed to first map it as a string with .format(“l”) to do the replacement.

Also, inside(/Test)|… or inside(“Test”)|… worked equally as well.

Stamp worked great.

Thanks. :slight_smile: Love this community. :heart::smiling_face_with_three_hearts:


I’d love to know the nuance difference between descendedFrom() and inside(). Perhaps @mwra or@eastgate can explain.

collect() collects things from somewhere else and gathers them in a list. That’s not what you want.

What you want, as @brookter suggests, is an agent:

query: $CMFlowName.contains("verification")
action: $CMFlowName=$CMFlowName.replace("verifiation","verification")

1 Like

If it’s a typo you might as well to a global replace, unless the filtering is obvious. Also, while an agent appears to add content to the document as the again is an object showing in views—compared to a stamp or rule, etc.—sometimes it’s quicker to set up and simply delete once done.

1 Like

You could also remove the undesired element from the set and add the element you wanted:

1 Like

The primary difference, IIRC, is that if a note has a alias in a container it matches. By comparison descendedFrom() only matches originals. If inside() is matching unexpected things, e.g. aliases, in a container with only children and no further descendants then using descendedFrom() will match only originals in that container and ignore the aliases.

Why the difference. This is because sometimes you want to know if a note is matched by a different agent. Take an example where 5 different agents all use the same complex query with one difference at the ent. On optimisation is to make a single new agent ('here, ‘agent6’) and get the other 5 agents to turn their query into a simpler query starting inside("agent6") & ...... If inside() didn’t match aliases, this powerful optimisation would not work.

A discrete version of inside() that doesn’t match aliases (either a new code or extra parameter) is a long-standing feature request.

Whilst talking of aliases, note the find() action has the opposite problem, in that unlike a an agent query it does not de-dupe the matches. So if a note and 2 aliases of it are in scope of the find() query, the resulting List wiki hold 3 occurrences of that note, albeit with different paths. If taking a count of the list size or iterating the list this will give you unexpected results. Generally, it can be useful to tack an &$IsAlias==false on the end of your find() query if you think you’re getting too many results.


Also, inside(theAdornment) is true if this note is in the same container as theAdornment and its position in the map overlaps the adornment.


So many possibilities. Love it! Fodder for a future lesson.

I’d an add one wrinkle here. Although it is intuitive to remove the bad value then add the new one, and the above two tasks both happen in the same action within the query cycle, in that case you start by removing the the thing matching the query. Though I’ve not been caught out by that it still feels like sawing off the branch on which you are sitting.

But, if we reverse the terms, then the good value is already in place before we reomve the query-sensitive term.


I don’t think the average user will meet that but the order still seems a good idea to me. Plus, occasionally, I’ve done the two task separately (in order to do some extra verification), in which case you must add the value before deleting the old one.

†. Memory returns that this reversal did once save me—albeit beta testing and a long while back when app crash-recovery was less robust.

1 Like

As far as I can tell, as well as Mark’s points about alias, inside() and descendedFrom() differ in that inside() only picks up the direct children of the parent, while descendedFrom() picks up every generation.


1 Like