Smart Rule to Search and Replace Markdown Contents

Yep, but it won’t replace existing links. So the script might still be useful.

1 Like

Important point. I index Obsidian folders in DEVONthink but if I have to edit an indexed Obsidian note, I use Obsidian syntax, knowing it might not work in DEVONthink.

@pete31, you are fast becoming my favorite person! Thank you so much for your help with this script and the previous one! Has made my life so much easier. All my images that originally used Obsidian’s syntax have now been updated using this script and are now showing across all external editors, including DEVONthink.

@Bluefrog, regarding the reason, Obsidian can read both syntax formats (conventional and their own), and as @lsievert correctly pointed out, the app does have an option to select [[wiki-links]] or markdown syntax… but it’s a one or the other deal. I use [[wiki-links]] to link notes and make connections, and I use ![](image.png) to insert images. Because of the current limitations of Obsidian (still in beta), I needed a work around. Thankfully DEVONthink and this wonderful community just keeps on giving :grin:

2 Likes

@pete31 Thank you for the script you provided in the post above.

I’ve adapted it to search for a specific keyword in the markdown file’s contents, and replace it with an empty string.

I use the keyword to automatically move notes indexed from TheBrain into specific DevonThink folders, but it is no longer needed once that smart rule has been triggered. So it would be awesome if the keyword could be somehow removed automatically.

Can you perhaps give a hint on how to make this script smart rule compatible? Any input is on this is much appreciated.

1 Like

It’s not possible to use AppleScriptObjC embedded in Smart Rule scripts.

So you need to move

  • the use statements (found at the top)
  • the handler (found at the bottom)

out of the script into a Script Library, i.e. a new scpt file that will live in/Users/USERNAME/Library/Script Libraries/.

See this post to see how a Script Library is called from another script.

1 Like

Great, thanks.

I’ve saved the script library to the location specified in your linked post. Then I used the instructions in that post to try to integrate the code from the script above into the general Smart Rule code.

I’m a total beginner in AppleScript, so I think there must be some kind of syntax error or something. In any case, when it is run the script returns the error message «script» doesn’t understand the “replacekeyword” message.

This is the smart rule script I came up with:

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with theRecord in theRecords
				set theText to plain text of theRecord as string
				
				if theText ≠ "" then
					set newText to my replacekeyword(theText)
					if newText ≠ theText and newText ≠ "" and newText ≠ "missing value" then set plain text of thisRecord to newText
				end if
			end repeat
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			
		end try
	end tell
end performSmartRule

And this is the code in the script library, which is named “Script Library - replacekeyword.scpt”

-- Script Library - replacekeyword

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


on replacekeyword(theText)
	try
		set ca to a reference to current application
		set theString to ca's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:("DT_Inbox") withString:("A") options:(ca's NSRegularExpressionSearch) range:{location:0, |length|:length of theText}
		set newText to newString as string
	on error error_message number error_number
		error number -128
	end try
end replacekeyword

Is there anything obvious here that needs to be fixed?

Yes! You did

  • remove the use statements
  • create a Script Library
  • create a Smart Rule script

That’s good :slight_smile:

But you also need to

  • remove the handler
    (as it can’t be used embedded in a Smart Rule script. That’s the only reason why it’s necessary to use a Script Library: to be able to remove it from the Smart Rule script)

  • call the Script Library from the Smart Rule
    (which means replacing my with script "[name of the Script Library]"'s.
    my is only used to call an embedded handler.)

Ah, I see your edit, so you’ve found the first mistake. Replace my and it should work.


If you use an external Smart Rule script: remember to restart DEVONthink as scripts are cached

(or see Script: Comfortably developing a Smart Rule Script)

1 Like

Feels like I’m getting close! Really appreciate your guidance here.

Something still isn’t 100% right though. Now there’s the following error message when using the code below: “Can’t make «script “ScriptLibraryReplacekeyword”» into type Unicode text.”

Maybe I misunderstood how to replace the “my” part in some way due to my current ignorance regarding general AppleScript syntax? :innocent:

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with theRecord in theRecords
				set theText to plain text of theRecord as string
				
				if theText ≠ "" then
					set newText to script "ScriptLibraryReplacekeyword.scpt"
					if newText ≠ theText and newText ≠ "" and newText ≠ "missing value" then set plain text of theRecord to newText
				end if
			end repeat
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			
		end try
	end tell
end performSmartRule

Please compare this line to the line that calls a Script Library in the script I‘ve linked.

Do you see the difference?

Success! It’s working beautifully now!

Pete, this has been really helpful. Thanks again, also for pushing me a bit to find the solution on my own - that’s how one learns, right? :grinning:

Here are the scripts, in case someone wants to use them.

Working smart rule script:

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with theRecord in theRecords
				set theText to plain text of theRecord as string
				
				if theText ≠ "" then
					set newText to script "ScriptLibraryReplacekeyword"'s replacekeyword(theText, "DT_Inbox")
					if newText ≠ theText and newText ≠ "" and newText ≠ "missing value" then set plain text of theRecord to newText
				end if
			end repeat
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			
		end try
	end tell
end performSmartRule

And this is the Script Library:

-- Script Library - replacekeyword

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


on replacekeyword(theText)
	try
		set ca to a reference to current application
		set theString to ca's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:("DT_Inbox") withString:("") options:(ca's NSRegularExpressionSearch) range:{location:0, |length|:length of theText}
		set newText to newString as string
	on error error_message number error_number
		error number -128
	end try
end replacekeyword

Are you sure the scripts as posted work?

In the script you‘re calling a Script Library‘s handler with 2 parameters:

But the handler in the Script Library expects only 1 parameter, I think:

Hmm, interesting. On my end, the script is 100% confirmed working, and I’ve also created further versions with different keywords that also behave as they’re supposed to.

So would you suggest modifying that line of code, e.g. like this?

set newText to script "ScriptLibraryReplacekeyword"'s 

You’re right, tested it now.

Interesting. I thought it’s necessary to always have the same count of parameters in both, the code that calls the handler and in the handler itself. Seems I’ve never encountered a situation where the calling code got more parameters than the handler expects.

However, it’s not possible the other way around: calling a handler with less parameters that the handler expects throws an error (that’s what I get quite often).

That’s not necessary. Use a variable in the handler (instead of your hard coded keywords), then call the handler with different keywords for this parameter.

Doesn’t make much sense to create a new handler for each keyword. You probably did it this way because the handler that you’ve used as template did not use this common mechanism (no idea why I did it that way).

Better don’t start doing it the wrong way. Best is to make handlers and especially Script Libraries usable for different tasks right from the start. I did some silly things in the past, it’s really no fun to make subsequent changes in a bunch of different versions. It’s not only unbelievable cumbersome but also very very error prone. Did I say that it’s error prone?

Be aware that the handler uses regex which means you have to take care of not accidentally using regex syntax. Otherwise you’ll get interesting results …

Here’s a handler that can be used variably

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

on regexReplace(theText, thePattern, theRepacementText)
	try
		set theString to current application's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:(thePattern) withString:(theRepacementText) options:(current application's NSRegularExpressionSearch) range:{location:0, |length|:length of theText}
		set newText to newString as string
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"regexReplace\"" message error_message as warning
		error number -128
	end try
end regexReplace

However, if all you need is to find and replace text (i.e. you don’t need a regex pattern) then everything would be far easier. You could find a lot of find and replace handlers on the forum (if not I could post one). They all can be used directly in a Smart Rule, no need to use a Script Library.

PS You might want to read this post.

Best is to make handlers and especially Script Libraries usable for different tasks right from the start. I did some silly things in the past, it’s really no fun to make subsequent changes in a bunch of different versions. It’s not only unbelievable cumbersome but also very very error prone.

That makes a lot of sense. As I said, for now I’m still a total beginner in AppleScript, so the goal was a “quick-and-dirty” version of the script that addresses my specific use case. And I’m really glad that this has worked out with your help (and based on the scripts you came up with).

There may be further use cases (for myself and others), where RegEx is needed so I would like to stick to this more complicated version and get it right.

I’ve tried to tinker with the SmartRule script to get it to work with the universal script library you provided, but couldn’t make it work. Would you be so kind to provide an example for e.g. the keyword DT_Inbox (or maybe a Regex pattern)?

Thanks for the link to your post on how to learn AppleScript. I will follow those general recommendations.

That’s true for many programming languages: the subroutine/function expects a certain number of parameter to be passed and stops looking when it received them. So passing more then the required number is mostly ok.
On the other hand, passing less then the required number very often causes havoc: the function/subroutine accesses an uninitialized area of the stack and crashes (which is good) or it finds some bogus value and works with that. This is really bad.
Some programming languages even allow for subroutines/functions with a variable number of parameters, eg C.

After using the script for a while without any regular expressions to replace text strings in MD contents, I am currently experimenting with regular expressions and running into an issue:

Whenever I use a \n character in the embedded smart rule script and then close the script editing window, a new line has been inserted while the “\n” characters have disappeared.

I thought it might have something to do with how DevonThink parses apple script, but the same actually happens in Script Editor.

Does anyone have an idea what might be going on and how to fix this?

In what context do you use the \n sequence? And did it help to write \\n instead?

Using two forward slashes instead of one seems to have solved the issue. Thanks, I never would have guessed.

I’m using RegEx to remove the following from certain markdown documents, as well as the new line following it:

[//]: # [TheBrain Thought Link](brain://api.thebrain.com/hUZzP6CPhkWLdyC_w1559g/QsGIrs42GlebTIulXYs30A/ThisIsTheTitle)

And this is the expression that is now working as expected:

.*\\)\\n

Hopefully, they were back slashes :slight_smile:

The RE should work just fine without the new line at the end. If you want to rejoice all occurrences of these links, that is. If you really only want to delete those at the end of a line, the \n is necessary.

As to the „why do I need two \ here“: as you’ve already noticed, \n is a special sequence in (probably) NSString that’s converted to a newline before the regex method even sees it. \\ is converted to a single \ which then in combination with the n reaches the RE method. Or something like that.

Of course, using JavaScript and its built in RE support would cause less hassle.

1 Like

Yes, indeed they were :innocent:

In my testing, adding the \n expression at the end and then leaving the replacement term empty not only deletes the link but also the line following the link in all occurrences (there is always a new line afterwards). This is the desired behavior in my use case.

Really interesting - I get it now.

1 Like