Why Doesn't This Code Work?

Here is some code that I believe should change the extension from an image type to a JPG.
(e.g. Change Image.TIFF to Image.JPG)

But instead it returns a file with a double filename extension. (e.g. Image.TIFF becomes Image.TIFF.JPG).

Why? What am I missing?


property openTypes : {"PDF", "com.adobe.pdf", "BMP", "com.microsoft.bmp", "PICT", "com.apple.pict", "PNG", "public.png", "PSD", "com.adobe.photoshop-image", "TIFF", "public.tiff"}

--Get the artwork file
set theFiles to choose file with prompt "Choose art file(s)" of type openTypes without invisibles
set theType to "JPG"

runTest(theFiles, theType)

on runTest(theArt, theType)
	display dialog "theArt is " & theArt
	tell application "Finder"
		set saveExt to extension hidden of theArt
		set extension hidden of theArt to true
		set theFolder to (container of theArt) as text
		set theNewArt to (theFolder as text) & (displayed name of theArt) & "." & theType
		set extension hidden of theArt to saveExt
	end tell
	display dialog "theNewArt is " & theNewArt
end runTest

By the way, here is some workaround code. But I am curious why the code above does not work as expected:


property openTypes : {"PDF", "com.adobe.pdf", "BMP", "com.microsoft.bmp", "PICT", "com.apple.pict", "PNG", "public.png", "PSD", "com.adobe.photoshop-image", "TIFF", "public.tiff"}

--Get the artwork file
set theFiles to choose file with prompt "Choose art file(s)" of type openTypes without invisibles
set theType to "JPG"

runTest(theFiles, theType)

on runTest(theArt, theType)
	display dialog "theArt is " & theArt
	set noExtName to removeExtension(theArt as text)
	tell application "Finder"
		set theNewArt to (noExtName as text) & "." & theType
	end tell
	display dialog "theNewArt is " & theNewArt
end runTest

on removeExtension(thisName)
	if thisName contains "." then
		set thisName to (the reverse of every character of thisName) as string
		set x to the offset of "." in thisName
		set thisName to (text (x + 1) thru -1 of thisName)
		set thisName to (the reverse of every character of thisName) as string
	end if
	return thisName
end removeExtension

After some testing, I solved the problem.

The first code (using “extension hidden”) does work BUT ONLY if you do NOT have the “Show all filename extensions” option enabled in the preferences for Finder.

My workaround code will work no matter how you set that option in Finder.

I hope this helps others. I was pulling out my hair for awhile!

Let me disagree with your workaround.
Look at this example.

on removeExtension(thisName)
	if thisName contains "." then
		set thisName to (the reverse of every character of thisName) as string
		set x to the offset of "." in thisName
		set thisName to (text (x + 1) thru -1 of thisName)
		set thisName to (the reverse of every character of thisName) as string
	end if
	return thisName
end removeExtension

set thisName to "Macintoh HD:Users:moi:Documents:31.12.43:myFile"
my removeExtension(thisName) --> "Macintoh HD:Users:moi:Documents:31.12"

No need to reinvent the wheel.

For years, stripping the extension is done with such kind of code.


property openTypes : {"PDF", "com.adobe.pdf", "BMP", "com.microsoft.bmp", "PICT", "com.apple.pict", "PNG", "public.png", "PSD", "com.adobe.photoshop-image", "TIFF", "public.tiff"}

--Get the artwork file
set theFiles to choose file with prompt "Choose art file(s)" of type openTypes without invisibles
set theType to "JPG"

runTest(theFiles, theType)

on runTest(theArt, theType)
	display dialog "theArt is " & theArt
	
	tell application "Finder"
		set theExt to name extension of theArt
		set theArt to theArt as string
		if theExt is "" then
			set noExtPath to theArt as string
		else
			set noExtPath to (text items 1 thru -(2 + (count theExt)) of theArt) as string
		end if
		set theNewArt to noExtPath & "." & theType
	end tell
	display dialog "theNewArt is " & theNewArt
end runTest

I know that some helpers dislike to use the Finder (I’m one of them but here, you already used it, with no real need) to drop the name extension.
But they take care to drop it from the file name, not from the file path as your code does.
It’s why I took care to use a varName matching what it’s supposed to contain.

I’m a bit puzzled by your first instruction.
As far as I know you would get the same behaviour with this shorter one:

property openTypes : {"com.adobe.pdf", "com.microsoft.bmp", "com.apple.pict", "public.png", "com.adobe.photoshop-image", "public.tiff"}

If you wish to experiment you may try to use this instruction:

set showExtensions to do shell script "defaults read com.apple.finder AppleShowAllExtensions"

If it returns 1, your paths are supposed to have an extension, if it returns 0 they are supposed to haven’t. I wrote ‘are supposed’ because my memory which may be facetious tell me that this feature doesn’t apply to path of applications.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) Wednesday 22 April 2020 18:25:45

Many thanks Yvan! My updated code fixed one problem but created another one.
(I was trying to fix the original code for ImageConvert in the Unscripted Forum on this website).

I agree that your code for is much better. It solves the original problem for the ImageConvert script. (i.e. the original code would create converted image files with double extensions (e.g. Image.PNG.JPG) when a user has enabled “Show all filename extensions” in the preferences for Finder. The extension hidden attribute of the Finder really does not work when this option is enabled in the preferences for Finder).

P.S. I turned your code into a subroutine so I can reuse it in other scripts I develop. Below is a sample script using the subroutine.


property openTypes : {"PDF", "com.adobe.pdf", "BMP", "com.microsoft.bmp", "PICT", "com.apple.pict", "PNG", "public.png", "PSD", "com.adobe.photoshop-image", "TIFF", "public.tiff"}

--Get the artwork file
set theFile to choose file with prompt "Choose art file(s)" of type openTypes without invisibles
set theNewType to "JPG"

runTest(theFile, theNewType)

on runTest(theArt, theType)
	display dialog "theArt is " & theArt
	
	set noExtPath to removeExtension(theArt)
	set theNewArt to noExtPath & "." & theType
	
	display dialog "theNewArt is " & theNewArt
end runTest

on removeExtension(thisName)
	tell application "Finder"
		set theExt to (name extension of thisName)
	end tell
	set thisName to thisName as string
	if theExt is "" then
		set noExtPath to thisName as string
	else
		set noExtPath to (text items 1 thru -(2 + (count theExt)) of thisName) as string
	end if
	return noExtPath as text
end removeExtension

I also created a companion subroutine that replaces the extension. See the sample script below using this subroutine:


property openTypes : {"com.adobe.pdf", "com.microsoft.bmp", "com.apple.pict", "public.png", "com.adobe.photoshop-image", "public.tiff"}

--Get the artwork file
set theFile to choose file with prompt "Choose art file(s)" of type openTypes without invisibles
set theNewType to "JPG"

runTest(theFile, theNewType)

on runTest(theArt, theType)
	display dialog "theArt is " & theArt
	set theNewArt to replaceExtension(theArt, theType)
	display dialog "theNewArt is " & theNewArt
end runTest

on replaceExtension(thisName, newExt)
	tell application "Finder"
		set theExt to (name extension of thisName)
	end tell
	set thisName to thisName as string
	if theExt is "" then
		set noExtPath to thisName as string
	else
		set noExtPath to (text items 1 thru -(2 + (count theExt)) of thisName) as string
	end if
	return (noExtPath & "." & newExt) as text
end replaceExtension

I also agree with you that one can use the simpler property statement:

property openTypes : {“com.adobe.pdf”, “com.microsoft.bmp”, “com.apple.pict”, “public.png”, “com.adobe.photoshop-image”, “public.tiff”}

instead of

property openTypes : {“PDF”, “com.adobe.pdf”, “BMP”, “com.microsoft.bmp”, “PICT”, “com.apple.pict”, “PNG”, “public.png”, “PSD”, “com.adobe.photoshop-image”, “TIFF”, “public.tiff”}

I suspect the other unnecessary types were added for readability sake by the original coder, Kevin Bradley.

Again many thanks for improving the code!

Yvan - FYI - I have updated the original code for ImageConvert using your method.

Please see
https://macscripter.net/viewtopic.php?id=24736

@RobK

thank you.
The code removing the extension is used by many Applescript users for years.
As I dislike the Finder (in fact I hate it) in my new scripts I do the job using ASObjC which is verbose but very efficient and powerful

----------------------------------------------------------------
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------

set aFile to choose file of type {"png", "pdf"}
my buildJpgPath:aFile

on buildJpgPath:aFile
	set POSIXPath to POSIX path of aFile
	set pathNSString to current application's NSString's stringWithString:POSIXPath
	set newPath to pathNSString's stringByDeletingPathExtension()
	set newPath to newPath's stringByAppendingPathExtension:"jpg"
	return newPath as «class furl» as string
end buildJpgPath:

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) Wednesday 22 April 2020 23:53:53

These snippets, which involve coercion of a list to a string, are a bit of a disaster waiting to happen, because the results depend on the state of text item delimiters. You should explicitly set them before doing this sort of thing.

Better, and more efficient, is not to coerce at all:

Hi Shane. I don’t think you meant to include the first line.

Nigel, does it make more sense now? I was referring to separate scripts…

Hi Shane.

text 1 thru -1 of (the reverse of every character of thisName) produces a list. I got the impression your intention was to demonstrate a direct substring extraction.

set thisName to "fred.jpg"
set thisName to text 1 thru -1 of (the reverse of every character of thisName)
--> {"g", "p", "j", ".", "d", "e", "r", "f"}

I looked carefully at the pointed thread and discovered that you misunderstood what I wrote.

It’s not what I pointed to.

on removeExtension(thisName)
	if thisName contains "." then
		set thisName to (the reverse of every character of thisName) as string
		set x to the offset of "." in thisName
		set thisName to (text (x + 1) thru -1 of thisName)
		set thisName to (the reverse of every character of thisName) as string
	end if
	return thisName
end removeExtension


-- what you posted. It behaves flawlessly
-- bad choice of variable name, it's not a name but a path
set thisName to "Macintoh HD:Users:moi:Documents:31.12.43:Image-12.22.09.bmp"
my removeExtension(thisName) --> "Macintoh HD:Users:moi:Documents:31.12.43:Image-12.22.09"

-- what I pointed to. It returns an awful result
-- bad choice of variable name, it's not a name but a path
set thisName to "Macintoh HD:Users:moi:Documents:31.12.43:myFile"
my removeExtension(thisName) --> "Macintoh HD:Users:moi:Documents:31.12"


-- correct variable name
set thisName to "myFile"
my removeExtension(thisName) --> "myFile"

As you carefully worked upon the reverse of the original path, your code gave a correct result when a name contain several dots.
The problem is that if the name contains no dot and a folder name in the path contains such character, the result will be awful: the end of the folder name will be dropped with the file name.

I repeat that your choice of the variable name facilitate this kind of error.
If you were really passing a file name the problem wouldn’t strike

Back to the extraction of the name extension.
I dislike to write that but only the Finder make a correct job.

----------------------------------------------------------------
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------

on getExtension:aFile
	set POSIXPath to POSIX path of aFile
	set pathNSString to current application's NSString's stringWithString:POSIXPath
	set theExt to pathNSString's pathExtension()
	return theExt as string
end getExtension:


set aFile to ((path to desktop as string) & "essai.jpeg") as alias

set theExt to name extension of (info for aFile) --> "jpeg"
log result
tell application "Finder" to set theExt to name extension of aFile --> "jpeg"
log result
tell application "System Events" to set theExt to name extension of aFile --> "jpeg"
log result
set theExt to my getExtension:aFile --> "jpeg"
log result

set aFile to ((path to desktop as string) & "essay") as alias

set theExt to name extension of (info for aFile) --> missing value which is correct but requires a 'special' treatment
log result
tell application "Finder" to set theExt to name extension of aFile --> ""
log result
tell application "System Events" to set theExt to name extension of aFile --> ""
log result
set theExt to my getExtension:aFile --> ""
log result


set aFile to ((path to desktop as string) & "essai.YK") as alias

set theExt to name extension of (info for aFile) --> "YK" which is WRONG
log result
tell application "Finder" to set theExt to name extension of aFile --> "" which is correct
log result
tell application "System Events" to set theExt to name extension of aFile --> "YK" which is WRONG
log result
set theExt to my getExtension:aFile --> --> "YK" which is WRONG
log result

As info for is deprecated for years, I’m not really bored by it’s behavior.
The behavior of System Events and ASObjC is more problematic.
With them, we must be sure of what we pass to them ends with a valid name extension.

YK is an acronym which I often use but it’s not a name extension.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 23 avril 2020 11:31:02

Thanks, Nigel – it was. I’ll edit accordingly (and get some sleep).

Thanks Yvan. Now I understand the original problem with my proposed solution code. It will not work if the filename does not have an extension (i.e. no period) and the path to the filename contains periods (or dots) (e.g one of the folders has a name with dots in it).

I also agree with you that it is better to just manipulate the filename and not the whole path including the filename. Working with the filename will help avoid a lot of problems.

In any event, it looks like I could fix my original code by checking whether the filename is missing an extension and deal with that case accordingly. For example:


on removeExtension(thisName)
	tell application "Finder"
		set theEXT to (name extension of thisName)
	end tell
	if theEXT is "" then
		set noExtName to thisName as string
	else
		set noExtName to (the reverse of every character of (thisName as string)) as string
		set x to the offset of "." in noExtName
		set noExtName to (text (x + 1) thru -1 of noExtName)
		set noExtName to (the reverse of every character of noExtName) as string
	end if
	return noExtName
end removeExtension

It appears to work with your test case (i.e. no filename extension but the path to the filename contains periods).

But your code is simpler and more elegant. It will probably run a lot faster too. (It also avoids coercion into strings!)

And thank you for taking the time to write your last example code. I never realized that there was a difference between a “name extension” and a “filename extension”. I always thought that they were the same. Your example using “assai.YK” shows that they are different.

I learned something new today! That is always a good thing.

Merci Beaucoup!

Robert
Quebec, Canada

It seems that I am unable to find the correct words.

Name extension is not a property of a file name, it’s a property of a file (aka a file path or a file reference)
Finder may get the name extension of an item which is a file descriptor.
It may be an alias like what is returned by choose file
or a Finder reference like what we get when we start from a selection.

tell application "Finder"
	set aFile1 to item 1 of (get selection)
	--> document file "P3110225.psd" of folder "Baudart YK" of folder "Desktop" of folder "**********" of folder "Users" of startup disk of application "Finder"
	set theExt1 to name extension of aFile1
	--> "psd"
	set aFile2 to (item 1 of (get selection)) as alias
	--> alias "SSD 1000:Users:**********:Desktop:Baudart YK:P3110225.psd"
	set theExt2 to name extension of aFile2
	--> "psd"
	
	set aFile3 to (item 1 of (get selection)) as «class furl»
	--> file "SSD 1000:Users:**********:Desktop:Baudart YK:P3110225.psd"
	try
		set theExt3 to name extension of aFile3
	on error errMsg number errNbr
		log errMsg & ",  number#" & errNbr
		--> (*Erreur dans Finder : Il est impossible d’obtenir name extension of file "SSD 1000:Users:**********:Desktop:Baudart YK:P3110225.psd".,  number#-1728*)
	end try
	
	set aName1 to name of aFile1
	try
		name extension of aName1
	on error errMsg number errNbr
		log errMsg & ",  number#" & errNbr
		--> (*Il est impossible d’obtenir name extension of "P3110225.psd".,  number#-1728*)
	end try
end tell

Maybe it’s more clear.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 23 avril 2020 17:15:14

Yes, human language can be vague and confusing.

But I believe I do understand you. in Applescript, the term “name extension” refers to a property of a file NOT to a property of the name of a file (or filename).

So you can only use “name extension” (typically within a Finder or System Events Tell Block) when one is dealing with files NOT names of files.

I will try to remember that. But maybe it is better to avoid using the Finder altogether! :slight_smile:

And “name extensions” are not the same as the extension of a file.

Using your example - assai.YK

YK is NOT a valid “name extension”. The name extension for assai.YK is “”.

But in my mind YK is an extension to the filename. It is just not a name extension.

Something has property in the programming only when it is object (1 special structure of code with properties, and methods). For example, string “Dog.txt” is not object, so the following will raise error:

properties of "Dog.txt"

the following returns result, but only because command item works here. It is not property:

item 1 of "Dog.txt" -- Result: "D"

NOTE: NSString @“Dog.txt” is object:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set aDog to current application's NSString's stringWithString:"Dog.txt" 
aDog's pathExtension() -- Result: "txt"

Here, used property pathExtension, which returns method pathExtension() of NSString class

Thanks KniazidisR.

Yes that makes sense. Only objects in programming have properties.

So going back to Yvan’s example. The “name” of a file is NOT an object so it does not have any properties.

But files are typically considered objects in programming. So files do have properties such as names, extensions etc.

Exactly, you understand me.

More specifically, AppleScript simple strings are also objects, only these are the simplest low-level objects. They have only one main method - the method that, when accessing an object, returns only 1, its value:

set anObject to a reference to "Dog.txt" -- Result: "Dog.txt"

But, anyway, it will be more correct to name an object only that which has some other properties and methods besides this main one.

How would you describe a filename like my old friend “31.12.43” which is perfectly correct?

On my machine there is not such filename because I NEVER urge the Finder to hide name extensions.

I repeat that, although I hate it, the Finder is the unique tool treating the name extensions correctly.

Info for is obsolete.
System Events and ASObjC assume that the last piece of a file name which is preceded by a dot is an extension. If I name a file : “La.guillotine”, guillotine will be treated as a name extension although it’s really not what it’s designed to achieve ???

Imagine a folder named “La.guillotine” passed erroneously in your original code. It would become La.JPG and I doubt that Preview would be at ease with it.

It’s important to understand that although AppleScript try to resemble to a natural language, it’s not one. Its vocabulary is made of words borrowed to natural languages but their meaning is not guaranteed to be exactly the common one.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 23 avril 2020 22:42:26

It’s not that simple, though. Suppose I write a new application, say Chop.app, and it saves its files as .guillotine files. guillotine is now a file extension – just not one defined in an app on your Mac (yet). By that argument, any extension not defined by the system or OS-installed apps is potentially not really an extension.

As you point out, the Finder’s scripting dictionary takes the stricter view. But most applications, and much of the system, take the opposite view – they use the approach Foundation uses because, well, they are built on Foundation. (FWIW, I suspect the Finder’s behavior is a hangover from the days of file types and creators, when extensions were entirely optional. In that world, it made sense.)

I think the real point here is that the whole notion of extensions is very fragile, and if you stray from the rule of always using them for documents, you’re asking for unpredictable results.