Are these as easy to use as JavaScript Regular Expressions?
Ease is in the eye of the beholder, but they’re not complex. A replace operation is simple:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
set aString to current application's NSString's stringWithString:"It costs $4.50 each way"
set aString to (aString's stringByReplacingOccurrencesOfString:"\\$([0-9,.]+)" withString:"€$1" options:(current application's NSRegularExpressionSearch) range:{0, aString's |length|()}) as text
Most of the work is going to and from NSStrings.
Finds are a bit more complex in that the results are returned as ranges, which you may then need to use to extract substrings:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
set aString to current application's NSString's stringWithString:"It costs $4.50 each way"
set theRange to aString's rangeOfString:"(?<=\\$)[0-9,.]+" options:(current application's NSRegularExpressionSearch)
set theResult to (aString's substringWithRange:theRange) as text
Or if there could be multiple matches:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
set aString to current application's NSString's stringWithString:"It costs $4.50 each way"
set {theExpr, theError} to current application's NSRegularExpression's regularExpressionWithPattern:"\\$([0-9,.]+)" options:0 |error|:(reference)
set theMatches to theExpr's matchesInString:aString options:0 range:{0, aString's |length|()}
set theResults to {}
repeat with aMatch in theMatches
set theRange to (aMatch's rangeAtIndex:1) -- ie, $1
set end of theResults to (aString's substringWithRange:theRange) as text
end repeat
It’s the sort of thing you can put in a library and forget.
JXA is Javascript Script Library? I tried this, but a you can only call a JavaScript script library from a JavaScript-script. It seems you can’t mix languages…
Hmm, looks like a lot of boilerplate.
A run shell script
with awk
or something will probably also do the trick.
It depends a bit on your priorities – shorter code or quicker execution.
No, “JXA” is JavaScript for Automation, which uses JavaScript as its core language.
Just like with AppleScript, any JXA script file with handlers (functions) can be a script library if you place it in one of the locations Apple has established.
See:
As I mentioned above, I have found through limited testing that you can use an AppleScript Script Library with JXA script, but not the other way around.
Shane Stanley:
Think NSRegularExpression.
Jim Underwood:
Are these as easy to use as JavaScript Regular Expressions?
This brings to mind an observation made by Jamie Zawinski, an American programmer and open source contributor:
Some people, when confronted with a problem, think "I know, I’ll use regular expressions."
Now they have two problems.
Sorry, I’m not sure I get your point.
Regular Expressions is fairly independent of the software in which it used (yes, there are differences, some significant).
My point is that the JavaScript implementation of RegEx is one of the easiest to use of any language I have used. In addition, there are many, many examples of using RegEx in JavaScript. I suspect NSRegularExpression is much less well known, thus fewer examples.
NSRegularExpression and JXA both use the same regular expression engine (ICU).
Many people don’t get Zawinski’s humor and actually take his statement seriously, as indicated by this post by Rex at http://www.rexegg.com/regex-humor.html in October of 2015:
I’ve been shocked at the number of people who didn’t get the joke and wrote in to explain the [two] so-called problems over the past year.
Of course, you’re not trying to explain the problems, you’re denying there are any. Despite the different manifestation, the cause may be the same—perhaps you don’t get the joke either.
Well Stan, perhaps I’m the only person out there who didn’t get your point, and could not care less about Zawinski’s joke. No doubt RegEx is a tough nut to crack, but my point was about ease of use, whatever the user’s/programmer’s knowledge is about RegEx.
Please don’t presume to read my mind and state what I’m trying to do, or not do. I am being very direct, very straight-forward. I find JavaScript RegEx easy to use, ASObjC, not so much. Others may disagree.
I think we all get that. But the OP asked about doing stuff from AppleScript, and said he has made an iPhone app, which suggests he’s probably familiar with NSRegularExpression already. So his perspective may well be quite different from yours (or mine).
I fully understand that (although the OP was also asking about JavaScript). I was just asking in general, as I was interested, and I suspect others might be as well.
There are a number of things that, IMO, it is easier/better to do in JavaScript than in AppleScript. It is a shame that AppleScript scripts can’t reference a JXA script library.
Did you try out the code I posted?
Hi all, thanks for the suggestions. I’m just learning the language, so trying thinks left and right. Now I think JavaScript is not the way to go. I ended up writing the use-case I had just in plain AppleScript, but I learned a lot on the way. Thanks.
Thank you Shane. that was really helpful. I frequently wish to use GREP but it has always been difficult to use in AppleScript. until now…
I expanded your code a little:
(*
Perform GREP searches using NSString:
To replace:
replaceString(inStr, findGREP, replaceGREP)
Returns the amended string
To find a single instance:
findString(inStr, findGREP)
Returns the found string.
If there are no matches, the constant null is returned.
To find multiple instances:
findStrings(inStr, findGREP)
Returns a list of the found strings
For each found match, a list is returned:
{$0, $1, $2, etc}
If a group is not found, the constant null is returned.
{$0, null, $2, etc}
If no matches are found, an empty list is returned.
The \ must be used to escape other \ and " characters within the GREP expressions.
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
-- Most of the work is going to and from NSStrings.
-- A replace operation is simple:
-- sample GREP: "\\$([0-9,.]+)"
to replaceString(inStr, findGREP, replaceGREP)
set aString to current application's NSString's stringWithString:inStr
set aString to (aString's stringByReplacingOccurrencesOfString:findGREP withString:replaceGREP options:(current application's NSRegularExpressionSearch) range:{0, aString's |length|()}) as text
return aString
end replaceString
-- Finds are a bit more complex in that the results are returned as ranges,
-- which you may then need to use to extract substrings:
-- sample GREP: "(?<=\\$)[0-9,.]+"
to findString(inStr, findGREP)
set aString to current application's NSString's stringWithString:inStr
set theRange to aString's rangeOfString:findGREP options:(current application's NSRegularExpressionSearch)
if theRange's |length| is not 0 then
return (aString's substringWithRange:theRange) as text
else
return null -- no matches
end if
end findString
-- Or if there could be multiple matches:
to findStrings(inStr, findGREP)
set aString to current application's NSString's stringWithString:inStr
set {theExpr, theError} to current application's NSRegularExpression's regularExpressionWithPattern:findGREP options:0 |error|:(reference)
set theMatches to theExpr's matchesInString:aString options:0 range:{0, aString's |length|()}
set theResults to {}
repeat with aMatch in theMatches
set theseMatches to {}
repeat with r from 0 to (((aMatch's numberOfRanges) as integer) - 1)
set theRange to (aMatch's rangeAtIndex:r)
if theRange's |length| is not 0 then
set end of theseMatches to (aString's substringWithRange:theRange) as text
else
set end of theseMatches to null -- no match for this group
end if
end repeat
set end of theResults to theseMatches
end repeat
return theResults
end findStrings
We had a similar discussion in Nov 2017, and Shane provided similar scripts. Here’s my refactor of Shane’s scripts, combining find of first match with find of all matches.
RegEx Handler to Return Capture Groups
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@RegEx Handler to Get List of Capture Groups @ASObjC @Shane @AS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DATE: 2017-11-22
AUTHOR: JMichaelTX refactor of Handlers by ShaneStanley
REF:
• Does SD6 Find RegEx Support Case Change?
• Late Night Software Ltd.,
• http://forum.latenightsw.com//t/does-sd6-find-regex-support-case-change/816/8
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
property LF : linefeed
set sourceStr to " 2016-01-11 and some text and 2017-02A-22"
set reFind to "(\\d{4})-(\\d{2})(A)?-(\\d{2})"
set reFirstMatch to my regexFind:reFind inString:sourceStr findAll:false
-->{"2016-01-11", "2016", "01", "", "11"}
set reAllMatches to my regexFind:reFind inString:sourceStr findAll:true
-->{{"2016-01-11", "2016", "01", "", "11"}, {"2017-02A-22", "2017", "02", "A", "22"}}
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on regexFind:pFindRegEx inString:pSourceString findAll:pGlobalBool
-------------------------------------------------------------------
(*
Find Match(s) & Return Match with All Capture Groups as List of Lists
• One List per Match
• First Item is full match
• Remaining Items are one item per Capture Group
• IF CG not found, Item is returned as empty string
if pGlobalBool is true, then Find ALL matches (Global) & return as list of lists
else Find FIRST match, & return simple list of match & capture groups
If NO matches, return empty list {}
This handler is a merge and refactor of the two handlers provided by @ShaneStanley
(http://forum.latenightsw.com//t/does-sd6-find-regex-support-case-change/816/8)
All errors are mine.
-------------------------------------------------------------------
*)
local theFinds, theResult, subResult, groupCount
try
set pSourceString to current application's NSString's stringWithString:pSourceString
set {theRegEx, theError} to current application's NSRegularExpression's regularExpressionWithPattern:pFindRegEx options:0 |error|:(reference)
if theRegEx is missing value then error ("Invalid RegEx Pattern." & LF & theError's localizedDescription() as text)
if (pGlobalBool) then ### FIND ALL MATCHES ###
set theFinds to theRegEx's matchesInString:pSourceString options:0 range:{0, pSourceString's |length|()}
else ### FIND FRIST MATCH ###
set theFind to theRegEx's firstMatchInString:pSourceString options:0 range:{0, pSourceString's |length|()}
set theFinds to current application's NSMutableArray's array()
(theFinds's addObject:theFind)
end if
set theResult to current application's NSMutableArray's array()
repeat with aFind in theFinds
set subResult to current application's NSMutableArray's array()
set groupCount to aFind's numberOfRanges()
repeat with i from 0 to (groupCount - 1)
set theRange to (aFind's rangeAtIndex:i)
if |length| of theRange = 0 then
--- Optional Capture Group was NOT Matched ---
(subResult's addObject:"")
else
--- Capture Group was Matched ---
(subResult's addObject:(pSourceString's substringWithRange:theRange))
end if
end repeat
(theResult's addObject:subResult)
end repeat -- theFinds
on error errMsg number errNum
set errMsg to "ASObjC RegEx ERROR #" & errNum & LF & errMsg
set the clipboard to errMsg
display dialog errMsg & LF & ¬
"(error msg is on Clipboard)" with title (name of me) with icon stop
error errMsg
end try
return theResult as list
end regexFind:inString:findAll:
nice!!! I’ve been scripting for a long time, but I haven’t seen some of the syntax that you’re using. is most of it NSString stuff?
95%+ of that script was written by @ShaneStanley. I wouldn’t call it “NSString stuff”, but “ASObjC” stuff.
My only real contribution was in combining the scripts with this section:
if (pGlobalBool) then ### FIND ALL MATCHES ###
set theFinds to theRegEx's matchesInString:pSourceString options:0 range:{0, pSourceString's |length|()}
else ### FIND FRIST MATCH ###
set theFind to theRegEx's firstMatchInString:pSourceString options:0 range:{0, pSourceString's |length|()}
set theFinds to current application's NSMutableArray's array()
(theFinds's addObject:theFind)
end if
I was able to deduce using the NSMutableArray's array()
from the statements here and elsewhere that Shane has written.
If you want to have a chance at learning ASObjC, then I’d recommend Everyday AppleScriptObjC, Third Edition, by Shane Stanley.
just bought it. nice to have access to the Cocoa libraries.