Script: Add tag alias without punctuation

This script adds an alias without punctuation to every tag of the current database that contains punctuation.

If you use DEVONthink’s external Smart Rule script “Assign Tags” with tags as shown in capture one then no tags are added:

.

If you add an alias without punctuation then DEVONthink’s external Smart Rule script “Assign Tags” assigns the tag “Hello, world”:

Setup:

  • By default all punctuation is removed.

  • If you want to restrict removal of punctuation set property theIgnoredPunctuation to e.g. "," .

    • Don’t include a space, i.e. don’t set ", " (or ", ;" ). This is correct: "," (or ",;" ).
-- Add alias without punctuation to every tag of the current database

-- Setup, optional:  	By default all punctuation is removed. If you want to restrict removal of punctuation set property theIgnoredPunctuation to e.g. ",".
--						Don't include a space " " in property theIgnoredPunctuation. Don't do this: ", " (or ", ;"). This is correct: "," (or ",;").

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property theIgnoredPunctuation : "" -- Add punctuation that should be removed. Don't include a space, i.e. don't set ", " (or ", ;"). This is correct: "," (or ",;"). Set to an empty string "" to remove all punctuation.
property theAliasesDelimiter : "," -- Set your preferred delimiter, either "," or ";". Don't include a space, i.e. don't set ", " (or "; "). This is correct: "," (or ";").

-----------

if theIgnoredPunctuation ≠ "" then
	set theCharacterSet to current application's NSMutableCharacterSet's alloc()'s init()
	theCharacterSet's addCharactersInString:(theIgnoredPunctuation)
else
	set theCharacterSet to current application's NSCharacterSet's punctuationCharacterSet()
end if

tell application id "DNtp"
	try
		set theTags to tag groups of current database
		show progress indicator "Adding Aliases... " steps (count theTags) as string with cancel button
		
		repeat with thisTag in theTags
			set thisTag_Name to name of thisTag
			step progress indicator thisTag_Name
			set thisTag_Name_withoutPuncation to my stripPunctuation(thisTag_Name, theCharacterSet)
			if thisTag_Name_withoutPuncation ≠ thisTag_Name then
				set thisTag_Aliases to aliases of thisTag
				set thisTag_Aliases_list to my tid(thisTag_Aliases, {", ", "; ", ",", ";"})
				if thisTag_Name_withoutPuncation is not in thisTag_Aliases_list then
					set thisTag_Aliases_list_new to thisTag_Aliases_list & {thisTag_Name_withoutPuncation}
					set thisTag_Aliases_new to my tid(thisTag_Aliases_list_new, theAliasesDelimiter)
					set aliases of thisTag to thisTag_Aliases_new
				end if
			end if
		end repeat
		
		hide progress indicator
	on error error_message number error_number
		hide progress indicator
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on stripPunctuation(theText, theCharacterSet)
	try
		set theString to (current application's NSString's stringWithString:theText)
		set theArray to theString's componentsSeparatedByCharactersInSet:(theCharacterSet)
		set theString_withoutPunctuation to theArray's componentsJoinedByString:""
		return theString_withoutPunctuation as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"stripPunctuation\"" message error_message as warning
		error number -128
	end try
end stripPunctuation

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

wow incredible! thank you!

1 Like

I just stumbled over this script - it’s really useful!

To automate even further, I modified the script slightly to work in smart rules.


-- Add alias without punctuation to every tag of the current database

-- Setup, optional:  	By default all punctuation is removed. If you want to restrict removal of punctuation set property theIgnoredPunctuation to e.g. ",".
--						Don't include a space " " in property theIgnoredPunctuation. Don't do this: ", " (or ", ;"). This is correct: "," (or ",;").

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property theIgnoredPunctuation : "" -- Add punctuation that should be removed. Don't include a space, i.e. don't set ", " (or ", ;"). This is correct: "," (or ",;"). Set to an empty string "" to remove all punctuation.
property theAliasesDelimiter : ";" -- Set your preferred delimiter, either "," or ";". Don't include a space, i.e. don't set ", " (or "; "). This is correct: "," (or ";").

-----------
on performSmartRule(theRecords)
	
	if theIgnoredPunctuation ≠ "" then
		set theCharacterSet to current application's NSMutableCharacterSet's alloc()'s init()
		theCharacterSet's addCharactersInString:(theIgnoredPunctuation)
	else
		set theCharacterSet to current application's NSCharacterSet's punctuationCharacterSet()
	end if
	
	tell application id "DNtp"
		
		try
			set theTags to tag groups of current database
			show progress indicator "Adding Aliases... " steps (count theTags) as string with cancel button
			
			repeat with thisTag in theTags
				set thisTag_Name to name of thisTag
				step progress indicator thisTag_Name
				set thisTag_Name_withoutPuncation to my stripPunctuation(thisTag_Name, theCharacterSet)
				if thisTag_Name_withoutPuncation ≠ thisTag_Name then
					set thisTag_Aliases to aliases of thisTag
					set thisTag_Aliases_list to my tid(thisTag_Aliases, {", ", "; ", ",", ";"})
					if thisTag_Name_withoutPuncation is not in thisTag_Aliases_list then
						set thisTag_Aliases_list_new to thisTag_Aliases_list & {thisTag_Name_withoutPuncation}
						set thisTag_Aliases_new to my tid(thisTag_Aliases_list_new, theAliasesDelimiter)
						set aliases of thisTag to thisTag_Aliases_new
					end if
				end if
			end repeat
			
			
			hide progress indicator
		on error error_message number error_number
			hide progress indicator
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			return
		end try
	end tell
	
	
end performSmartRule


on stripPunctuation(theText, theCharacterSet)
	try
		set theString to (current application's NSString's stringWithString:theText)
		set theArray to theString's componentsSeparatedByCharactersInSet:(theCharacterSet)
		set theString_withoutPunctuation to theArray's componentsJoinedByString:""
		return theString_withoutPunctuation as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"stripPunctuation\"" message error_message as warning
		error number -128
	end try
end stripPunctuation

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

Anyone wanting to try the smart rule version I posted above:

Please note that just as the original script (which does this intentionally), the smart rule script always applies itself to all group tags in the database. I tried to modify the smart rule version to respect user-defined criteria, but later noticed that attempt was unsuccessful (hence this edit :wink:).

@pete31: Would you maybe be able to help out here or give a hint?

@AW2307 sorry for the delay (still setting up a new mac which turns out to be far more work than ever imagined).

Not sure: what’s the question.?

Hey @pete31, thanks for getting back to me.

The issue where your input would be much appreciated is the following: I can’t get the smart rule script to only apply itself to the scope set in the smart rule.

So let’s say I don’t want any tag aliases without punctuation to be added for tags that begin with “_”.

I set the scope of the smart rule to Kind is tags and Name does not begin with "_ ". However, the script will still process those tags that begin with a “_” character.

Similarly, if I set Kind to “is Group Tag” in the smart rule, the script will still apply itself to regular tags as well.

You are not using the Smart Rule script’s input (theRecords) inside the script.

My original script contains this line

set theTags to tag groups of current database

and theTags is then used in the repeat (repeat with thisTag in theTags).

So if you want to process the Smart Rule script input you need to change theTags to theRecords.

Great, that makes a lot of sense.

I implemented the changes in the script but for some reason the smart rule criteria is still not being applied…

I think it might have something to do with this line here:

set theRecords to tag groups of current database

Sounds to me as if this might override any criteria set in the smart rule, but it’s just an intuition that could be completely off.

Here’s the updated script, maybe you can see what is wrong with it.

-- Add alias without punctuation to every tag of the current database

-- Setup, optional:  	By default all punctuation is removed. If you want to restrict removal of punctuation set property theIgnoredPunctuation to e.g. ",".
--						Don't include a space " " in property theIgnoredPunctuation. Don't do this: ", " (or ", ;"). This is correct: "," (or ",;").

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property theIgnoredPunctuation : "" -- Add punctuation that should be removed. Don't include a space, i.e. don't set ", " (or ", ;"). This is correct: "," (or ",;"). Set to an empty string "" to remove all punctuation.
property theAliasesDelimiter : ";" -- Set your preferred delimiter, either "," or ";". Don't include a space, i.e. don't set ", " (or "; "). This is correct: "," (or ";").

-----------
on performSmartRule(theRecords)
	
	if theIgnoredPunctuation ≠ "" then
		set theCharacterSet to current application's NSMutableCharacterSet's alloc()'s init()
		theCharacterSet's addCharactersInString:(theIgnoredPunctuation)
	else
		set theCharacterSet to current application's NSCharacterSet's punctuationCharacterSet()
	end if
	
	tell application id "DNtp"
		
		try
			set theRecords to tag groups of current database
			show progress indicator "Adding Aliases... " steps (count theRecords) as string with cancel button
			
			repeat with thisRecord in theRecords
				set thisRecord_Name to name of thisRecord
				step progress indicator thisRecord_Name
				set thisRecord_Name_withoutPuncation to my stripPunctuation(thisRecord_Name, theCharacterSet)
				if thisRecord_Name_withoutPuncation ≠ thisRecord_Name then
					set thisRecord_Aliases to aliases of thisRecord
					set thisRecord_Aliases_list to my tid(thisRecord_Aliases, {", ", "; ", ",", ";"})
					if thisRecord_Name_withoutPuncation is not in thisRecord_Aliases_list then
						set thisRecord_Aliases_list_new to thisRecord_Aliases_list & {thisRecord_Name_withoutPuncation}
						set thisRecord_Aliases_new to my tid(thisRecord_Aliases_list_new, theAliasesDelimiter)
						set aliases of thisRecord to thisRecord_Aliases_new
					end if
				end if
			end repeat
			
			
			hide progress indicator
		on error error_message number error_number
			hide progress indicator
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			return
		end try
	end tell
	
	
end performSmartRule


on stripPunctuation(theText, theCharacterSet)
	try
		set theString to (current application's NSString's stringWithString:theText)
		set theArray to theString's componentsSeparatedByCharactersInSet:(theCharacterSet)
		set theString_withoutPunctuation to theArray's componentsJoinedByString:""
		return theString_withoutPunctuation as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"stripPunctuation\"" message error_message as warning
		error number -128
	end try
end stripPunctuation

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

You want to process the input (i.e. the matched records) of your Smart Rule, so you need to use this input (and not tag groups of current database).

Currently you’re just running a script via a Smart Rule - there’s currently simply no connection between the Smart Rule’s input and the script.

What you need to do is:

  • define Smart Rule critera that match the tags you want to process
  • use this input via theRecords
    (that’s the parameter, i.e. input, in on performSmartRule(theRecords))
  • remove line set theTags to tag groups of current database
  • use theRecords for the repeat
2 Likes

Shouldn’t that be set theRecords to … ?

I used the line from his first try (and my original script), yours is the line from the second try.

Yep. And that’s where they are redefining theRecords, which makes the whole thingy useless. And since we’re at it, I took the liberty to rewrite the whole thing in JavaScript (not tested because I don’t have tags with punctuation). I find it a bit clearer (being biased), and it is definitely a lot shorter.

Note the usage of a regular expression for the ignoredProperty constant. Seems a cleaner to me than calling an empty string theIgnoredPunctuation which is then in fact not used but rather populated with something else. One could of course modify the AppleScript version to use REs as well.

Edit: I re-factored it to run as a standalone function. Don’t see the point using it in a smart rule – it is changing all the tags for a database, not properties of normal records.

(() => {
/* Add alias without punctuation to every tag of the current database */
/* Define regular expression for punctuation to remove. Default is Unicode Punctuation character class. To remove only commas and semicolons, use '/[,;]/g' (withouth the quotes). In general, use '/[abc]/g' to remove all occurences of the characters a, b, and c.*/
const ignoredPunctuation = /\p{P}/ug;

/* Define the delimiter used in the alias. Define only a _single_ character here, i.e. do _not_ use ', ' or '; ' */
const aliasDelimiter = ';'; 

  const app = Application("DEVONthink 3");
  const tags = app.currentDatabase.tagGroup.children();
  /* Add progress indicator initialisation here if needed */
  tags.forEach(t => {
	/* increase progress indicator here if needed */
	const name = t.name();
	const cleansedName = t.name().replace(ignoredPunctuation,"");
	if (cleansedName != name) {
    /* Get the aliases of this record as a string, 
        split them at commas and semicolons (possibly followed by a space), 
        and stuff them into the array cleansedAliases. Filter out empty string(s) */
	  const cleansedAliases = t.aliases().split(/[,;] ?/).filter( o => o);
	  /* If the current tag, with all punctuation removed, 
         is _not_ in the list of aliases, add it to this list */
	  if (cleansedAliases.indexOf(cleansedName) === -1) {
		cleansedAliases.push(cleansedName);
		t.aliases = cleansedAliases.join(aliasDelimiter);
	  }
	} /* if cleansedName != name */
  }) /* tags.forEach */
})()
  /* Remove progress indicator if added previously */
1 Like

Thank you @pete31 and @chrillek for the additional input.

I will experiment further on this basis.

Please come back to this thread if you encounter problems.

1 Like

Did you make it work the way you want?

Honestly, no. In my attempts, either the script continued to ignore the smart rule scope or it wasn’t valid due to syntax error so it couldn’t run at all.

However, I found a way to adapt my workflows such that there is no longer a requirement for the script to process only a specific smart rule scope. Based on these adjustments, the original smart rule script works fine as-is for my use case.