I get the same result:
[<__NSCFString 0x600000dcb660> valueForUndefinedKey:]: this class is not key value coding-compliant for the key fileURL.
I get the same result:
[<__NSCFString 0x600000dcb660> valueForUndefinedKey:]: this class is not key value coding-compliant for the key fileURL.
I give up. Looks like there’s some sort of bridging issue here. @Shane Stanley already suggested using Bridge Plus. Perhaps JsObjC does the job, but I only have a rudimentary knowledge of it.
Working JsObjC code can be embedded into AppleScript using osascript command. By the way, there has already been one such case. I’ll raise one of your questions that I was able to solve with JsObjC.
I think the “fileURL” method/property on NSString May only be available for newer OS Foundation Frameworks.
Shane’s bridgeplus uses a custom framework with extensions on NSString.
I’m not quite sure what “the job” is in this case, but the following snippet is working in JS/JXA/JSObjC:
const paths = ["/Users/me/test_directory/Keyboard_Maestro_Test_Folder/A copy 1", "/Users/me/test_directory/Keyboard_Maestro_Test_Folder/A copy 2", "/Users/me/test_directory/Keyboard_Maestro_Test_Folder/ActionXML.plist"];
const nsURLArray = paths.map(p => $.NSURL.fileURLWithPath($(p)));
nsURLArray
is a JavaScript Array
though. If you need an NSArray
object, use $(nsURLArray)
.
As to
The main issue here is how to return something useful from JSObjC to AS. If you use osascript
, there’s nothing but strings (or strings in disguise, like numbers) that you can pass back to your script. For lists, this is not overly practical (but feasible).
Back to the original question: Why not simply URL-encode the file’s path (converting spaces to %20
etc) and then prepend file:///
to the result?
Same here (of course, being on Ventura). I know very little of (AS)ObjC, but I’m wondering what to expect of valueForKey
if given the parameter "fileURL"
in this context. The array contains NSString
objects only, and valueForKey
is (if I understand correctly what KVC means) returning a property of these objects. But there is no property fileURL
for NSString
objects (at least not one that I can see). OTOH, there is a fileURL
property for NSURL
objects (which only returns true
or false
, though). And using that as the parameter for valueForKey
on an NSArray
of NSURL
objects (like $(nsURLArray)
in the code above) works just fine.
That would appear to require a repeat loop, in which case NSURL’s fileURLWithPath method would seem a better (or at least more commonly used) choice.
Hm. Interestingly, the following also works on Catalina.
Obviously, fileURL here is a property of the array’s NSString objects. Damn it, I can’t find the corresponding entry in the official documentation of the NSString class:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set posixPaths to {"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"}
set NSStrings to current application's NSArray's arrayWithArray:posixPaths
set theURLs to (NSStrings's fileURL) -- as list, optional
I don’t think so. NSStrings
(not a very good choice for a variable name in that context) is an NSArray
object. So NSStrings's fileURL
would be a property of NSArray
, not of NSString
(you’re not using valueForKey
here). And the code throws an error on Ventura.
What exactly is theURLs
in your environment?
NSURLs without coercion as list
Odd…
That works in Script Debugger 5.0.12 but not in Script Debugger 8.0.6 or Apple’s Script Editor.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set posixPathList to {"/Users/chris/Desktop/FastScripts ⇢ Search & Replace Evaluation.scpt", "/Users/chris/Desktop/REBUTTAL.scpt", "/Users/chris/Desktop/test.txt"}
set theNSArray to current application's NSArray's arrayWithArray:posixPathList
set theURLs to theNSArray's fileURL --<<-- Script Debugger 8.0.6 (8A66) on OSX 10.14.6 fails here.
return theURLs as list
--> Error: Can’t get fileURL of «class ocid» id «data optr00000000609E070000600000».
FWIW, Shane has explained that using a property name without parentheses is the same as using value for key. The following script demonstrates this.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set posixPaths to {"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"}
set NSStrings to current application's NSArray's arrayWithArray:posixPaths
set theURLs to (NSStrings's lastPathComponent) --> (NSArray) {"New Text File 1.txt", "New Text File 2.txt", "New Text File 3.txt"}
It works for me in both editors without problems.
As I noted above, the fileURL property appears to be a property of the NSString class that is not documented anywhere, and is poorly implemented or not implemented at all in most OS X versions. This is another secret of the Apple team, which can be revealed by asking them a question.
I just had a look for fileURL
in all ObjC headers in …Foundation – the only one found was the property in NSURL
. Given that I’m on Ventura and almost everybody else here seems to be on older OS versions, the property seems to have gone down the drain meanwhile. Relying on undocumented features is never a good idea, I think.
As we have already found out, it is impossible to obtain a universal solution without a repeat loop.
Your script works, but it can be improved. I suggest that you don’t create an extra mutable array, but replace POSIX paths with NSURLs directly on the existing array:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
on posixPaths2NSURLs(posixPaths)
set theURLs to (current application's NSMutableArray's arrayWithArray:posixPaths)
repeat with anItem in theURLs
set contents of anItem to (current application's |NSURL|'s fileURLWithPath:anItem)
end repeat
return theURLs
end posixPaths2NSURLs
posixPaths2NSURLs({"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"})
The script above is little faster only then yours (about 1.15 times). If you want to get a large speed improvement, then you need to use a hybrid solution. NOTE: with a large list, the difference in speed will increase significantly. For example, for 96 posix paths I get speed improvement = 35 times.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
on posixPaths2NSURLs(theFiles) -- list of Posix paths
repeat with anItem in theFiles
set contents of anItem to anItem as POSIX file
end repeat
return current application's NSArray's arrayWithArray:theFiles
end posixPaths2NSURLs
posixPaths2NSURLs({"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"})
Well … Sensibly, anyway.
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
on join(lst, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set txt to lst as text
set AppleScript's text item delimiters to astid
return txt
end join
set theFiles to {"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"}
set theFiles to "{posix file \"" & join(theFiles, "\", posix file \"") & "\"}"
set theFiles to current application's class "NSArray"'s arrayWithArray:(run script theFiles)
An interesting solution. Unfortunately it is 2 times slower than my last script. I tested with 96 posix paths.
Thanks Fredrik71 and Nigel for the fine suggestions. Because computers and macOS versions vary greatly, and because absolute timing results can be more important than relative results, I ran some timing tests using Script Geek with 100 POSIX path files. My computer is a 2023 Mac mini running Ventura.
FORUM MEMBER - IN POST – RESULT
Peavine - 5 - 4 milliseconds
KniazidisR (first) - 32 - 3 milliseconds
KniazidisR (second) - 32 - 3 milliseconds
Nigel - 33 - 5 milliseconds
It’s not clear why we both test at Script Geek and get different tests. I have your script (and my 1st script) at least 20 times slower than my 2nd script.
Also, your script (and my 1st script) is at least 10 times slower than Nigel Garvey’s script. Are you building a list of Posix paths in a repeat loop? And measured time with it? You need to provide a list of 100 paths for the purity of the experiment. Here is list of 96 posix paths to test:
set theFiles to {"/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt", "/Users/Robert/Working/New Text File 1.txt", "/Users/Robert/Working/New Text File 2.txt", "/Users/Robert/Working/New Text File 3.txt"}
My test script contained an error, and I have revised my timing results above. The following is a screenshot of Script Geek with one of the tested scripts for review.
And, here is a screenshot that seems to verify that the script works as expected: