Droplet to remove leading and trailing spaces from filenames

Hi all,

An ApplesScript neophyte, I work in a digital photostudio where the photogs are not always conscientious about their copying and pasting when they’re naming image files. As a result, I get files uploaded frequently to servers with leading and/or trailing spaces in their filenames. As yo can probably imagine, this causes all sorts of problems with our workflow.

What I would like to do is to present the photogs with a droplet that they can use before they upload the files that would take a bunch of files or folders dropped on it and strip out any trailing or leading spaces in the filenames. At first, I figured that would be easy to find something already written that would do the trick, but all my searching has turned up nothing that really fits my needs.

So I made an attempt to assemble an AppleScript of my own. I say “assemble” rather than “write” because like any sensible person would, I’ve stolen most of it from other scripts, mostly Apple’s examples.

However, I still don’t have a working solution. Here’s the script as it exists so far, please don’t laugh too much:

-- This droplet processes both files or folders of files dropped onto the applet
on open (these_items)
	repeat with i from 1 to the count of these_items
		set this_item to (item i of these_items)
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else if folder of the item_info is false then
			process_item(this_item)
		end if
	end repeat
end open

-- this sub-routine processes folders
on process_folder(this_folder)
	set these_items to list folder this_folder without invisibles
	repeat with i from 1 to the count of these_items
		set this_item to alias ((this_folder as text) & (item i of these_items))
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else if folder of the item_info is false then
			process_item(this_item)
		end if
	end repeat
end process_folder

on trim_line(this_text, trim_chars, trim_indicator)
	-- 0 = beginning, 1 = end, 2 = both
	set x to the length of the trim_chars
	-- TRIM BEGINNING
	if the trim_indicator is in {0, 2} then
		repeat while this_text begins with the trim_chars
			try
				set this_text to characters (x + 1) thru -1 of this_text as string
			on error
				-- the text contains nothing but the trim characters
				return ""
			end try
		end repeat
	end if
	-- TRIM ENDING
	if the trim_indicator is in {1, 2} then
		repeat while this_text ends with the trim_chars
			try
				set this_text to characters 1 thru -(x + 1) of this_text as string
			on error
				-- the text contains nothing but the trim characters
				return ""
			end try
		end repeat
	end if
	return this_text
end trim_line

-- this sub-routine processes files
on process_item(this_item)
	trim_line(this_item, " ", 2)
end process_item

Now, I realize that in my last subroutine, there’s no command that actually sets the name of the file being processed to the text returned by the trim_line handler, but that’s because I can’t figure out how to do it. I also realize, sort of, that in the last subroutine, “this_item” is a file reference in alias form, and I’ve read a bunch of stuff about file references, and dereferencing, but I still can’t make heads or tails of this. No matter which way I try to set the name of the file, AppleScript still reports that it can’t get the name of the alias.

Is there some kind soul out there willing to take on a charity case and help me figure out what I am doing?

The “Trim Files” script that comes with Applescript in your sample scripts folder is a great place to start. It is a “select by folder” routine rather than a droplet. But, as you stated, a little re-arranging does the trick.

Drop Trim, a variation of

Trim File Names/ Copyright © 2001 Apple Computer, Inc.


on open files2trim
	set text_to_trim to display dialog "Text to trim from every file name; default is a blank space:" default answer " " buttons {"Cancel", "Trim Start", "Trim End"}
	copy the result as list to {the text_to_trim, the button_pressed}
	--if the button_pressed is "Cancel" then return "user cancelled"
	
	set the character_count to the number of characters of the text_to_trim
	set the item_list to (items of files2trim)
	repeat with i from 1 to number of items in the item_list
		set this_item to item i of the item_list
		set this_item to this_item as alias
		set this_info to info for this_item
		if folder of this_info is false then
			set the current_name to the name of this_info
			if the button_pressed is "Trim Start" then
				if the current_name begins with the text_to_trim then
					set the new_name to (characters (the character_count + 1) thru -1 of the current_name) as string
					set_item_name(this_item, new_name)
				end if
			else
				if the current_name ends with the text_to_trim then
					set the new_name to (characters 1 thru -(the character_count + 1) of the current_name) as string
					set_item_name(this_item, new_name)
				end if
			end if
		end if
	end repeat
end open

on set_item_name(this_item, new_item_name)
	tell application "Finder"
		--activate
		set the parent_container_path to (the container of this_item) as text
		if not (exists item (the parent_container_path & new_item_name)) then
			try
				set the name of this_item to new_item_name
			on error the error_message number the error_number
				if the error_number is -59 then
					set the error_message to "This name contains improper characters, such as a colon (:)."
				else --the suggested name is too long
					set the error_message to error_message -- "The name is more than 31 characters long."
				end if
				--beep
				tell me to display dialog the error_message default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
				copy the result as list to {new_item_name, button_pressed}
				if the button_pressed is "Skip" then return 0
				my set_item_name(this_item, new_item_name)
			end try
		else --the name already exists
			--beep
			tell me to display dialog "This name is already taken, please rename." default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
			copy the result as list to {new_item_name, button_pressed}
			if the button_pressed is "Skip" then return 0
			my set_item_name(this_item, new_item_name)
		end if
	end tell
end set_item_name

It’s cool to go to
http://www.apple.com/applescript/guidebook/sbrt/index.html
to get Apple written sub-routines, but look at all the error checking already included in that beast! They get paid, let them do it! :twisted:
SC
post back and let me know if this works for you…it works for me from 9 to 10.1 and up

Quote: A droplet that they can use before they upload the files that would take a bunch of files or folders dropped on it and strip out any trailing or leading spaces in the filenames.

Sorry I missed the folder inclusion. That was one of the error checks. Code below.

As far as alias’s go, droplets don’t need alias’s. The dropped selection is already a list of alias’s!
What about the line:

set this_item to this_item as alias 

That was left over from the “trim by folder” script. Those files need an alias reference. Give that line a “–” or delete it and save the script. It works the same. Again, The dropped selection is already a list of alias’s

To see this in action, drop a list of files onto this


on open fileList
	display dialog item 1 of fileList as string
end open

That will display the file’s alias reference correctly. Now try


on open fileList
	display dialog item 1 of fileList as list
end open

And you get the error “Cant make alias {HardDrive:Desktop:Examplefile.jpg} into a list”
It’s telling you that it is an alias-


on open files2trim
	set text_to_trim to display dialog "Text to trim from every file name; default is a blank space:" default answer " " buttons {"Cancel", "Trim Start", "Trim End"}
	copy the result as list to {the text_to_trim, the button_pressed}

	
	set the character_count to the number of characters of the text_to_trim
	set the item_list to (items of files2trim)
	repeat with i from 1 to number of items in the item_list
		set this_item to item i of the item_list
		--set this_item to this_item as alias   THIS IS LINE IS REDUNDANT
		set this_info to info for this_item
		--if folder of this_info is false then     THIS LINE FILTERS OUT FOLDERS
		set the current_name to the name of this_info
		if the button_pressed is "Trim Start" then
			if the current_name begins with the text_to_trim then
				set the new_name to (characters (the character_count + 1) thru -1 of the current_name) as string
				set_item_name(this_item, new_name)
			end if
		else
			if the current_name ends with the text_to_trim then
				set the new_name to (characters 1 thru -(the character_count + 1) of the current_name) as string
				set_item_name(this_item, new_name)
			end if
		end if
		--end if  THIS CLOSED THE FOLDER FILTER "IF" STATEMENT
	end repeat
end open

on set_item_name(this_item, new_item_name)
	tell application "Finder"
		--activate
		set the parent_container_path to (the container of this_item) as text
		if not (exists item (the parent_container_path & new_item_name)) then
			try
				set the name of this_item to new_item_name
			on error the error_message number the error_number
				if the error_number is -59 then
					set the error_message to "This name contains improper characters, such as a colon (:)."
				else --the suggested name is too long
					set the error_message to error_message -- "The name is more than 31 characters long."
				end if
				--beep
				tell me to display dialog the error_message default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
				copy the result as list to {new_item_name, button_pressed}
				if the button_pressed is "Skip" then return 0
				my set_item_name(this_item, new_item_name)
			end try
		else --the name already exists
			--beep
			tell me to display dialog "This name is already taken, please rename." default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
			copy the result as list to {new_item_name, button_pressed}
			if the button_pressed is "Skip" then return 0
			my set_item_name(this_item, new_item_name)
		end if
	end tell
end set_item_name

Copy and paste this, and it does files & folders
SC

Hi Sitcom,

I can’t thank you enough for your thoughtful responses. Your first response really put me on the right track. I made some good progress on the script last night at home, and put it all together and got it functioning in an unusual quiet hour or so at work this morning. Imagine my surprise when I signed in to report my success to find that you had provided something even closer to what I needed!

Basically, the changes I made incorporated Apple’s handlers for processing both files and folders, and simplified the trimming routine by removing the user interface elements, which were unnecessary for my purposes. It also trims recursively, in case they somehow manage to get multiple spaces on the beginning or end.

Thanks for your help. I couldn’t have done it without your encouragement and your pointing me to the original script with the good trimming and renaming algorithms from Apple. This was my first real attempt at using AppleScript other than simply running other people’s scripts, and the examples I had found in the AppleScript Resources “Essential Subroutines” weren’t nearly as useful.

Here’s my product, I’ll let you know if testing uncovers any bugs. :smiley:

on open files2trim
	repeat with i from 1 to the count of files2trim
		set this_item to (item i of files2trim)
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else if the folder of the item_info is false then
			trim_names(this_item)
		end if
	end repeat
end open

on process_folder(this_folder)
	set these_items to list folder this_folder without invisibles
	repeat with i from 1 to the count of these_items
		set this_item to alias ((this_folder as text) & (item i of these_items))
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else if the folder of the item_info is false then
			trim_names(this_item)
		end if
	end repeat
end process_folder

on trim_names(this_item)
	set text_to_trim to " " as string
	set this_item to this_item as alias
	set this_info to info for this_item
	if folder of this_info is false then
		set the current_name to the name of this_info
		if the current_name begins with the text_to_trim then
			repeat while current_name begins with the text_to_trim
				try
					set new_name to characters 2 thru -1 of current_name as string
				on error
					-- the text contains nothing but the trim characters
					return ""
				end try
				set current_name to new_name
			end repeat
			set_item_name(this_item, new_name)
		end if
		
		if the current_name ends with the text_to_trim then
			repeat while current_name ends with the text_to_trim
				try
					set new_name to characters 1 thru -2 of current_name as string
				on error
					-- the text contains nothing but the trim characters
					return ""
				end try
				set current_name to new_name
			end repeat
			set_item_name(this_item, new_name)
		end if
	end if
end trim_names


on set_item_name(this_item, new_item_name)
	tell application "Finder"
		--activate 
		set the parent_container_path to (the container of this_item) as text
		if not (exists item (the parent_container_path & new_item_name)) then
			try
				set the name of this_item to new_item_name
			on error the error_message number the error_number
				if the error_number is -59 then
					set the error_message to "This name contains improper characters, such as a colon (:)."
				else --the suggested name is too long 
					set the error_message to error_message -- "The name is more than 31 characters long." 
				end if
				--beep 
				tell me to display dialog the error_message default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
				copy the result as list to {new_item_name, button_pressed}
				if the button_pressed is "Skip" then return 0
				my set_item_name(this_item, new_item_name)
			end try
		else --the name already exists 
			--beep 
			tell me to display dialog "This name is already taken, please rename." default answer new_item_name buttons {"Cancel", "Skip", "OK"} default button 3
			copy the result as list to {new_item_name, button_pressed}
			if the button_pressed is "Skip" then return 0
			my set_item_name(this_item, new_item_name)
		end if
	end tell
end set_item_name

That’s quite an accomplishment for a newbie :lol:
Me thinks you no longer are…
SC

Thanks, I guess my high school AP Computer Science days trying and mostly failing to write stuff in Turbo Pascal were not totally forgotten. Thanks, Mr. Bartlett! :lol:

I’m always slightly scandalised that Apple’s own scripters set bad examples by using inefficient expressions like “number of characters of the text_to_trim” and “(characters 1 thru -(the character_count + 1) of the current_name) as string”. I can’t find an example of the former construction in Essential Sub-Routines at the moment, but the latter certainly occurs in the text trimming offering. More efficient alternatives would be:

-- number of characters of the text_to_trim
number of the text_to_trim -- faster for not having to identify "characters"
length of the text_to_trim -- even faster, I don't know why
count the text_to_trim -- the fastest by a whisker

-- (characters 1 thru -(the character_count + 1) of the current_name) as string
text 1 thru -(the character_count + 1) of the current_name
--> More efficient, immune to text item delimiters, preserves original text class

This should never be done:

‘display dialog’ returns a record, which shouldn’t be coerced to list if you care what’s what within it. The properties of records are labelled, not ordered. The items of records are ordered, which is what the second line relies on above. The ‘display dialog’ record may usually coerce to a list ordered in the way you’re expecting, but it isn’t required or guaranteed to do so. There are other ways to set the variables:

set {the text_to_trim, the button_pressed} to {text returned of result, button returned of result}
-- Or:
set {the text_to_trim, the button_pressed} to the result's {text returned, button returned}
-- Or:
tell the result to set {the text_to_trim, the button_pressed} to its {text returned, button returned}
-- Or:
set {text returned:the text_to_trim, button returned:the button_pressed} to the result

An alternative to scripting a recursion would be to use the Finder’s ‘entire contents’ capabilities to return just the files and/or folders in the hierarchy whose names begin or end with a space. The script itself would then only have to deal with those items. I can post a working example if anyone wants it; otherwise I leave it as an exercise for the interested. :wink: