Tinderbox Forum

Tinderbox Training Video 31 - Integrating Export Code & Action Code in Your Text

Hmmm… I get how to pull multiple values, but how to get them in a $MyList?

Here’s some code that queries my file and gets me an ordered list (per Michael’s example):
^action($MyList=collect_if(descendants(this),$Prototype=="Data Extract",$Name);$MyList=$MyList.format("<ol>","<li>","</li>","</ol>");)^

And then I get that list by:
^value($MyList)^

What do I add to the query to grab $Name and $Tags for any given “Data Extract?”

Beck, there might be a more efficient way to accomplish what you want to do? Are you looking to create a bulleted list that looks like this:

  • Becker, Privacy
  • Mark, Developer
  • Peter, Engineer

And then, do you want it in the body of a specific not surrounded by some text, i.e. embedded as include, or represented in some other way? In other words, can you create a quick example (hand-drawn picture is fine) as to what you want you output to look like and where your data is coming from?

How about

$MyList=collect_if(descendants(this),$Prototype==“Data Extract”,$Name+","+$OtherAttribute)

2 Likes

Got it! I tried that at first and it did not work. I must have had a typo. Beck, this works in the example:

^action($MyList=collect_if(descendants("Key points"),$Prototype!="pNotes",$Name+", "+$Point);$MyList=$MyList.format("<ol>","<li>","</li>","</ol>");)^
^value($MyList)^

Output is this:
List attributes for Key points.

  1. Point 1, Interesting idea 1
  2. Point 2, Interesting idea 2
  3. Point 3, Interesting idea 3
  4. Point 4, Interesting idea 4
  5. Point 5, Interesting idea 5
  6. Sub-point of 5,
2 Likes

This works! In my code example:

^action($MyList=collect_if(descendants(this),$Prototype=="Data Extract",$Name+", "+$Tags);$MyList=$MyList.format("<ol>","<li>","</li>","</ol>");)^ ^value($MyList)^

Now, to get those items with multiple tags formatted well… stay tuned!

2 Likes

BTW, I think it is important to remember that all this export code need not be done in the note itself, it can also be integrated into export templates, its natural home. Just another amazing point about Tinderbox’s flexibility.

1 Like

Yes! This was a realization I made yesterday!

2 Likes

Twisting the ideas in this thread a little further, I’m trying to turn the concepts into a Table of Contents to be exported in Markdown format, incorporating links to the actual header.

E.g. I export a note with children to a markdown file, and at the top of the file there’s a TOC with clickable links to the child sections (which have to be in the format [Heading name](#heading-name))

Using the code in this thread so far, I have:

^value((collect(descendants(this),"[" +$Name+"](#"+$Name.lowercase.replace(" ","-")+")")).unique.format("","- ","\n",""))^

This produces:

- "[Daily Notes](#daily-notes)"

- "[Markdown image links redux](#markdown-image-links-redux)"

- "[Another note](#another-note)"

As you can see, the problem is that it surrounds each line with unwanted quotation marks, and I can’t see where they’re coming from.

If I replace the string concatenation with $Name on its own, there are no quotation marks and it turns out as expected:

^value((collect(descendants(this),$Name)).unique.format("","- ","\n",""))^

-->

- Daily Notes
- Markdown image links redux
- Another note

If I take the concatenation out of the action code and preset it into a calculated attribute ($TOCtitle), so the collect is calling an simple attribute rather than constructing the string on the fly, I still get the unnecessary quotation marks — so why is this different from calling another string $Name?

I must be missing something obvious, but I can’t see what it is…

Thanks for any help (and thanks very much for all the great ideas in the thread so far. It’s expanded my knowledge and ambitions hugely…)

OK, the cause is the parentheses () in the literal string sections of the collect(). It has nothing to do with attribute called AFAICT. This is easily proven if you change the from () to {}. Now the content exports OK, without the quotes but with the wrong type of brackets.

I made a wrong call here, fixed/explained further below. I’ll leave my error in strike-through so the thread doesn’t get confusing. But, my sincere apologies to @eastgate

I tried escaping the parentheses in the original code, i.e. \( and \) but that still resulted in quoted output. I then used the curly bracket substitution and a pair of replace calls to the overall formatted list. IOW after the .format() call. I tried with and without escaping of the parentheses, here with escape code:

.replace("{","\(").replace("}","\)")

No joy. It seems in both context—within collect() and within .replace()—you can’t seemingly use literal parentheses without getting an incorrect result. FWIW, I did a lot more testing but I won’t bore all with detail of things that didn’t work around the issue.

I would note, in fairness, that IIRC until recently collect() was only used for pulling a single attribute value from each note in the designated group rather than creating strings.

@eastgate, is this result to be expected. I had assumed that \( would cause Tinderbox to treat a parenthesis symbol as a literal and not a closure around an input or subordinate clause.

My error with .replace() was to forget that it is the first input term was is a regex. Unfortunately, the made me overlook the fact that the curly brackets needed escaping, like so:

.replace("\{","(").replace("\}",")")

Sorry, everyone. An updated demo file with additional code resolving this issue is further below.

1 Like

Hi, Mark,

Thanks for the explanation! I’d been doing some more digging and had just found this in atbref, (I had it ready to post when I saw your reply…) so I was beginning to suspect it was the parentheses.

collect(group,attribute)

If the collected attribute type is multi-value, i.e. Set or a List type, collect() adds its elements to the result. If the collected attribute is not a Set or List type attribute, but contains a semicolon, quotation mark, or parentheses, the value will be added to the result as a quoted string. The change should correct a variety of confusing edge cases.

There are always going to be odd cases where regular expressions don’t work, and this is a fairly niche case, perhaps.

I suppose the next question is, is there a way to build the list of bullet points without using collect or replace?

Something like (conceptually):

while there are still children
$TOCtext = $TOCtext + "- [" + child($Name) +  "](" + child($AmendedName) + ")\n"

only using syntax that actually exists… I’ve never used repeat loops in Tinderbox, so I’m off to look at atbref again, but I suspect it will fail because there’ll have to be a set, list or replace() in there somewhere…

Right, I think I’ve found a way to do this — but using runCommand and sed, rather than internally in Tinderbox.

Just to pull it all together for anyone coming to the thread afresh, the aim is to produce a Table of Contents of a note’s children, in markdown, with each heading a clickable link (for which the markdown syntax is [Heading Name](#heading-name).

The problem is that the method used to collect the headings from the children always add " " around the entries, because lists and sets always do that when ( ) are in a entry, and because replace() can’t deal with ", you can’t post-process the output in Tinderbox to remove the them…

But, if we use the command line program ‘sed’ we can strip the " from the elements.

Basically, we need

  1. A user attribute $SedCommand with the value sed 's/\"//g'. (I’ve put it in a note Config, so reference it with `$SedCommand(“Config”).

  2. A runCommand to apply SedCommand to the relevant set and remove the " into a string which can then be printed. The action code is:

Collect the names of the children and format them in a bullet point clickable list of headings:

^action($MySet=(collect(descendants(this),"[" + $Name+"](#" + $Name.lowercase.replace(" ","-") + ")")).unique.format("**TOC***\n","- ","\n",""))^

Echo the values of $MySet and $SedCommand into a single command run in the command line, and return the quote-less result back into $MyString:

^action($MyString=runCommand("echo '" + $MySet + "' | " + $SedCommand("Config")))^

Export the string in the normal way

^value($MyString)^

The result is:


**TOC**
- [Daily Notes](#daily-notes)
- [Markdown image links redux](#markdown-image-links-redux)
- [Playing with the grandkids](#playing-with-the-grandkids)

Hope this is useful for anyone else who wants to try something similar, and thanks to all on whose ideas this is built.

1 Like

Here’s a method avoiding use of run-Command so avoiding a slower trip out to the command line. this is the template for the container (template ‘md list’ in the demo file):

^action(
   $MyList =;
   $MyString =;
   $MyList = (collect(descendants,$Name)).unique;
   $MyList.each(X){
      $MyString = $MyString + "- [" + X +"](#" + X.lowercase.replace(" ","-") + ")\n";
   };
)^

^value($MyString)^

^action($MyList =;$MyString =;)^

Essentially, we do the concatenation inside an .each(){} loop making a string as we go. We then write out that string to export via ^value()^ tidying up the attributes used so as not to leave unneeded mess.

Note that we don’t need the this offset for the descendants as that is implicitly the case. I also suspect the unique is not needed either unless you have genuinely duplicate descendants (i.e. more than just same $Name).

You can lose one attribute to a variable, but sadly variables can’t be passed to export. But here’s that variant (template ‘md list2’ in the demo file):

^action(
   var vList();
   $MyString =;
   vList = (collect(descendants,$Name)).unique;
   vList.each(X){
      $MyString = $MyString + "- [" + X +"](#" + X.lowercase.replace(" ","-") + ")\n";
   };
)^

^value($MyString)^

^action($MyString =;)^

Here’s a demo of the above: md-list.tbx (105.2 KB)

That’s great, thanks Mark. As I said, I’ve never used loops in Tinderbox before, so it’s good to see how they can help in such circumstances.

To be fair, I’ve never used either runCommand or Sed much either and was quite chuffed when I got that to work as well — I’ve enjoyed learning all this.

Thanks again.

1 Like

Nothing wrong with runCommand and all the cool tools art your disposal and well done for breaking through to their use. :slight_smile:

My last was an alternate as opposed to an outright correction. If I’d not been rushed easier today, I’d probably have got to the loop method first time. As you note the collect issue is known (that aTbref cryptic note is now illustrated!). The Replace fail was more of a surprise but I’ve a feeling that too might be a (now forgotten) known limitation. Were we starting over with computers today I suspect we might have created some non standard text characters to use in code precisely to avoid this issue where it can be hard for the code to guess what the human means, contextually, by ‘(’.

2 Likes

Probably just as well we’re not trying to code Emacs Lisp in Tinderbox…

1 Like

Stupid me, .replace() was fine. I was escaping the wrong, being over focussed on parentheses. Consider:

.replace("{","(")

What’s wrong. We recall the first parameter is a regex. Doh, so both curly and straight brackets are characters with regex roles, so must be escaped, like so:

.replace("\{","(")

The second term is a literal string so the ( has no special meaning there). Thus my profuse apologies to @eastgate for a totally wrong call above.

As a result @brookter’s one-liner can be made to work with a bit of tweaking. First we work around collect() generating quotes in the output by using curly brackets instead of parentheses:

collect(descendants,"[" +$Name+"]{#"+$Name.lowercase.replace(" ","-")+"}").unique.format("","- ","\n","")

Then when the whole string is made, we replace the curly brackets with parentheses.

.replace("\{","(").replace("\}",")")

Here is the whole thing as a one liner. I’ve removed the redundant this reference and the extra parentheses enclosing collect :slight_smile:```
^value(collect(descendants,"[" +$Name+"]{#"+$Name.lowercase.replace(" “,”-")+"}").unique.format("","- “,”\n","").replace("{","(").replace("}",")"))^

Here is my earlier demo with the latter code as a new template 'md list3'. Try it out: [md-list2.tbx|attachment](upload://rgHVvVQoKWrCqEllyJPJJ6T8Nl8.tbx) (105.9 KB)
1 Like

Hah!

Well, if you hadn’t taken that initial wrong turn, I wouldn’t have looked at using sed, or read your tutorial on how to use each in this context, and I’d not have learnt as much — so it’s all good!

But I’m also grateful that you’ve unearthed this single line version as well — most helpful…

BTW, one of the difficulties with getting to grips with some of the arcania of Tinderbox is that really useful lessons like this can be buried at the bottom of threads which started off with something else.

If you don’t mind, I think I’ll summarise the results of this discussion as a new thread with its own title so it’s easier to find — I don’t think there’s been one explicitly considering creating markdown lists from child notes and the use of clickable headers and it may be useful for someone else. Won’t be tonight though.

Thanks again!

2 Likes

Could not agree more, that’s why I started the videos.

Do please. Sometimes it’s much better summarised from the POV of someone for whom the material is new. :slight_smile:

Here’s a summary of the final (much simplified!) position we reached — it incorporates Mark’s later refinements from this thread: Trying to pull a list with ^action()^ and it each item is quotes, how do I get rid of the quotes)

http://forum.eastgate.com/t/trying-to-pull-a-list-with-action-and-it-each-item-is-quotes-how-do-i-get-rid-of-the-quotes/4303/5

1 Like