Encoders, NSDictionary, AS records... and files.

Hello,

I have a record of keyed values to read from a file, install a reference to this record into an array, modify the record and dump it back to the file (some trivial work). At the moment I have a pretty heavy way to do this: I decode the file’s contents with a decoder and store each key in a global variable, modify them in several views and then I re-store them with an encoder.

This method is fat in coding and not so fast to read/write operations. In brief, it’s inelegant.

Is it possible to do this:

or is there an ASOJ equivalent, something like tell myRecord to writeToFile (myFile)?

What is the fastest, most easy-to-code manner in ASOC? My goal is:

Can you help me? Thanks!

I search, but I don’t find how to exchange records between AS and OC:

set myDictionary to myRecord – nope.

myDictionary’s initWithDictionary_(myRecord) – nope.

myDictionary’s initWithObjectsAndKeys_(myRecord) – nope.

I read in another post this from Shane Stanley:

It’s not the case here.

Somebody has an idea?

You have several errors here. First, you can’t have an init without an alloc. Second, initWithObjectsAndKeys takes an alternating list of objects and keys separated by commas, not a record. valueForKey returns the value for the key, it’s not used to set values – you would need to use myDic’s setValue_forKey_(“Hello World”, “oMessage”) to set the value.

What Shane said is true, you just need to be using the correct method – the AS equivalent of a dictionary is a record, so if you want to pass a record to a cocoa method, it should be a method that takes a dictionary as an argument like dictionaryWithDictionary. So to convert an AS record to a cocoa dictionary you would say “set myDictionary to current application’s NSDictionary’s dictionaryWithDictionary_(myRecord)”.

To read: set myDict to current application’s NSMutableDictionary’s dictionaryWithContentsOfFile_(myFile)

To set: myDict’s setValue_forKey_(“Hello World”,“oMessage”)

To write: myDict’s writeToFile_atomically_(myFile,1)

Ric

Hello Ric, thanks for the post.

Your solution seems to be what I need, I always prefer the more-cocoa-approach ” this mix between Applescript and Objective-C is ok for the time being, but this use of coercion and when to put my and when not is sometimes annoying.

There is a simple way to structure a record in AS, you may declare property myRecord : {key1:0,key2:“”.}, but how do you define the keys with a NSDictionary? Or make, somewhere, a statement like:

RECORD MYRECORD : {};
NSDICTIONARY MYDIC : NIL;
EQUIVALENCE (MYRECORD, MYDIC); :stuck_out_tongue:

ASOC is certainly a excellent mid-solution, and of course there is certainly a way to implement this idea, but in these circumstances I regret to not be able to do it in Objective-C.

Edit : Is it like this?

        set theRec to {keyOne:1,keyTwo:"Hello".}
        set theDic to current application's NSDictionary's dictionaryWithDictionary_(theRec)

and in this case (admitting there will be no compatibility fore/backwards) can the record contain ANY type of value, of any length (i.e., {., myImage : missing value, mySound : missing value, myComment:“”,.}

Because if it’s the case, this will just make my code 20% smaller, not to mention the clarity.

Regards,

script dictAppDelegate
	property parent : class "NSObject"
    property text1 : missing value
    property text2 : missing value
    property image1 : missing value
    property image2 : missing value   
	
    on dumpToFile_(sender)
        set theFile to choose file
        set theFile to posix path of (theFile as string)
        set theRec to {txt1 : text1's stringValue(), txt2 : text2's stringValue(), img1 : image1's image(), img2 : image2's image()}
        set theDic to current application's NSMutableDictionary's dictionaryWithDictionary_(theRec)
        theDic's writeToFile_atomically_(theFile,true)
    end
    
    on loadFromFile_(sender)
        set theFile to choose file
        set theFile to posix path of (theFile as string)
        set theDic to current application's NSMutableDictionary's dictionaryWithContentsOfFile_(theFile)
        my text1's setStringValue_(theDic's valueForKey_("txt1") as string)
        my text2's setStringValue_(theDic's valueForKey_("txt2")as string)
        my image1's setImage_(theDic's valueForKey_("img1"))
        my image2's setImage_(theDic's valueForKey_("img2"))
   end

end script

works for texts, but not for images.

An NSDictionary to a file can only contain property list items like NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary but no NSImage. So you need to use something to make your NSImage into NSData to store and and do that backwards to load it.

Thank you DJ!

.and I suppose there is no NSData*(myNSImage) ? :confused:

If you write a dictionary out to a file using writeToFile_atomically_ , then you are writing out a property list as DJ said, but you can use an NSKeyedArchiver to write out your dictionary, and then you can put images (and pretty much anything else I think) into it. So you could do it like this:

property parent : class "NSObject"
	property theData : missing value
	property theDict : missing value
	
	on applicationWillFinishLaunching_(aNotification)
		set img1 to current application's NSImage's imageNamed_("IMG_2796")
		set img2 to current application's NSImage's imageNamed_("IMG_2794")
		set theDict to current application's NSDictionary's dictionaryWithObjectsAndKeys_(img1, "image1", img2, "image2", "Some Text", "text1", missing value)
	end applicationWillFinishLaunching_
	
	on makeArchive_(sender)
		set sp to current application's NSSavePanel's savePanel()
		sp's runModal()
		set theData to current application's NSKeyedArchiver's archiveRootObject_toFile_(theDict, sp's |URL|()'s |path|())
	end makeArchive_
	
	on unArchive_(sender)
		set op to current application's NSOpenPanel's openPanel()
		op's runModal()
		setTheData_(current application's NSKeyedUnarchiver's unarchiveObjectWithFile_(op's URLs's objectAtIndex_(0)'s |path|()))
	end unArchive_

Here I have 2 methods connected to buttons for testing, and I bound the 2 imageViews’ values to theData.image1 and theData.image2 and it worked fine. Also, as you can see from this code, if you want to create a dictionary directly from some keys and values, you can use dictionaryWithObjectsAndKeys instead of creating an AS record first – there’s really no need to do that unless you already have that record in your code somewhere else.

OK, so I could totally get rid of the AS record? And I can, anywhere in code, use this NSDictionnary, set/reset its values by key access, put (or not) these values in views giving a myData.mySpecialKey (or maybe myDic.mySpecialKey, as myDic is also a property?

And is it possible to initialize a NSDictionary, maybe before the first writeToFile, with missing values, or is it better with zero-lenght items? That’s because I’m not sure we can, for example, pass missing values to a text view or an image view. Or is it better to do :

set theImage to myDic's getValue_forKey_(myImage, "img1")
if theImage is missing value then set theImage to myDefaultPicture

In any case, thank you very much you two! I’ll report back when I shall have tested this (with small programs first, not with my application!)

Two points. The dictionary that you get back from the unarchiver is immutable, so you can’t change it. So, you might want to try something like this:

on unArchive_(sender)
		set op to current application's NSOpenPanel's openPanel()
		op's runModal()
		setTheData_(current application's NSKeyedUnarchiver's unarchiveObjectWithFile_(op's URLs's objectAtIndex_(0)'s |path|()))
		set theDict to current application's NSMutableDictionary's dictionaryWithDictionary_(theData)
	end unArchive_

In this example I set theDict back to a mutable dictionary using theData as the input dictionary. Then you can use setObject_forKey_ to change the value for a particular key, or you can add a new key value pair that way.

There isn’t any need to create empty dictionary entries, you can have a UI element bound to a key that doesn’t yet exist and you don’t get any error messages.

Ric

Hey, it runs like hell! And in two lines of code instead of two 15-line handlers :stuck_out_tongue:

A big thank you to those who helped me (Ric, DJ and Shane). It’s not just thanks to Cocoa that I can program faster and better! This site is awesome!

BTW, did you notice how the documentation on a class becomes easier to read when you have made every possible errors with this class?

Regards

Little offtopic:
Also understanding errors and warnings helps you solving problems faster what you did wrong. I think my first error was (makes pointer from integer without a cast and I was thinking what the hell means that, in those days there was no such thing like internet so the only solution was digging into my books. I had an lack of knowledge of warnings and error messages so I had no clue what I did wrong. The day you understand those messages debugging will be very easy because you know what went wrong in your code.

I’m trying to adapt this technique to my project and I was starting with the the first method mentioned:

on dumpToFile_(sender)
set theFile to choose file
set theFile to posix path of (theFile as string)
set theRec to {txt1 : text1's stringValue(), txt2 : text2's stringValue(), txt3 : text3's stringValue(),txt4 : text4's stringValue(),txt5 : text5's stringValue()}
set theDic to current application's NSMutableDictionary's dictionaryWithDictionary_(theRec)
theDic's writeToFile_atomically_(theFile,true)
end

on loadFromFile_(sender)
set theFile to choose file
set theFile to posix path of (theFile as string)
set theDic to current application's NSMutableDictionary's dictionaryWithContentsOfFile_(theFile)
my text1's setStringValue_(theDic's valueForKey_("txt1") as string)
my text2's setStringValue_(theDic's valueForKey_("txt2") as string)
my text3's setStringValue_(theDic's valueForKey_("txt3") as string)
my text4's setStringValue_(theDic's valueForKey_("txt4") as string)
my text5's setStringValue_(theDic's valueForKey_("txt5) as string)
end

my version is very similiar to the above the only difference I have is that I have a total of 80 values to store to my file. Everything seems to be working great until my number of variables gets above 56, at that point it is no longer creating a file. Would switching to the other method using NSKeyedArchiver potentially help me solve this problem or is there another direction in which I should be going. Thanks for any help or direction you can provide, these forums have been a great help as I start on this applescript / objC journey.

Tim

Tim,

I can’t think of a reason why the program would stop working at 56 entries. Are you really writing all these lines 80 times? I’m sure there are much more efficient ways to do that with a loop. Do you have 80 text fields? It seems like a table or a matrix would be better suited to your needs. Can you describe your project more fully?

Ric

Hi Ric, thanks for your assistance. I’m creating an application that will assist us in automating the creation of xml files that we regularly have to create. My app does have over 80 Text fields as I collect 5 different titles, descriptions, etc for 16 different items. The goal of my application is to have anyone (even those unskilled with the xml files) copy and paste titles and descriptions into the UI and then let the computer generate all the necessary versions of files. Being a newbie I’m sure there are ways that I can accomplish some of these tasks a little easier. As far as the number of textfields, once I convert them to applescript variables I put them in List so that I can loop through the xml creation process. Currently I have the program creating all of the necessary versions of the files but would like to add save and open functionality so that someone could reload a previous set of titles and descriptions at a later time.

Here is the actual dump_to_file function that I currently have. If I comment out everything after the 7th set of variabes(sprintCheck7) then it saves a file, if I leave them all in or go any further than 7 it doesn’t create anything and xcode says " {dumpToFile:]: No result was returned from some part of this expression. (error -2763)

 on dumpToFile_(sender)
        set theFile to choose file name with prompt "Save file as:" default name "untitled.xml"
        set theFile to posix path of (theFile as string)
        set theRec to {baseName_1: baseName1's stringValue(), title_1 : title1's stringValue(), description_1 : description1's stringValue(), yahooCheck_1: yahooCheck1's state(),sprintCheck_1: sprintCheck1's state(),baseName_2: baseName2's stringValue(), title_2 : title2's stringValue(), description_2 : description2's stringValue(), yahooCheck_2: yahooCheck2's state(),sprintCheck_2: sprintCheck2's state(), baseName_3: baseName3's stringValue(), title_3 : title3's stringValue(), description_3 : description3's stringValue(), yahooCheck_3: yahooCheck3's state(),sprintCheck_3: sprintCheck3's state(),baseName_4: baseName4's stringValue(), title_4 : title4's stringValue(), description_4 : description4's stringValue(), yahooCheck_4: yahooCheck4's state(),sprintCheck_4: sprintCheck4's state(),baseName_5: baseName5's stringValue(), title_5 : title5's stringValue(), description_5 : description5's stringValue(), yahooCheck_5: yahooCheck5's state(),sprintCheck_5: sprintCheck5's state(),baseName_6: baseName6's stringValue(), title_6 : title6's stringValue(), description_6 : description6's stringValue(), yahooCheck_6: yahooCheck6's state(),sprintCheck_6: sprintCheck6's state(),baseName_7: baseName7's stringValue(), title_7 : title7's stringValue(), description_7 : description7's stringValue(), yahooCheck_7: yahooCheck7's state(),sprintCheck_7: sprintCheck7's state(),baseName_8: baseName8's stringValue(), title_8 : title8's stringValue(), description_8 : description8's stringValue(), yahooCheck_8: yahooCheck8's state(),sprintCheck_8: sprintCheck8's state(),baseName_9: baseName9's stringValue(), title_9 : title9's stringValue(), description_9 : description9's stringValue(), yahooCheck_9: yahooCheck9's state(),sprintCheck_9: sprintCheck9's state(),baseName_10: baseName10's stringValue(), title_10 : title10's stringValue(), description_10 : description10's stringValue(), yahooCheck_10: yahooCheck10's state(),sprintCheck_10: sprintCheck10's state(),baseName_11: baseName11's stringValue(), title_11 : title11's stringValue(), description_11 : description11's stringValue(), yahooCheck_11: yahooCheck11's state(),sprintCheck_11: sprintCheck11's state(),baseName_12: baseName12's stringValue(), title_12 : title12's stringValue(), description_12 : description12's stringValue(), yahooCheck_12: yahooCheck12's state(),sprintCheck_12: sprintCheck12's state(),baseName_12: baseName12's stringValue(), title_12 : title12's stringValue(), description_12 : description12's stringValue(), yahooCheck_12: yahooCheck12's state(),sprintCheck_12: sprintCheck12's state(),baseName_14: baseName14's stringValue(), title_14 : title14's stringValue(), description_14 : description14's stringValue(), yahooCheck_14: yahooCheck14's state(),sprintCheck_14: sprintCheck14's state(),baseName_15: baseName15's stringValue(), title_15 : title15's stringValue(), description_15 : description15's stringValue(), yahooCheck_15: yahooCheck15's state(),sprintCheck_15: sprintCheck15's state(),baseName_16: baseName16's stringValue(), title_16 : title16's stringValue(), description_16 : description16's stringValue(), yahooCheck_16: yahooCheck16's state(),sprintCheck_16: sprintCheck16's state()}
        
        set theDic to current application's NSMutableDictionary's dictionaryWithDictionary_(theRec)
        theDic's writeToFile_atomically_(theFile,true)
    end

Thanks again for any assistance.

Tim

Well, I still don’t see anything that would cause this to fail. Try putting in log statements after your long record defining statement and after the “set dic to…” statement to see if either gets printed out – at least that will tell you where it’s failing.

Ric

Well it seems that as long as my record statement is over 35 variables the program quits during that statement. If it is 35 or under then it steps through and continues on with the rest of the dumptofile tasks. I’m gonna do some searching and see if there are any limits,etc. Thanks.

There may be limits, but not any where near 80. I’ve made dictionaries with a million entries. I wonder if there is something wrong with the text field (or is it a check box?) where the program hangs. That’s why I asked you to put in the log statements, to see if it gets all the way through the record definition.

Ric

Yeah that is currently my suspicion as well. I did watch it line by line and it was not making it through that definition. I’m continuing to troubleshoot and I believe I have it down to one block of the variables that seems to be tripping it up. I’m currently identifying which one or multiples it is having troubles with. I’ll let you know what I find.

Tim

Looks like I got it. Turns out it was one textField (title8) that was not linked in interface builder. All seems to be working well now. Thanks for helping me out.

Tim