User record property labels as text

regulus633 has demonstrated a short method of getting text representations of user record property labels. (If you need to do this, you’re not using AppleScript properly; but the question keeps coming up.)

set theRecord to {firstName:"John", lastName:"Smith"}

tell application "Automator Runner"
	set keysList to call method "allKeys" of theRecord
end tell

If the record contains any reserved labels, the script renders them in the returned list as the integer values of their four-byte compiled tokens.

It’s rather irksome having to script an application just for this purpose, especially if you don’t normally have the application open. The time taken to launch it and quit it can make the script quite slow. So here’s a script which uses vanilla methods to the same effect. It uses the File Read/Write commands to write the record to a temporary file and to parse the file’s contents:

on allKeys(rec)
	script o
		property keyList : {}
		
		-- Parse the temporary file for top-level record labels.
		on parseAllLabels(fRef) -- Enter here having skipped "reco" at the beginning of the file.
			-- The next four bytes give the number of reserved-label properties in the record. Properties with user labels are grouped togther as a list value with the reserved label "usrf".
			repeat (read fRef for 4 as integer) times
				set labelKey to (read fRef for 4 as integer) -- Get each reserved label as a 4-byte integer value. 
				if (labelKey is 1.970500198E+9) then -- If it's "usrf", extract the user label(s).
					parseUserLabels(fRef)
				else -- Otherwise store the integer and wind on to the next label.
					set end of my keyList to labelKey
					skipValue(fRef)
				end if
			end repeat
		end parseAllLabels
		
		-- Extract user labels from the "usrf" list.
		on parseUserLabels(fRef) -- Enter here having just read "usrf".
			read fRef for 4 -- Skip the next four bytes ("list").
			-- The four bytes after that give the number of items in the list. The odd-numbered items are the labels and the even-numbered ones the values.
			repeat (read fRef for 4 as integer) div 2 times
				set labelTextClass to (read fRef for 4 as string)
				set labelByteLength to (read fRef for 4 as integer)
				if (labelTextClass is "utxt") then -- Unicode text in Snow Leopard (and Leopard?).
					set end of my keyList to (read fRef for labelByteLength as Unicode text)
				else -- "TEXT" in Tiger.
					set end of my keyList to (read fRef for labelByteLength as string)
					-- Skip any padding of the label in the file to an even number of bytes.
					read fRef for (labelByteLength mod 2)
				end if
				skipValue(fRef)
			end repeat
		end parseUserLabels
		
		-- Recursively coax the file mark past the value of a property in the file,
		on skipValue(fRef)
			set valueClass to (read fRef for 4 as string) -- Get the value class code.
			if ((valueClass is "reco") or (valueClass is "list")) then
				repeat (read fRef for 4 as integer) times -- Skip each entry in a subordinate record or list.
					if (valueClass is "reco") then read fRef for 4 -- Skip the reserved label if in a record.
					skipValue(fRef)
				end repeat
			else
				set valueByteLength to (read fRef for 4 as integer)
				read fRef for (valueByteLength + valueByteLength mod 2) -- Skip the value and any padding.
			end if
		end skipValue
	end script
	
	-- Open a temporary file to which to write the record and from which to parse it.
	set fRef to (open for access file ((path to temporary items as Unicode text) & "Rec.dat") with write permission)
	try
		set eof fRef to 0
		write rec to fRef
		read fRef from 5 for 0 -- Set the file mark to 5 to skip the opening "reco".
		o's parseAllLabels(fRef) -- Parse the labels.
	on error msg
		display dialog msg
	end try
	close access fRef
	
	return o's keyList
end allKeys

set theRecord to {firstName:"John", otherNames:{"Aardvark", "Lazenby"}, lastName:"Smith"}
allKeys(theRecord)

The script advances the file mark past information it doesn’t need by simply reading the information from the file (and then ignoring it). It might be more efficient to maintain an ongoing calculation of the file mark and just read what’s actually needed with the help of 'read’s ‘from’ parameter.

That’s pretty amazing Nigel. It really shows a lot of knowledge. I’m impressed.