Is it possible to call javascript from AppleScript?

foundation
asobjc

(Jim Underwood) #4

Are these as easy to use as JavaScript Regular Expressions?


(Shane Stanley) #5

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.


(Doeke Zanstra) #6

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…


(Doeke Zanstra) #7

Hmm, looks like a lot of boilerplate.
A run shell script with awk or something will probably also do the trick.


(Shane Stanley) #8

It depends a bit on your priorities – shorter code or quicker execution.


(Jim Underwood) #9

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.


(Stan Cleveland) #10

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.


(Jim Underwood) #11

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.


(Shane Stanley) #12

NSRegularExpression and JXA both use the same regular expression engine (ICU).


(Stan Cleveland) #13

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. :wink:


(Jim Underwood) #14

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.


(Shane Stanley) #15

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).


(Jim Underwood) #16

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.


(Shane Stanley) #17

Did you try out the code I posted?


(Doeke Zanstra) #18

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.


#19

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

(Jim Underwood) #20

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:


Unable to "open text file" or "save workbook as" in Excel 2016
#21

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?


(Jim Underwood) #22

95%+ of that script was written by @ShaneStanley. I wouldn’t call it “NSString stuff”, but “ASObjC” stuff. :wink:

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.


#23

just bought it. nice to have access to the Cocoa libraries.