accessing a record in a repeat loop with a variable

I have a script that gets a simple record {|id|:“17” title:“Great page” content:“

Welcome

etc…”}
there are many items in this record which come back as an empty string and this causes an error, so in my XML-RPC server I have it send all empty strings as a string “”.

This record is being assigned to the variable “returnArray” in my script.
I then extract the keys to one list and the values to another in the hopes that I can loop through them and then change the actual content of the returnArray record.

For instance, I can manually do: set title of returnArray to “not so great page” and that works fine, but I want to loop through each item in my returnArray, check to see if it is value is equal to “” and then replace it with “”.

Here is what I have:


		set theKeys to call method "allKeys" of returnArray
		set theValues to call method "allValues" of returnArray
		
		set num to 1 as number

		repeat with i in theValues
			
			set theMsg to "KEY => " & (item num of theKeys) & " VALUE => " & (item num of theValues)
			if (item num of theValues) is equal to "<!-- empty -->" then
				display dialog theMsg
				set myKey to (item num of theKeys)
				display dialog myKey of returnArray)
			end if

			set num to num + 1 as number
		end repeat

Now I know it is getting the right data, it is finding the items in theValues list that match the “” criteria, and it is successfully pairing that with theKey of that item, so I set myKey to the value of that particular key.

But… when I go to display a dialog (just for tests, eventually will replace data here) it is taking the variable myKey as a literal for a record in return array. This is not at all what I was hoping would happen! Is there any way I can have the myKey variable parsed before it tries to find that value in returnArray so for instance is would be looking for “title” in returnArray instead of myKey?

*** Edit ***
I also tried:


		--set myKey to (item num of theKeys)
		set myReplace to (item num of theKeys) & " of returnArray"
		display dialog myReplace

which just displays the variable myReplace as text, “pageTitle of returnArray” for example, but I don’t want the text, I want the actual value associated with the “pageTitle” of the record “returnArray”. Next I tried…


		--set myKey to (item num of theKeys)
		set myReplace to (item num of theKeys) & " of returnArray"
		display dialog literal of myReplace

also tried “actual of” and “value of”.
but that generates the error Can’t get literal of “pageTitle of returnArray”

This is closer, because what I actually want to get is the pageTitle of returnArray to replace it, but this is still not working as I expected.

Any suggestions?

-sD-
Dr. Scotty Delicious, Scientist.

If I read that correctly, then you’re trying to access record names dynamically (?), like this:

set someRecord to {a:"Hello", b:"World"}

choose from list {"a", "b"} default items {"a"} without multiple selections allowed and empty selection allowed
set someKey to result
if result is false then error number -128

someKey of someRecord

Unfortunately, AppleScript records are not cool enough to support that. :confused:

Hi Scotty,

sorry , I probably forgot to mention one important thing in my last explanation: the resulting list of keys of NSDictionary’s method ‘allKeys’ is a list of strings. That’s the problem: AppleScript does not like string keys :frowning: Besides ugly workarounds it can’t handle key variables at all …
The solution is again a call method - an other method of the same class. It looks like this:

set valueForTheKey to  (call method "objectForKey:" of myDictionary with parameter theKey)

(typed here - i hope there are no typos … it’s very late here and i am too tired to try it out).

Hope that helps.

Dominik

@Bruce:
Well Bruce, what I typed in my script was:

set exampleRecordKey of exampleRecord to "new value"

then when I set a text field somewhere to that value, it works… it shows “new value”
for example, when I type:

set content of text field "pageTitle" of window "main" to (exampleRecordKey of exampleRecord)

the result in the text field when the script is run is what I would expect… the new value.

so what I want to do is to loop through the lists to find a value that is set to “” then find its associated key, then using that key, tell the script to set that key of exampleRecord to a predetermined value that I picked just like I did manually above. The problem is that that key is being stored in a variable (myKey) and the script is looking for the ACTUAL key “myKey” instead of the value that myKey represents. Of course, in my record, there is no key called myKey, so it throws an error.

@Dominik:
Ok… That sounds complicated, but as I am learning Obj-C and Cocoa at the same time as AppleScript, I am totally following along with the syntax you are mentioning.
Just to clarify though, I should use something like the code you suggested instead of " set theKeys to call method “allKeys” of returnArray", or do I need to put that in the repeat loop?

Thank you to both of you. I understand how much of an obligation it can be to pass on your knowledge and understanding to beginners, but I hope you feel a sense of reward as a look to you with admiration and say “You da man!” and that I hope I can be “the man” as well!

-sD-
Dr. Scotty Delicious, Scientist.

Hallo Scotty,

yes that’s what i meant - something like this:

set theDictionary to ... <here the command that results your record/NSDictionary>
set theKeys to (call method "allKeys:" of theDictionary)
repeat with thisKey in theKeys
if (call method "objectForKey:" of theDictionary with parameter thisKey) is "<!-- empty -->" then
-- do something with the variable thisKey
end if
end repeat

In Objective-C you could use NSDictionary’s keyEnumerator & nextObject methods to loop through the keys instead:

NSEnumerator *enumerator = [theDictionary keyEnumerator];
id thisKey;
while ((thisKey = [enumerator nextObject])) {
    if ([[theDictionary objectForKey: thisKey] isEqual:@"<!-- empty -->"]) {
        // do something with the variable thisKey
    }
}

Dominik

I won’t be able to try this until tomorrow morning, but I just wanted to say thanks and I will report back on my results. :smiley:

-sD-
Dr. Scotty Delicious, Scientist.

Well, this is definitely much closer to what I am looking for, but I am still missing something.
This is what I have so far.
the xmlrpc response fills the variable returnArray with a record that looks like {|id|:“17” pageTitle:“My Web Page Title” longTitle:“Another great page from Scotty Delicious” ect…}


		set theKeys to (call method "allKeys" of returnArray)
		set theValues to (call method "allValues" of returnArray)
		
		set num to 1 as number
		repeat with i in theValues
			
			set theMsg to "KEY => " & (item num of theKeys) & " VALUE => " & (item num of theValues)
			if (item num of theValues) is equal to "<!-- empty -->" then
				display dialog theMsg
				set myReplace to (call method "objectForKey:" of returnArray with parameter (item num of theKeys))
				display dialog myReplace
			end if

			set num to num + 1 as number
		end repeat

Now this is starting to cook! myReplace is actually getting the right data! that is awesome. But here is the reason I want to get the data… Because I want to modify it. So, basically I want to set the value of the object that myReplace represents to “” (blank) instead of “”

If I script


set MyReplace to ""

obviously it just forgets about the object that WAS there and replaces the value of myReplace with an empty string. So, myReplace was eqaual to the object of (pageTitle of returnArray) which had a value of “”, but instead of setting (pageTitle of returnArray) to “”, I just gave myReplace a new value.

So next I tried:


set  (call method "objectForKey:" of theValues with parameter (item num of theKeys)) to ""

But that will not build and run, so I am guessing that it is not allowed.

So what I could do is just have a really long list of if then statements instead of trying to do this automatically in a loop.


if (pageTitle of returnArray) is "<!-- empty -->" then
    set (pageTitle of returnArray) to ""
end if
if (longTitle of returnArray) is "<!-- empty -->" then
    set (longTitle of returnArray) to ""
end if
if (linkAttr of returnArray) is "<!-- empty -->" then
    set (linkAttr of returnArray) to ""
end if
if (docContent of returnArray) is "<!-- empty -->" then
    set (docContent of returnArray) to ""
end if

etc.... etc... etc...

And do this for all 30 some items of the returnArray record. I just thought it may be possible to do it with less code using a repeat block. My understanding of AppleScript and its capabilities and limitations as a scripting language is far from complete. For all I know, what I am trying to do may not even be possible with AppleScript.

-sD-
Dr. Scotty Delicious, Scientist.

Hi Scotty,

without having read your post thorroughly (sorry I am a little bit in hurry now, but i will do later) - here a first attempt to help …

EDIT: …sorry, this post was totally nonsense. I should not respond when I am in hurry … I will rethink this problem and answer later

Hi again, Scotty,

I guess you are right - there is no (nice) solution to solve this from AppleScript. So let’s use a little Objective-C …

i suggest to use an NSDictionary category:

here is how to:

add these two files in your project (name doesn’t matter):

File ‘MyDictionaryAdditions.h’:

[code]#import <Cocoa/Cocoa.h>

@interface NSDictionary (Additions)
+(NSDictionary *)dictionaryByReplacingObject:(id)obj withObject:(id)replacement inDictionary:(NSDictionary *) sourceDict;
@end[/code]
File ‘MyDictionaryAdditions.m’:

[code]#import “MyDictionaryAdditions.h”

@implementation NSDictionary (Additions)

+(NSDictionary *)dictionaryByReplacingObject:(id)obj withObject:(id)replacement inDictionary:(NSDictionary *) sourceDict{
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] initWithDictionary:sourceDict]; // copy sourceDict over in a mutable dictionary
NSArray *allKeys = [tempDict allKeysForObject:obj]; // gets all keys whose object is obj
NSEnumerator *keyEnumerator = [allKeys objectEnumerator];
id key;
while(key = [keyEnumerator nextObject]) {
[tempDict setObject:replacement forKey:key]; // Loop and replace all ocurrences of obj
}
NSDictionary *newDictionary = [[NSDictionary alloc] initWithDictionary]; // copy the mutable dict back to a regular dictionary
[tempDict release]; // free memory
return newDictionary; // give it back to the calling method
}

@end[/code]
And here an example, how to use it:

set theDict to {|id|:"17", |pageTitle|:"My Web Page Title", |longTitle|:"Another great page from Scotty Delicious", |nextKey|:"<!-- empty -->", |anotherKey|:"<!-- empty -->", |oneMoreKey|:"value", |lastKey|:"<!-- empty -->"}
log theDict
set newDict to (call method "dictionaryByReplacingObject:withObject:inDictionary:" of class "NSDictionary" with parameters {"<!-- empty -->", "", theDict})
log newDict

Regards,

Dominik

Dominik, that works brilliantly!

I have added it to my AS Studio application, but before I go any further, I am going to RTFM on NSDictionary so I understand what is going on with each step. Obviously your comments in the code are incredibly helpful, so I understand what the code is doing, I just want to understand it to the point where I can use something like it again in the future if/when the need arises.

Thanks Dominik, you are a the professor!

-sD-
Dr. Scotty Delicious, Scientist.