Writing file from within AS App doesn't work, Script Editor does?

OK, I’m at my wits’ end. I’ve fought with this all day and I am throwing in the towel. If anyone can offer some insight, an alternate way of doing this, or just wants to send me a virutal beer to cry in, here’s the problem:

I have an app I’m working on, part of which involves importing addresses from the Address Book. That part works fine, and the datasource works fine, too. For reasons I won’t go into, I have a copy of the data in a property (the same property that was originally appended to the datasource) that I wish to write out to a text file.

Now, the code works peachy in Script Editor. Here’s the Script Editor version:

property addressbookData : {}
property addressbookRecord : {|name|:"", company:"", phone:"", fax:"", email:"", street:"", city:"", state:"", zip:""}

tell application "Address Book"
	repeat with myperson in (every person whose first name is greater than " ")
		set |name| of addressbookRecord to name of myperson
		set tempValue to (value of (every phone whose label is "work") of myperson)
		if the length of tempValue is 0 then
			set tempValue to (value of (every phone whose label is "home") of myperson)
		end if
		if the (count of tempValue) is 0 then
			set |phone| of addressbookRecord to ""
		else
			set |phone| of addressbookRecord to first item of tempValue
		end if
		set tempValue to (value of (every phone whose label is "fax") of myperson)
		if the length of tempValue is 0 then
			set tempValue to (value of (every phone whose label contains "fax") of myperson)
		end if
		if the (count of tempValue) is 0 then
			set fax of addressbookRecord to ""
		else
			set fax of addressbookRecord to first item of tempValue
		end if
		set |street| of addressbookRecord to street of address of myperson as string
		set |city| of addressbookRecord to city of address of myperson as string
		set |state| of addressbookRecord to state of address of myperson as string
		set |zip| of addressbookRecord to zip of address of myperson as string
		set tempValue to (value of (every email whose label is "work") of myperson)
		if the length of tempValue is 0 then
			set tempValue to (value of (every email whose label is "home") of myperson)
		end if
		if the (count of tempValue) is 0 then
			set |email| of addressbookRecord to ""
		else
			set |email| of addressbookRecord to first item of tempValue
		end if
		set end of addressbookData to contents of addressbookRecord
	end repeat
end tell

set myPath to choose file name with prompt "Export Clients" default name "clients.txt"
saveText(addressbookData, myPath)
display dialog "Done writing file."

on saveText(theData, theFilepath)
	set theFileID to open for access theFilepath with write permission
	set AppleScript's text item delimiters to {tab}
	repeat with myRecord in theData
		set newRecord to {}
		set end of newRecord to |name| of myRecord
		set end of newRecord to company of myRecord
		set end of newRecord to phone of myRecord
		set end of newRecord to fax of myRecord
		set end of newRecord to email of myRecord
		set end of newRecord to street of myRecord
		set end of newRecord to city of myRecord
		set end of newRecord to state of myRecord
		set end of newRecord to zip of myRecord
		write (newRecord as text) & return to theFileID
	end repeat
	set AppleScript's text item delimiters to {""}
	close access theFileID
end saveText

And here’s the relevant handlers from the app, along with the property declarations:

--address globals
property addressbookData : {}
property addressbookRecord : {|name|:"", company:"", phone:"", fax:"", email:"", street:"", city:"", |state|:"", zip:""}

on importAddresses()
	display window "importPanel" attached to window "invoiceWindow"
	tell application "Address Book" to set theCount to count of (every person whose name is not "")
	tell progress indicator "importProgress" of window "importPanel"
		set uses threaded animation to true
		set indeterminate to true
		start
	end tell
	set addressbookData to {}
	tell application "Address Book"
		repeat with myperson in (every person whose first name is greater than " ")
			set |name| of addressbookRecord to name of myperson
			set tempValue to (value of (every phone whose label is "work") of myperson)
			if the length of tempValue is 0 then
				set tempValue to (value of (every phone whose label is "home") of myperson)
			end if
			if the (count of tempValue) is 0 then
				set |phone| of addressbookRecord to ""
			else
				set |phone| of addressbookRecord to first item of tempValue
			end if
			set tempValue to (value of (every phone whose label is "fax") of myperson)
			if the length of tempValue is 0 then
				set tempValue to (value of (every phone whose label contains "fax") of myperson)
			end if
			if the (count of tempValue) is 0 then
				set fax of addressbookRecord to ""
			else
				set fax of addressbookRecord to first item of tempValue
			end if
			set |street| of addressbookRecord to street of address of myperson as string
			set |city| of addressbookRecord to city of address of myperson as string
			set |state| of addressbookRecord to state of address of myperson as string
			set |zip| of addressbookRecord to zip of address of myperson as string
			set tempValue to (value of (every email whose label is "work") of myperson)
			if the length of tempValue is 0 then
				set tempValue to (value of (every email whose label is "home") of myperson)
			end if
			if the (count of tempValue) is 0 then
				set |email| of addressbookRecord to ""
			else
				set |email| of addressbookRecord to first item of tempValue
			end if
			set end of addressbookData to contents of addressbookRecord
		end repeat
	end tell
	append addressbookDataSource with addressbookData
	
	close panel window "importPanel" with result 0
end importAddresses

on choose menu item theObject
	(*Menu selection handler*)
	if name of theObject is "exportMenu" then
		set theResult to display save panel for file types {"txt"} in directory "~/Desktop" with file name "contacts.txt"
		if theResult = 1 then
			exportClients(path name of save panel)
		end if
	end if
end choose menu item

on exportClients(theFilePath)
	set theFileID to open for access theFilePath with write permission
	set AppleScript's text item delimiters to {tab}
	
	repeat with myRecord in addressbookData
		set newRecord to {}
		set end of newRecord to |name| of myRecord
		set end of newRecord to company of myRecord
		set end of newRecord to phone of myRecord
		set end of newRecord to fax of myRecord
		set end of newRecord to email of myRecord
		set end of newRecord to street of myRecord
		set end of newRecord to city of myRecord
		set end of newRecord to |state| of myRecord
		set end of newRecord to zip of myRecord
		write ((newRecord as text) & return) to theFileID
	end repeat
	set AppleScript's text item delimiters to {""}
	close access theFileID
end exportClients

If you look carefully, you’ll see that the export handler is copied verbatim with the exception of I had to put vertical bars around the word “state” because it’s also a recognized term in the application environment. The import handler is similarly copied to the Script Editor app (or vice versa).

When I run this in the debugger, it steps through the repeat of the export handler all the way to the last record, supposedly writing the whole time to the file, yet when I get done, there is no file. AAAARRRRGGHHHH! :mad:

Also in the debugger, I see that I am getting a nice pathname for the file, the file opens and gives up a file ID ok, and the data is being transcribed ok from the addresbookData to newRecord. I can see newRecord in the variable viewer as well, and it looks like a nice little list of text items.

I’m on the verge of hysteria. If anyone can help, please do. In fact, if you want to have the app, I’ll send you the files along with the keys to my car, my cat and all her toys, and my copy of Bob Dylan’s greatest hits (ok, just kidding about the cat…):wink:

“Curiouser and curiouser,” said Alice.

OK, you’re not gonna believe this…

I found the files, they were in fact being written to the root level of the HD?! And get this…they have filenames that look like paths…“/User/nitewing/Desktop/contacts.txt” as a file name!

STraNge…

Now…what do I do about it?

Hi :slight_smile:

Here my test with some little alternatives:


property addressbookData : {}

-- The run handler only to test
on run
	set theFilePath to "" & (path to desktop folder from user domain) & "Test.txt"
	set addressbookData to {{|name|:"Nom", company:"Bull", phone:"2525", fax:"5252", email:"moi@com", street:"Rue", city:"Ville", |state|:"Rien", zip:"5555"}, {|name|:"Nom", company:"Bull", phone:"2525", fax:"5252", email:"moi@com", street:"Rue", city:"Ville", |state|:"Rien", zip:"5555"}}
	
	my exportClients(theFilePath)
end run

-- The exportClient handler for your XCode project (tested only in Script Editor)
on exportClients(theFilePath)
	try -- If the file can not open
		set theFileID to open for access theFilePath with write permission
		repeat with myRecord in addressbookData
			try -- If can not write the data
				-- You can build the data list directly from the record, like this
				set newRecord to {|name|, company, phone, fax, email, street, city, |state|, zip} of myRecord
				-- Make the text item delimiters in the loop, to not perturb de write action
				set AppleScript's text item delimiters to tab
				-- Create the text variable before writing the data
				set newString to (newRecord as string) & return
				-- Make the default value of text item delimiters
				set AppleScript's text item delimiters to ""
				-- Write the data as string, starting at the end of file (eof)
				write newString as string to theFileID starting at eof
			end try
		end repeat
		close access theFileID
	on error
		close access theFileID
	end try
end exportClients

:wink:

Edit:

Woups… sorry Kevin, I wrote my message before seeing your last. I leave, if that can help.

Thanks for the idea, Fredo. I think the disconnect is in the pathname conventions between the save panel object (which uses POSIX path names) and the open for access command which wants standard (old style) Macintosh path names (with the : character instead of /).

I’ve been looking through dictionaries and know you can convert to POSIX path from a Mac path, but see no utility for going the other direction. Does anyone know of one?

It’s not just a matter of converting the /'s to :'s, because the Mac path also includes the HD volume name: So “/Users/Nitewing/Desktop/clients.txt” converts to “Brain:Users:Nitewing:Desktop:clients.txt” and if you try to put it on a different disk, it goes from “/Volumes/Pinky/Users/Nitewing/Desktop/clients.txt” to “Brain:Users:Nitewing:Desktop:clients.txt”.

Sounds like I’m going to have to write my own routine to convert from POSIX to Mac names. YUK. But at least I now know what the problem is and have an attack at solving it. When I found all the files at the root of the startup drive I became convinced I had a HD problem, ran Disk Utilities and TechTool and was freaking out.

WHEW!:cool:

OK, for those that need it, here’s the POSIX to hfs handler:

on hfsPath(POSIXpath)
	set AppleScript's text item delimiters to {"/"}
	set theList to text items of POSIXpath
	set AppleScript's text item delimiters to {":"}
	set theList to rest of theList

	if first item of theList is "Volumes" then
		set theList to rest of theList
		
	else
		tell application "Finder" to set theDrive to (name of startup disk)
		set beginning of theList to text item 1 of theDrive
	end if
	
	return theList as string
end hfsPath

Hi Kevin :slight_smile:

The paths to the standard Unix, Posix thus, were introduced at the time of version 1.8 of AppleScript, look at the note concerning this version: AppleScript 1.8 Release Notes

To be able to pass a path from the Posix type to the standard Mac format, coercion should be used is “as POSIX file”, like this:


set theUnixPath to POSIX path of (choose file without invisibles) as string
--> "/Users/me/Desktop/Test.txt"
set theMacPath to (theUnixPath as POSIX file) as string
--> "OsX:Users:me:Desktop:Test.txt"

:wink: