John D
John D

Reputation: 159

Applescript with do Javascript and passed Applescript Variable

I have written a script that has automated the creation of products on a website I administer. In my process I upload JPEG images of the products and pull the Keywords that are tagged in the JPEG to add into the product information. In this process I use Applescript to Activate Safari and process a Javascript line of code. The line of code includes the a variable that is derived from Applescript Shell Script.

Code below

tell application "Finder"
set sourceFolder to folder POSIX file "/Users/<username>/Desktop/Upload/Temp/HighRes/"
set theFiles to files of sourceFolder
set inputPath to "/Users/<username>/Desktop/Upload/Temp/"

end tell

repeat with afile in theFiles

set filename to name of afile

set fname to text 1 thru ((offset of "." in filename) - 1) of filename

--INPUT CODE TO BE LOOPED OVER BELOW--


--Add Image Keywords from Metadata--
try
    set pathVAR1 to "/Users/<username>/Desktop/Upload/Temp/HighRes/"

    set pathVAR2 to pathVAR1 & filename

    set myvar to do shell script "mdls -name kMDItemKeywords " & quoted form of pathVAR2

    set var1 to ((offset of "(" in myvar) + 1)

    set var2 to ((length of myvar) - 1)

    set myKeywords to ((characters var1 thru var2 of myvar) as string)

    --Inputs the Keywords from the Image Metadata--
    tell application "Safari"
        activate
        do JavaScript "document.getElementById('ctl00_cphMainContent_txtKeyWords').value = \"" & myKeywords & "\";" in current tab of window 1
    end tell
end try

--END OF CODE TO BE LOOPED OVER--


end repeat

==End Code==

Problem:

The code below is not passing the variable myKeywords to Safari, but if I run a dialog it will appear in the dialog.

do JavaScript "document.getElementById('ctl00_cphMainContent_txtKeyWords').value = \"" & myKeywords & "\";" in current tab of window 1

Upvotes: 1

Views: 655

Answers (2)

John D
John D

Reputation: 159

I had tried the backticks ( ` ) suggested by CJK but that did not work for me. The main issue being raised was that the kMDItemKeywords returned escaped characters.

Heart,
Studio,
Red,
\"RF126-10.tif\",
Tree,
\"Heart Tree\",
occasion,
Farm,
birds,
\"Red Farm Studio\",
\"all occasion\",
all

I was able to get rid of the escaped characters using the following:

NEW CODE

set myKeywords to do shell script "echo " & quoted form of myKeywords & " | tr -d '[:cntrl:]'| tr '[:upper:]' '[:lower:]' | tr -d '\"'"


UPDATED CODE FOR JAVASCRIPT

--Inputs the Keywords from the Image Metadata--
tell application "Safari"
    activate
    do JavaScript "document.getElementById('ctl00_cphMainContent_txtKeyWords').value = '" & myKeywords & "';" in current tab of window 1
end tell


RESULT

--> "    heart,    studio,    red,    rf126-10.tif,    tree,    heart tree,    occasion,    farm,    birds,    red farm studio,    all occasion,    all"

Upvotes: 1

CJK
CJK

Reputation: 6112

I don't have a specific solution that will definitely solve your problem, but I do have a number of observations about your script with recommendations on how it can be changed to improve its speed, robustness and adherence to principles of best practice.

  1. Get rid of that try block. You have no idea what's happening in your script when things go wrong if you're masking the errors with unnecessary error-catching. The only line that needs to be enclosed in try...end try is do shell script, but only put it in once you know your code is working. In general, try blocks should only be used:

    • when your script has the potential to throw an error that is entirely predictable and explainable, and you understand the reasons why and under what conditions the error occurs, allowing you to implement an effective error-handling method;
    • around the fewest possible number of lines of code within which the error arises, leaving all lines of code whose existence doesn't depend on the result of the error-prone statement(s);
    • after your script has been written, tested, and debugged, where placing the try block(s) no longer serves to force a script to continue executing in the wake of an inconvenient error of unknown origin, but has a clear and well-defined function to perform in harmony with your code, and not against it.
  2. As a general rule in AppleScript, don't use Finder to perform file system operations if you can avoid it: it's slow, and blocks while it's performing the operations, meaning you can't interact with the GUI during this time. Use System Events instead. It's a faceless application that won't stop other things operating when it's performing a task; it's fast, in the context of AppleScript and Finder in particular, and isn't prone to timing out quite so much as Finder does; it handles posix paths natively (including expansion of tildes), without any coercion necessary using POSIX file; it returns alias objects, which are the universal class of file object that every other scriptable application understands.

    There are a couple of instances where Finder is still necessary. System Events cannot reveal a file; nor can it get you the currently selected files in Finder. But it's simple enough to have Finder retrieve the selection as an alias list, then switch to System Events to do the actual file handling on this list.

  3. This is curious:

    set filename to name of afile
    set fname to text 1 thru ((offset of "." in filename) - 1) of filename
    

    Am I right in thinking that fname is intending to hold just the base file name portion of the file name, and this operation is designed to strip off the extension ? It's a pretty good first attempt, and well done for using text here to itemise the components of the string rather than characters. But, it would, of course, end up chopping off a lot more than just the file extension if the file name had more than one "." in it, which isn't uncommon.

    One way to safely castrate the end of the file name is to use text item delimiters:

    set filename to the name of afile
    set fname to the filename
    
    set my text item delimiters to "."
    if "." is in the filename then set fname to text items 1 thru -2 of the filename as text
    

    You should then be mindful or resetting the text item delimiters afterwards or there'll be consequences later on when you try and concatenate strings together.

    Another way of chopping of the extension without utilising text item delimiters is string scanning, which is where you iterate through the characters of a string performing operations or tests as you go, and achieving the desired outcome. It's speedier than it sounds and a powerful technique for very complex string searching and manipulations:

    set filename to the name of afile
    set fname to the filename
    
    repeat while the last character of fname ≠ "."
        set fname to text 1 thru -2 of fname
    end
    
    set fname to text 1 thru -2 of fname
    

    You could also retrieve the name extension property of the file, get its length, and remove (1 + that) many characters from the end of the file's name. There a myriad ways to achieve the same outcome.

  4. This is wrong in this particular instance:

    set myKeywords to ((characters var1 thru var2 of myvar) as string)
    

    characters produces a list, which you then have to concatenate back into a string, and this is unsafe if you aren't sure what the text item delimiters are set to. As you haven't made a reference to it in your script, it should be set to an empty string, which would result in the joining of the characters back into words produce the expected result. However, this could easily not be the case, if, say, you performed the first technique of file extension castration and neglected to set the text item limiters back—then the resulting string would have a period between every single letter.

    As a policy in AppleScript (which you can personally choose to adhere to or ignore), it's considered by some as poor form if you perform list to string coercion operations without first setting the text item delimiters to a definitive value.

    But you needn't do so here, because rather than using characters, use text:

    set myKeywords to text var1 thru var2 of myvar
    
  5. You're performing a shell command that looks like this: mdls -name kMDItemKeywords <file>, and then the two lines of AppleScript that follow awkwardly try and trim off the leading and trailing parentheses around the text representation of a bash array. Instead, you can turn on the -raw flag for mdls, which simplifies the output by stripping off the name of the key for you. This then places the parentheses as the very first and very last characters; however, since there's a load of dead whitespace in the output as well, you might as well get bash to perform all the clean up for you:

    mdls -raw -name kMDItemContentTypeTree <file> | grep -E -io '[^()",[:blank:]]+'
    

    This disregards parentheses, double quotes, commas, and whitespace, so all you get back is a list of keywords, one per line, and without any extra baggage. If you needed to itemise them, you can set a variable to the paragraphs of the output from the do shell script command, which splits the text into lines placing each keyword into a list. But it seems here that you need text and don't mind it being multilinear.


When I started to write this answer, I didn't have an inkling as to what was causing the specific issue that brought you here. Having gone through the details of how mdls formats its output, I now see the issue is with the fact that the myKeywords string will contain a bunch of double quotes, and you've surrounded the placement of the myKeywords entity in your JavaScript expression with double quotes. All of these quotes are only being escaped equally and once only in the AppleScript environment but not in the JavaScript environment, which results in each neighbouring double quote acting as an open-close pair. I ran a similar command in bash to obtain an array of values (kMDContentTreeType), and then processed the text in the way AppleScript does, before opening the JavaScript console in my browser and pasting it to illustrate what's going on:

JavaScript console

Anything in red is contained inside a string; everything else is therefore taken as a JavaScript identifier or object (or it would be if the messed up quotes didn't also mess up the syntax, and then result in an unterminated string that's still expecting one last quote to pair with.

I think the solution is to use a continuation character "\" for backward compatibility with older browsers: so you would need to have each line (except the last one) appended with a backslash, and you need to change the pair of double quotes surrounding the myKeywords value in your JavaScript expression to a pair of single quotes. In newer browsers, you can forgo the headache of appending continuation marks to each line and instead replace the pair of outside double quotes with a pair of backticks (`) instead:


❌'This line throws 
an EOF error in
JavaScript';


✅'This line is \
processed successfully \
in JavaScript';


✅`This line is also
processed successfully
in JavaScript`;

Upvotes: 1

Related Questions