Question about implementing a regex comparison in AppleScript?

Hi there,

with the very kind help of the members here I succeeded to build a script, that saves every incoming Mail message as .eml file.

In Apple Mail I built a rule to target the script to special mails.

But…

I’d like to be more specific than what’s possible with Apple mail rules.
In the beginning of the script I’d like to check wether the subject of the mail matches a certain pattern. And for the pattern I have built a regex expression.

Question:
(how) is it possible to check at the beginning of the script if the subject matches the regex - if positive perform the script or otherwise quit the script?

The regex expression identifies mail subjects that look like:
ubfg-210312-gg-proposal for contract

The expression is: (x|(w-))?[^-]…-\d{6,6}-\w\w-.{0,}

The script is:



global theFile


using terms from application "Mail"
	on perform mail action with messages these_messages for rule this_rule
		tell application "Mail"
			repeat with eachMessage in these_messages
				
				set ptd to alias "Macintosh HD:Users:xy:Documents:mail_deposit:"
				set theText to source of eachMessage
				set theSubject to (subject of eachMessage) -- as string
				if theSubject is "" then set theSubject to "no subject"
				if theSubject contains ":" then ¬
					set theSubject to my snr(theSubject, ":", "_")
				set theFile to ((ptd & theSubject) & ".eml") as string
				
				-- display dialog "File" & (theFile as string)
				
				
				my resolveConflict(ptd, theSubject)
				set theFileID to open for access file theFile with write permission
				write theText to theFileID
				close access theFileID
				
			end repeat
			-- display dialog "There were " & length of these_messages & " messages saved."
			
		end tell
		
	end perform mail action with messages
end using terms from

on resolveConflict(ptd, theSubject)
	tell application "Finder"
		set theCounter to 1
		set noConflict to false
		repeat until noConflict
			
			
			if exists file (theFile) then
				
				set theFile to (((ptd & theSubject) & " -" & theCounter as string) & ".eml")
				set theCounter to theCounter + 1
				
			else
				set noConflict to true
				
			end if
		end repeat
	end tell
end resolveConflict

on snr(theString, searchString, replaceString)
	tell (a reference to my text item delimiters)
		set {oldTID, contents} to {contents, searchString}
		set {theString, contents} to {theString's text items, replaceString}
		set {theString, contents} to {theString as Unicode text, oldTID}
	end tell
	return theString
end snr


Hi,

Test yourself. I edited your resolveConflict handler as well. And, I removed your snr handler, because your subjects doesn’t contain “:” symbol. Script:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property NSString : a reference to current application's NSString
property NSRegularExpressionSearch : a reference to current application's NSRegularExpressionSearch
property ptd : ((path to documents folder) as text) & "mail_deposit:"
property theRegex : "(x|(w-))?[^-]...-\\d{6,6}-\\w\\w-.{0,}"

using terms from application "Mail"
	on perform mail action with messages these_messages for rule this_rule
		tell application "Mail"
			repeat with eachMessage in these_messages
				tell eachMessage to set theSubject to its subject
				-- CHECK matchinng to Regex
				set theNSString to (NSString's stringWithString:theSubject)
				set subject_MatchesToRegex to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)'s |length|() > 0
				-- EXPORT as EML file if subject matches to regex
				if subject_MatchesToRegex then
					tell eachMessage to set theText to its source
					set theFile to my resolveConflict(ptd, theSubject)
					set theFileID to open for access file theFile with write permission
					write theText to theFileID
					close access theFileID
				end if
			end repeat
		end tell
	end perform mail action with messages
end using terms from

on resolveConflict(ptd, theSubject)
	tell application "Finder"
		set theCounter to 1
		repeat
			set theFile to ptd & theSubject & " -" & theCounter & ".eml"
			if not (exists file theFile) then return theFile
			set theCounter to theCounter + 1
		end repeat
	end tell
end resolveConflict

Thank you KniazidisR, the code looks beautiful.
Strangely I didn’t get it to work in my environment. Maybe I made some mistake while transferring it?
I added 2 dialogs for debugging but I couldn’t identify the problem.

When the Script gets triggered I get the first dialog telling me “Start repeat loop.” but then the script seems to stop and the second dialog “Begin check matching.” does no appear at all.

Another thing: is there a way to display the result of the check for debugging issues. Just for the case if I made some mistake with my regex expression. I already tested it at https://regex101.com/ but maybe I made some mistake by transferring it to AppleScript.


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property NSString : a reference to current application's NSString
property NSRegularExpressionSearch : a reference to current application's NSRegularExpressionSearch
property ptd : ((path to documents folder) as text) & "mail_deposit:"
property theRegex : "(x|(w-))?[^-]...-\\d{6,6}-\\w\\w-.{0,}"

using terms from application "Mail"
	on perform mail action with messages these_messages for rule this_rule
		tell application "Mail"
			repeat with eachMessage in these_messages
				display dialog "Start repeat loop."
				tell eachMessage to set theSubject to its subject
				display dialog "Begin check matching."			
				-- CHECK matchinng to Regex
				set theNSString to (NSString's stringWithString:theSubject)				
				set subject_MatchesToRegex to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)'s |length|() > 0
				-- EXPORT as EML file if subject matches to regex
				
				if subject_MatchesToRegex then
					display dialog "Subject matches."
					
					tell eachMessage to set theText to its source
					set theFile to my resolveConflict(ptd, theSubject)
					set theFileID to open for access file theFile with write permission
					write theText to theFileID
					close access theFileID
				end if
			end repeat
		end tell
	end perform mail action with messages
end using terms from

on resolveConflict(ptd, theSubject)
	set theFile to ptd & theSubject & ".eml"
	set theCounter to 0
	repeat
		try
			alias theFile
			return theFile
		on error
			set theCounter to theCounter + 1
			set theFile to ptd & theSubject & " -" & theCounter & ".eml"
		end try
	end repeat
end resolveConflict


Display dialog is modal and waits response from you. And may be hidden on the background. Try to put instead display notification for debugging. For example:


display notification "Start repeat loop." giving up after 2 -- seconds
delay 3

Or, put after tell application “Mail” following, to bring Mail’s window and display dialogs to front:

activate

To debug result of check, you can use:


if subject_MatchesToRegex then display notification "Subject Matches To Regex" giving up after 2
delay 3

I add a delay of 3 seconds (which should be slightly longer than the notification’s self-disappearance time), because if the script is too fast, the notifications may overlap. And we need clarity when debugging.

Now,
here, I check the subject provided by you in the post #1, and script returns true:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property NSString : a reference to current application's NSString
property NSRegularExpressionSearch : a reference to current application's NSRegularExpressionSearch
property theRegex : "(x|(w-))?[^-]...-\\d{6,6}-\\w\\w-.{0,}"

set theSubject to "ubfg-210312-gg-proposal for contract"
set theNSString to (NSString's stringWithString:theSubject)
set subject_MatchesToRegex to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)'s |length|() > 0
--> true

Thank you again for your input.

Unfortunately I’m completely lost and clueless.

First of all I tried to implement the notification alerts but it only get’s me an error telling me something like ‘end of line expected but identifier found’. Maybe it’s because I have BigSur with AppleScript 2.7. If I replace the word ‘notification’ by ‘dialog’ there is no error, but then I have a dialog again.

Next I tried ‘activate’ but this doesn’t reveal any hidden dialog boxes.

And then I tried all kinds of modifications to get the script working, but nothing worked.
So I tried again the script version with the two dialogs but still the same.
It shows the dialog “Start repeat loop.” but then it seem’s to stop.

The next dialog “Begin check matching.” right after the line “tell eachMessage to set theSubject to its subject” does not appear and the small turning gear icon in the menue stops and then disappears.

Do you have any idea if maybe the other AppleScript version is causing this problem?

Sorry for bothering you.

First, my resolveConlict handler contained an error. I fixed it now.

Secondly, the script is easier to debug not as a mail rule, but as a regular script. To do this, just comment out the line on/end perform mail actions and use (get selection) instead of these_messages.

I tested the following script, edited as regular script and it works. It is easy for you to turn this script to mail rule. If it does not work in the form of a rule, then the reason will no longer be in the script, but in the Mail’s bug with mail rules. So, debug version script:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property NSString : a reference to current application's NSString
property NSRegularExpressionSearch : a reference to current application's NSRegularExpressionSearch
property ptd : ((path to documents folder) as text) & "mail_deposit:"
property theRegex : "(x|(w-))?[^-]...-\\d{6,6}-\\w\\w-.{0,}"

using terms from application "Mail"
	-- on perform mail action with messages these_messages for rule this_rule (COMMENTED)
	tell application "Mail"
		repeat with eachMessage in (get selection) -- these_messages (EDITED)
			
			tell eachMessage to set theSubject to its subject
			-- CHECK matchinng to Regex
			set theNSString to (NSString's stringWithString:theSubject)
			set subject_MatchesToRegex to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)'s |length|() > 0
			-- EXPORT as EML file if subject matches to regex
			
			if subject_MatchesToRegex then
				tell eachMessage to set theText to (its source) as rich text
				set theFile to my resolveConflict(ptd, theSubject)
				set theFileID to open for access file theFile with write permission
				write theText to theFileID
				close access theFileID
			end if
			
		end repeat
	end tell
	-- end perform mail action with messages (COMMENTED)
end using terms from


on resolveConflict(ptd, theSubject)
	tell application "Finder"
		set theCounter to 1
		repeat
			set theFile to ptd & theSubject & " -" & theCounter & ".eml"
			if not (exists file theFile) then return theFile
			set theCounter to theCounter + 1
		end repeat
	end tell
end resolveConflict

Thank you for everything, KniazidisR!

The script is now working perfectly. As a ‘normal’ script and as a script attached to a mail rule as well.

Very good hint to test it as a ‘normal’ script for debugging.
This will sure help me in the future although in this case no debugging was needed because it all worked well without any adjustments.

Cheers
Carl

Just curious…
Is there a way to extract the string, that is found by the regex search?

In this actual case it might not make sense. But if the subject only ‘contains’ a string that matches the regex expression it might make sense to be able to work with the string.

For example: if the subject of the mail was…

Re.: Re.: Re.: ubfg-210312-gg-proposal for contract

… it might help to extract ‘ubfg-210312-gg-proposal for contract’

I tried this by:


set extraktString to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)
			display dialog "extraktString " & (extraktString as rich text)

But this was obviously a fail…

Cool, thank you very much. I’m gonna try

Sorry - I was a little bit too optimistic about my capabilities to figure this out.
I tried for quite a while but failed.

Do you have another hint for me?

Thanks
Carl

rangeOfString:options: returns an NSRange, a struct with a location and a length, you have to extract the string with subStringWithRange


set extraktRange to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)
tell current application to set extraktString to (theNSString's subStringWithRange:extraktRange) as text
display dialog "extraktString " & extraktString

The tell current application expression avoids the terminology clash text <–> rich text

Hi Stefan,

thank you very much for your help.
When I try to implement it, I get the following message:

error “-[__NSCFString subStringWithRange:]: unrecognized selector sent to instance 0x600000227c00” number -10000

Can’t figure out what the problem is… do you have an idea maybe?

Cheers
Carl

subStringWithRange should be substringWithRange:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
property NSString : a reference to current application's NSString
property NSRegularExpressionSearch : a reference to current application's NSRegularExpressionSearch
property ptd : ((path to documents folder) as text) & "mail_deposit:"
property theRegex : "(x|(w-))?[^-]...-\\d{6,6}-\\w\\w-.{0,}"

set theNSString to (NSString's stringWithString:"ubfg-210312-gg-proposal for contract")

set extraktRange to (theNSString's rangeOfString:theRegex options:NSRegularExpressionSearch)
set extraktString to (theNSString's substringWithRange:extraktRange) as string -- EDITED
display dialog "extraktString:    \"" & extraktString & "\""

Great. Once again… many thanks to you and Stefan
I would have never figured out the problem by myself.

Cheers
Carl

You could also use NSPredicate Matches comparison which can take a Reg-Ex string.

At the very start of your script You then could filter your whole array of messages and end up
With an array of messages that pass your filter.

https://nshipster.com/nspredicate/