Automatic Alias if title+prototype already exists

Well, as action code doesn’t create new notes**, the issue is moot.

** As a design intent, action code does not (currently) allow new notes to be created, or existing ones deleted, or (apart for a workaround) to create aliases. This choice was because of the scope for user error to create run-away actions that resulted in a lot of work to repair unintended outcomes.

1 Like

Apologies for reviving an old thread. But I have a similar question to the original poster.

I likewise am making a keyword glossary. So long as I work inside TBX, the Attribute Browser, as described by Mark, is perfect.

But what if I want to export my glossary? For each term, I wish to export a plain text file that has the glossary term as the note’s name, with the body of the text file listing the $Name of all notes that are tagged (in an attribute $Keyword) with that keyword.

Is this possible?

Yes. ‘HTML’ export makes this perfectly possible, though you don’t have to be creating HTML formatted documents. The templates and export code you use in your document control the sort of document created.

1 Like

Thank you for the confirmation, Mark.

In the discussion above, you had discouraged using agents to create a register. I can imagine how, using agents, I might use ‘HTML’ export to make one exported document per agent.

But could you kindly point me in the direction of how to do this otherwise? At the moment, my “keyword glossary” is just the Attribute Browser set to show me all notes organised according to the $Keyword attribute. How do I move from this to exporting one note per Keyword?

Please note - this code is not tested (as a bit busy right now) so don’t feel it’s rude to ask if the code doesn’t work.

Task is that for each $Keyword term:

  • export a plain text file that has the glossary term as the note’s name…
  • with the body of the text file listing the $Name of all notes that are tagged (in an attribute $Keyword) with that keyword.

So we make a note with this rule:

$MyList = values("Keywords")

$MyList now contains every discrete $Keywords value across the whole document - which is likely what your are using in AB view. so, now we have to export a note with info per discrete $Keywords value…

So, for each $MyList value (i.e each discrete keyword) we want a list of notes that use that value in $Keywords.

We can’t easily export a file per keyword (without making a note or agent per value - bad if there are tens or hundreds of values) but we can export a single file with a delimiter which makes it easy to split the exported file into discrete files using a text/code editor.

Add an extra user List-type attribute $MyList1.

So our export template is:

^action(
$MyString=;
$MyList= values("Keyword")
$MyList.each(aValue){
   $MyString = aValue;
   $MyList1= find($Keyword.icontains(aValue));
   $MyList1.each(aKeyword){
      $MyString = $MyString + "\n" + aKeyword;
   }
})^^Value($MyString)^^action($MyString=;$MyList=;$MyList1=;)^
```
As noted, the above is _not_ tested (no spare time) but it should work or hopefully help you to the correct outcome.

Thank you so much for this, Mark, especially as you are busy.

I’ve given this a test run, but no luck. I’ve attached a link to a minimum working example.

(Note that in the action code of the export template in the file below, I changed all instances of “keyword” (singular) to “keywords” (plural). I only did this after testing with your original code, which did not work. I then assumed that ‘keyword’ was just an oversight since the attribute is $Keywords.)

I am grateful for this help.

There were punctuation, spelling, curly quotes, and syntax errors in your template_keywords template. Try:

^action(
$MyString=;
$MyList= values("Keywords");
$MyList.each(aValue){
   $MyString = aValue;
   $MyList1= find($Keywords.icontains(aValue));

   $MyList1.each(aKeywords){
      $MyString = $MyString + "\n" + aKeywords;
	}
}
)^
^value($MyString)^
^action($MyString=;$MyList=;$MyList1=;)^

I believe there are other problems, but have no time to parse another’s work.

Thank you, Paul. This is a step forward. However, only the keyword that comes last in the sorted list of $MyList is exported. So, out of “alfalfa;banana;broccoli;testing;”, only “testing” appears in the exported file.

Below is an updated minimum working example:

OK, now working. Corrected/expanded TBX uploaded: keyword-test2.tbx (110.3 KB)

First, I added appropriate prototypes & templates and containers for said objects. The error in my untested code was $MyString was getting overwritten each time the outer loop ran, so only the last keyword’s data was retained. I fixed that and note ‘keyword_list’ using template ‘t_keywords’, shows the result. I also added the code as an edict that writes the output to the same note’s $Text. This shows a quite useful way of debugging code that otherwise only runs during export. Of course, once working you would delete or turn off the edict and remove the test $Text.

So, the example now lists each discrete $Keywords value, but lists the full path of each note using it. This is because find() always returns a list of $Path values. Here, I assume just the note title would be better. So, in note ‘keyword_list1’ using template ‘t_keywords1’ I’ve amended the code so only the note’s $Name is listed.

Many thanks, Mark (and Paul): it is now exporting correctly! This is really very helpful, and I am grateful for the code.

(As an aside, are there any tutorial-type resources to learn how to produce such code, walking a student step-by-step through learning the grammar and vocabulary of TBX code-writing? Over the years, I’ve learnt how to use all the basic features of TBX that have a user-interface, and can do some basic rules, but things like the code you’ve provided above — things like “aValue”, and the general syntax — are beyond me.)

A further aside: this is a long-standing desideratum of mine: the ability to use action code to create new notes. I understand the scope for error, but most of my TBX projects would really benefit from such a feature.

I think the confusion arises in associating the code too closely with one output. In this thread there isn’t a pre-built function to do your task. Therefore we need to apply some generalised techniques. We need to:

  • Get a list of all the discrete values used in Keywords.
  • Iterate through that list - i.e. step through it value by value in order to to further work with each value.
  • Get a list of all the notes using that particular keyword value and loop through that to build the final output list.
  • Around this we need to make/use a few extra String and List type attributes to handle the data being processed

So le’ts walk through the code in t_keywords1. We start with some Markdown code which isn’t pertinent to our task be is needed as part on the final export file:

# ^value($Name)^ 

**Date:** ^value($Created)^

Now, because we want to run action code during export, we need to open an ^action()^ export code:

^action(

Now we can start out action code. First we reset the default value for $MyString to ensure we don’t get any data held over from a previous run of the code:

$MyString=;

Now, we store all the discrete values used by $Keywords in a List-type attribute. It doesn’t have to be ‘$MyList’ but I assumed using that particular type of name would help indicate the type of attribute needed:

$MyList = values("Keywords");

We now have our list. We need to use the .each() operator to iterate across it. To use .each() you need to provide a name for the in-loop variable that with hold the value of the list item being used. I could have used ‘X’ or ‘babanna’ but ‘aValue’ tries to indicate the purpose. Note that when declared in the .each() command’s parentheses we do not use quotes around the name.

$MyList.each(aValue){

Now we are in the first loop. If $MyList as the values “apple;fig;pear”, on the first iteration of the loop, where you see the code ‘aValue’, it will actually pass the string “apple”, “fig” on the second iteration, etc. First, we add a line break and the current list value to $MyString. On the very first run, $MyString is empty and so you get a blank line (due to the “\n”) before the first text. You could at an if() clause here to catch that but it would only make the code more complex for beginners to understand and the blank line does no harm.

   $MyString = $MyString + "\n" + aValue;

Now we make a new list ($MyList1) that finds any note where the value(s) of $Keywords match ‘aValue’.

   $MyList1 = find($Keywords.icontains(aValue),$Name);

now we start the second (inner loop). Again, we use .each(), this time using a different loop variable name - this time it is ‘aKeywords’. With hindsight 'aKeyword` might have amde more sense but i hope you get the general idea:

   $MyList1.each(aKeywords){

Here, we simply add a line break and the title (i.e. $Name) of each note in $MyList1:

      $MyString = $MyString + "\n" + $Name(aKeywords);

Now close out both loops and the ^action()^ element::

	}
}
)^

Some more markdown and general export code follows:

**Keywords:** ^value($MyString)^

Lastly we add a further ^action()^ element containing code that resets the 3 attributes we’ve been using to run this code. Clearing these ensures we aren’t persistently storing lots of temporary data after the code has run:

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

All done. Does that help make sense of things? Annotating code like this takes a while and can’t easily be done in Tinderbox as there isn’t a code-commenting mark-up.

Hey @talazemyou can create notes with action code! :slight_smile:

This is truly very helpful, Mark. Thank you for taking the time to walk me through this. I really think it is such “step-by-step” tutorials for the coding of TBX that makes sense of what is otherwise very helpful but also very abstract information in the Tinderbox Cookbook and Reference File. What you’ve written above now makes the language more intelligible, even to someone as code-ignorant as I. Thank you again!

Pat: I am intrigued. I’ll take a look at your link, thank you!

Mark, what a wonderful tour of action code and export codes. It is a model of clarity and a very generous contribution to the community. Thank you. David.

I hope @mwra will allow me to take this one step further, as I have been unsuccessful in modifying the above to slightly different needs.

I have a prototype for “Keywords”. For each note using this prototype, I want the note’s text to equal a list of the $Name and $ID of all other notes in which the name of this note exists a ‘set’ attribute (like $Keywords) of those notes.

For example, I have a note named ‘banana’. The text of this note should list the name and ID of all notes in which “banana” is a term found in their $Keywords attribute.

The code provided in the thread above produces a single note listing all the keywords in the document, and, under each keyword, the name of all notes in which they occur. What I’d like is just a list of those notes in which the name of the present note occurs as a keyword.

Thus far, I’ve managed:

$MySet=$Name(find($Keywords.icontains($Name(current))));
$Text=format($MySet,"\n");

But I haven’t been able to figure out how to get both the $Name and $ID for each such note. Any help would be appreciated.

Can’t actually test code [sic] right now but…

I’ll assume $MySet is returning the right matches. We’ll change the code slightly so $MySet gets a list of $paths for those notes. Now:

$MyList=;
$MySet=find($Keywords.icontains($Name(that))&$IsAlias==false);
$MySet.sort.each(aValue){
   $MyList = $MyList + ($Name(aValue) + " " + $ID(aValue));
}
$MySet=;

So, first we reset $MyList for the current note as we want a fresh listing. Then, we collect all the $Path values where the current note’s $Name is a $Keyword value in that note. As find() doesn’t de-dupe like a query, the $IsAlias test ensures we only match original notes.

Next, we sort the set and iterate using .each(). Each aValue is actually the path to a note so we collect the $Name and $ID of the note at that path, joined with a space (you might want to edit that separator to something else). We put parentheses around that task so it is a single literal string by the time we add it to $MyList where it becomes a new value.

Sorry I can’t test code just now but i hope that works. Note the use the need to use the that designator in the query so we use the value of the note calling the find(). current is testing the note currently being tested by the find() - so will return everything as every note will match itself!

OK, I ran the above as an edict in your file keyword-test2edV2.tbx. I added a note called ‘banana’, ran the edict, and $MyList now shows:

note4 1553467228;note5 1553497378

So, I think it works.

I can confirm it does. And I’ve learnt a bit more about the code needed to do it. Thank you again, Mark.

1 Like