Obtaining Property references from a list of strings?

I’m running my head against a wall, but I think it’s that I’m overlooking some king of coerced class directive or something in the way I’m doing this…

The script is retrieving object properties for records in Devonthink. I’d like to create a list that can do two things: specify the properties to be retrieved using a list of strings. The initial strings would be the combination of the desired property name and a description of the data format

For example:
set myfancylist to {“addition date#date”, “modified date#date”, “creation date#date”, “type#set{option 1|option 2|option3}”, “mdtotal#real{format#currency}”}

I can handle converting the list to this:
set mypropertylist to {addition date, modified date, creation date, type, mdtotal}

Within the appropriate tell or using terms block, AppleScript recognizes those as properties….

If I type:
set mycreationdate to creation date of mycurrentrecord

It works great! But it all falls apart when I try to iterate the list:

set mycreationdate to (item x of mypropertylist) of mycurrentrecord

AppleScript is seeing a string (not a property) for “item x of mypropertylist”…. Is there a magic phrase to force the property there? Or, is there a better way?

Try this

tell application "DevonThink"
set {additiondate, modifieddate, creationdate, rectype, mdtotal} to {addition date of mycurrentrecord, modified date of mycurrentrecord, creation date of mycurrentrecord, type of mycurrentrecord, mdtotal of mycurrentrecord}
end tell

or shorter version

tell application "DevonThink"
set {additionDate, modifiedDate, creationDate, recType, mTotal} to {addition date, modified date, creation date, type, mdtotal} of mycurrentrecord
end tell

I don’t have Devon Think to test

1 Like

You can’t iterate records, which properties are. You can iterate through them if you first coerce them into a list, but then you have a list.

@robertfern
I’m running an older version but things are mostly the same. I omitted mdtotal as it’s not in the v2 dictionary but otherwise, this is what it might return.

set mcr to content id 123456 of database id 2
set {additiondate, creationdate, modifieddate, rectype} ¬
    to {addition date of mcr, creation date of mcr, ¬
    modification date of mcr, type of mcr}

--> {date "Wednesday, February 15, 2023 at 15:53", date "Wednesday, February ¬
-- 	15, 2023 at 15:54", date "Wednesday, February 15, 2023 at 15:55", formatted note}
1 Like

Did you try to take ‘of mcr’ out of the braces to shorten the line like I did in the second version above?

You’re trying to access a value of a record with a key defined as a variable, AppleScript doesn’t allow you to do this. It can be done, but you need to use AppleScriptObjC to do it.

use framework "Foundation"

on run
	
	set mycurrentrecord to {demo:"Hello World", weather:"Sunny"}
	
	set example1 to getValueFromRecord(mycurrentrecord, "demo")
	
	set keyVariable to "weather"
	set example2 to getValueFromRecord(mycurrentrecord, keyVariable)
	
end run

on getValueFromRecord(theRecord, theKey)
	set ca to current application
	set theValue to (ca's NSDictionary's dictionaryWithDictionary:theRecord)'s objectForKey:theKey
	if missing value ≠ theValue then
		return item 1 of ((ca's NSArray's arrayWithObject:theValue) as list)
	else
		return missing value
	end if
end getValueFromRecord
2 Likes

Okay — this might be workable. I could realize that the issue was that AS was seeing a string every time and when I’d try to force the variable to a key name, it wasn’t having any of that! Hahahaha

The bad part of this is that I know no obj-c! LOL. I’ve managed to avoid it all these years except for an occasional copy/paste hack here and there! But I’ve been tempted to learn some so I can access the app kit and core libraries, so maybe it’s time.

My goal here is to make a script object that can extract an arbitrary set of values based on what’s passed to it, and return a record of just that data. So it required trying abstraction of the set of keys and then applying only relevant keys from what is passed.

I initially tried get properties of myrecord And that successfully returns a record with all key-value pairs…. But when I tried getting only what I need from that result, it was having the same issue.

Forcing the record to a string is a mess, partly because many of the keys have spaces in them (such as “addition date” and “custom meta data”).

Trouble with forcing a record to list is the order…. And missing values in sub records. Sub records have arbitrary order, etc.

This gives me an idea…. Thank you! Will try.

Not for the OP and just as a point of information, I think this can be done as shown below, although it requires that the record be made into a string. Shai1’s ASObjC solution is way better.

set mycurrentrecord to "{demo:\"Hello World\", weather:\"Sunny\"}"
set keyVariable to "demo"
set example1 to run script keyVariable & " of " & mycurrentrecord --> "Hello World"
set keyVariable to "weather"
set example2 to run script keyVariable & " of " & mycurrentrecord --> "Sunny"

thanks – yeah, I need to add some logic to deal with nested records in the metedata – property lists that are nested within the top level of properties. dealing with indexes made me wonder if using a database event and making a scratch database to do the analysis and cherry picking might be easier.

Your solution is similar to one I was thinking of – an associative list whereby I could place the labels into a record property as a value, something list this {the_property:"addition date", the_value:myvariable, the_user_requested:true_false, the_record:myrecord} So there’d need to be a hardwired set routine that has the property name retrieved and set for each record, such that it’d be a mess.

The problem with that associative list, as I see it, is that it requires several repeat loops to find, set, and retrieve… but that’s the joy of applescript – working around queries … I’m too dumb with javascript to know whether javascript can more easily navigate a record list like that without iterating every item.

Between this solution and that of @Shai1 above, I have some good tools to work with here and wrangle out

@JBManos - The ASObjC code I supplied above is complete and will let you grab any value of a record by it’s key value, even if that key value is in a variable.

Additionally, you can use simliar ASObjC code to get a list of all the top level keys of the record, if you want to be able to loop through ALL the keys instead of working off some other list of keys.

Lastly, I wanted to mention, if you have sub records in the record, you can use the same ASObjC code against a sub record to get it’s keys and/or values. In my example code, you would just change the record you are passing into the handler. Let me know if you want to see any examples of those options.

1 Like

@Shai1 -do you recommend any particular resource to learn more about the ASObjC methods and programming?

I can sometimes wrap my head around object oriented methods, but I’ve always been procedural (mostly applescript, fortran77, dabbling of php, and some hacking by way of JS here as there).

@JBManos - Honestly, I’m just learning ASObjC myself. I purchased Shane Stanley’s book “Everyday AppleScriptObjC” as well as trying to read and understand existing code online. I’m a “hands on” kind of learner, so attempting something and looking at existing functional code is how I learn the best.

1 Like

Thanks! I hadn’t realized that Shane’s book was there! I went and got it also. So far, so good!

Best bonus: a filter for lists!!! LOL

Shane Stanley a long time ago posted a wrapper lib around ASObjC NSMutableDictionary which lets you iterate over keys etc. etc.
You can get it (among several others) from here:

https://github.com/AppleScript-Library-Project/library-listing

I can avoid creating associative lists using plain-AppleScript (as well as using AsObjC). For those who likes plain-AppleScript solutions, here it is. The solution is shown on Info For command’s result record - (info for (path to desktop folder)):

set propertiesList to {"creation date", "modification date", "size"}
set propertiesList to "{" & (my convertList:propertiesList toTEXTbyDelimiter:", ") & "}"

run script propertiesList & " of (info for (path to desktop folder))"
-- return item 2 of result -- uncomment to test

on convertList:theList toTEXTbyDelimiter:theDelimiter
	set ATID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	set theText to theList as text
	set AppleScript's text item delimiters to ATID
	return theText
end convertList:toTEXTbyDelimiter:

.
Other example: get the certain property:

set propertiesList to {"creation date", "modification date", "size"}

run script item 3 of propertiesList & " of (info for (path to desktop folder))"
1 Like

I’d do it in JavaScript.

const fancyList = {
'addition date': 'Date',
'modified date': 'Date',
'creation date': 'Date',
'type': ['option1', 'option2', 'option3'],
'mdtotal': 'Number'
}
const propertyList = Object.keys(fancyList);
const mycreationDate = currentRecord[propertyList[x]]();
/* where x is the index into propertyList */
}

There’s more on scripting DT with JavaScript here Scripting with JXA | JavaScript for Automation (JXA)

‘Wanted to report that this works for some items — some properties of the Devonthink records show up as green-colored labels in script editor.

Other record properties show up as PURPLE. It appears that this method will not get the value of any record property that appears in purple colored text in script editor, so I’m confused. The result will show the keys in the record, but this method won’t grab the value for those purple colored key labels…

So example: {PDF annotations:“3”, mdItemName: “myName.txt”}

Any ideas?

@JBManos

In some cases key names need to be surrounded with pipes. For example, if the key name is a protected word like “name” or “first”

use framework "Foundation"

on run
	
	--set mycurrentrecord to {demo:"Hello World", weather:"Sunny"}
	set mycurrentrecord to {|PDF annotations|:3, mdItemName:"myName.txt"}
	set example1 to getValueFromRecord(mycurrentrecord, "PDF annotations")
	log example1
	set keyVariable to "mdItemName"
	set example2 to getValueFromRecord(mycurrentrecord, keyVariable)
	log example2
end run

on getValueFromRecord(theRecord, theKey)
	set ca to current application
	set theValue to (ca's NSDictionary's dictionaryWithDictionary:theRecord)'s objectForKey:theKey
	if missing value ≠ theValue then
		return item 1 of ((ca's NSArray's arrayWithObject:theValue) as list)
	else
		return missing value
	end if
end getValueFromRecord

@Shai1 you got me going in the right direction with this thread.

Turns out the purple labels I was seeing require implicit referencing.

Over on the Devonthink discourse, someone had a snippet that could use a string to build an applescript, compile and execute it in the running script. I was able to adapt that to use the variable name in the script, run it and get the implicit property value returned.

Works a charm - check out the clunky mess of it I made: Rich Text in Sheet Cells - #11 by jbmanos - Automation - DEVONtechnologies Community

@chrillek helped in both threads also, but the problem is that I don’t know JXA and the rest of the script - at this point a giant beauty — is all applescript and I couldn’t find a way to run JXA from an applescript. Actually, the snippet I linked to probably can do it but since the solution works well and very fast here — it churns through 1700 records and about 30 values of properties in around 5 minutes on an i7 MacBook. That’s good enough for me.