Array to Tabular CSV

One of the key data sources of my app is an array of dictionaries containing product information which is stored in a plist file.

I need to supply this data as a table (csv text file)

I know how to read the file in, bind it to an array controller, display it in a Table View, etc., etc. However, getting the data into the right arrangement to writeToFileAtomically is completely eluding me. I just keep getting the plist array, not tabular data, in the variable I want to write out as text.

I know it must be something incredibly simple to do, but you know how it goes.

Here is what I am trying currently.

    
on exportPIRtable_(sender)

set plistExportPath to "/Users/jhaney/Desktop/PIRexport.txt"

     set exportDictionary to current application's NSDictionary's dictionaryWithObjects_forKeys_(pirArrayController's arrangedObjects(), {"Product_Name", "Brand", "Building_Environment", "Channel", "Comments_Notes", "Department", "Geography", "Key_words", "Language", "Market_Code", "PIR_Number", "Performance_Category", "Product_Classification", "Product_Name", "Product_Related", "Reviewer", "USG_Organization", "User_Access"})

       log exportDictionary

       exportArray's writeToFileAtomically_(plistExportPath, true) 

end exportPIRtable_

However, I am getting the following error:
-[NSDictionary initWithObjects:forKeys:]: count of objects (872) differs from count of keys (1) (error -10000)

So, it thinks that my objects are in the first level of the array, when actually each of the 872 objects has a dictionary of 18 keys. I want to assemble a table with each “object” as a row, and 18 columns containing their values.

BTW: How do you indent lines within the

 tag in forum messages?

Hello,

You say that you

It means that when you read the file, you define the correct structure to get its content. Why don’t you use the same structure to write it back?

It is right, that’s exactly what you give to it: an array of 872 objects (the arrangedObjects of your controller) with a single key. You don’t have an array of dictionaries, but one dictionary with an array!

Maybe you should use a dictionary containing an array of dictionaries, like this:

(your arranged objects)
(your first record)

(your last record)

Then you could archive you root dictionary…

What do you think?

PS You can use the buttons in the editor mode (B, I, U, Quote...) to format your answer.

Regards,

Bernard,

Thanks for the response.

However, I’m still one step away from getting this.

re: “Why don’t you use the same structure to write it back?”

Because when I display the data in the table, each column is bound to a key in the each dictionary key within the array.

i.e.
contents of plist:
[Key : Type : Value]

Root : Array : 576 Items
—Item 0 : Dictionary : 2items
------Brand : String : “Mega Brand”
------Product_ID : String : “MB-11512”
—Item 1 : Dictionary : 2 items
------Brand : String : “Mega Brand”
------Product_ID : String : “MB-25335”

This is read into an array “dataArray”
and bound to an array controller “dataArrayController”

The arranged objects of “dataArrayController” is
{
—{
—Brand = “Mega Brand”;
—Product_ID = “MB-11512”;
—}
—{
—Brand = “Mega Brand”
—Product_ID = “MB-25335”
—}
}

I want:
{
Brand Product_ID
“Mega Brand” “MB-11512”
“Mega Brand” “MB-25335”
}

Well, I would simply loop thru the controller’s content and add an NSString to an NSMutableString, a bit like this:

NSMutableString *mutString = [NSMutableString stringWithCapacity:0];
// start the repeat loop
NSString *newLine = [NSString stringWithFormat:@"%@\t%@\n", [currentObject valueForKey:@"Brand"], [currentObject valueForKey:@"Product_ID"]];
[mutString appendString:newLine];
//end repeat loop

Then to remove the extra return at the end:

[mutString deleteCharactersInRange:NSMakeRange((mutString.length - 1 ), 1)];

It’s been awhile since I’ve coded in ASOC, but I think you’d get the idea. \n and \t in the string are respectively a return and tab character, in case you were wondering.

Does it help?

Leon,

Thanks for the tips, you definitely set me on the right track.

I was just blindly believing that there HAD to be a simple way to reference the tabular data using bindings.

Oh well.

In following your advice I came across one of those hidden gotchas in ASOC. Apparently “stringWithFormat” is unreliable in ASOC,

So I quickly implemented a very “AppleScripty” function that gets the job done.

    on exportPIRtable_(sender)
        set pirCSV to "PIR_Number" & tab & "Product_Name" & tab & "Brand" & tab & "Building_Environment" & tab & "Channel" & tab & "Comments_Notes" & tab & "Department" & tab & "Geography"  & tab & "Key_words" & tab & "Language" & tab & "Market_Code" & tab & "Performance_Category" & tab & "Product_Classification" & tab & "Product_Name" & tab & "Product_Related"  & tab & "Reviewer" & tab & "USG_Organization" & tab & "User_Access" & return

            repeat with currentObject in pirArrayController's arrangedObjects()
            
                set newLine to   currentObject's valueForKey_("PIR_Number") as string & tab & currentObject's valueForKey_("Product_Name") as string & tab & currentObject's valueForKey_("Brand") as string  & tab & currentObject's valueForKey_("Building_Environment") as string & tab & currentObject's valueForKey_("Channel") as string & tab & currentObject's valueForKey_("Comments_Notes") as string & tab & currentObject's valueForKey_("Department") as string & tab & currentObject's valueForKey_("Geography") as string & tab & currentObject's valueForKey_("Key_words") as string & tab & currentObject's valueForKey_("Language") as string & tab & currentObject's valueForKey_("Market_Code") as string & tab & currentObject's valueForKey_("Performance_Category") as string & tab & currentObject's valueForKey_("Product_Classification") as string & tab & currentObject's valueForKey_("Product_Name") as string & tab & currentObject's valueForKey_("Product_Related") as string & tab & currentObject's valueForKey_("Reviewer")
                
                set pirCSV to pirCSV & newLine & return
                
            end repeat

        set currPathID to open for access plistExportPath with write permission
        write pirCSV to currPathID
        close access currPathID
        
 end exportPIRtable_

Looks a bit complicated vs the cocoa way, but if it works, you’ll get no complaints from me! :slight_smile:

Odd that you mention stringWithFormat being unreliable in ASOC. Perhaps it needs NSString objects created before being used in this method. not sure. Sad, because in this case it is very useful.

I had the same problem two years ago:

Well – these are the sort of things that made me switch to Objective-C.

@ jhaneyzz : if your app does not pilot the Finder or other apps, you should really consider using Obj-C. Not only for the code readability, but also for speed if you are dealing with large tables.

This is a key independent way using the Objective-C equivalent of AppleScript text item delimiters.
it assumes that the first array entry contains all keys


on exportPIRtable_(sender)
	set contentArray to pirArrayController's arrangedObjects()
	set allKeys to contentArray's objectAtIndex_(0)'s allKeys()
	
	set csvLines to current application's NSMutableArray's array()
	csvLines's addObject_(allKeys's componentsJoinedByString_("\t"))
	repeat with aDict in contentArray
		set theArray to aDict's objectsForKeys_notFoundMarker_(allKeys, "n/a")
		csvLines's addObject_(theArray's componentsJoinedByString_("\t"))
	end repeat
	set csvParagraphs to csvLines's componentsJoinedByString_("\r")
	csvParagraphs's writeToFile_atomically_encoding_error_(plistExportPath, true, current application's NSUTF8StringEncoding, missing value)
end exportPIRtable_

Bernard,

I totally get the point:

@ jhaneyzz : if your app does not pilot the Finder or other apps, you should really consider using Obj-C. Not only for the code readability, but also for speed if you are dealing with large tables.

Unfortunately, the purpose of this app is to merge multiple data sources + business rules + Digital Assets so the users (Marketing Managers) can classify their assets, images, documents, video, etc.

Once they have identified the asset visually (using Extensis Portfolio currently) and using my app to find the right ID code, I send applescript to Portfolio to tag the assets.

Portfolio is involved really only to give the users a visual method of selecting individual or groups of assets to tag.

Coercing between AppleScript strings and OBC strings has been a real hassle.

Stefan:

You win the prize!!!

Your code worked perfectly.

I have never noticed the “objectsForKeys” function using “notFoundMarker”.

I will definitely keep this one in my tool box!

Thank you very much.

I love this forum, everyone responds so quickly, and with good information.

  • James