Delete field from record

Hi,

Given building a record in this manner:

set the_rec to {}
set the_rec to the_rec & {a:1}
set the_rec to the_rec & {b:2}
set the_rec to the_rec & {c:3}
return the_rec

how can you delete field ‘a’ from the_rec?

Editted: without hacking the text of the script. That’s the last option.

Thanks,

I changed it. What if you build the record this way:

set the_rec to {}
set sub_rec to {a:1}
set the_rec to the_rec & sub_rec
set sub_rec to {b:2}
set the_rec to the_rec & sub_rec
set sub_rec to {c:3}
set the_rec to the_rec & sub_rec
return the_rec

This way you can store sub_rec. Is there a way to delete a sub record?

That’s ok. I have a solution:

property the_rec : {}
property rec_list : {}

set sub_rec to {a:1}
set end of rec_list to sub_rec
set the_rec to the_rec & sub_rec
set sub_rec to {b:2, c:3}
set end of rec_list to sub_rec
set the_rec to the_rec & sub_rec
set sub_rec to {d:4}
set end of rec_list to sub_rec
set the_rec to the_rec & sub_rec
rec_list

Now I can delete an item from rec_list and rebuild the_rec. Is there a better way to do this?

This sort of thing probably needs to be dealt with on a per-task basis. Working with record lists is super ugly, and there’s really no good way to universally manipulate them. If you have a predictable set of data, it’s easy to simply reconstruct the list from a temporary one and then replace the original list with the edited new version. The following is a modification of my examples of using script objects to handle making references to items in records. I’ve included the code to add items as well, because you need to keep track of what the key’s you’re using are (since applescript can’t do that for you :()…

property theKeyList : {}
property theRecord : {}

set theAction to (button returned of (display dialog "Select an Action:" buttons {"Cancel", "Delete Key", "Add Key"}))

if theAction is "Add Key" then
	set theKey to (text returned of (display dialog "Enter Key String:" default answer ""))
	set theValue to (text returned of (display dialog ("Enter Value for Key '" & theKey & "':") default answer ""))
	addKeyToRecordList(theKey, theValue) --> Add the new key/value pair to theRecord 
	
else if theAction is "Delete Key" then
	set theKey to choose from list theKeyList
	deleteKeyFromRecordList(theKey) --> Remove the new key/value pair from theRecord 
end if

return theRecord

(****** Subroutines ******)
to deleteKeyFromRecordList(theTargetKey)
	set theNewRecord to {}
	set theNewKeyList to {}
	
	repeat with tmpkey in theKeyList
		set tmpkey to (tmpkey as string)
		if tmpkey is not (theTargetKey as string) then
			copy tmpkey to the end of theNewKeyList
			set theNewRecord to (run script "on run{theNewRecord,theRecord}
	set theNewRecord to theNewRecord & {" & tmpkey & ":(" & tmpkey & " of theRecord)}
	return theNewRecord
end" with parameters {theNewRecord, theRecord})
		end if
	end repeat
	set theKeyList to theNewKeyList
	set theRecord to theNewRecord
end deleteKeyFromRecordList

to addKeyToRecordList(theNewKey, theNewValue)
	copy theNewKey to the end of theKeyList
	
	set theRecord to (run script "on run{theNewKey, theNewValue, theRecord}
	set theRecord to theRecord & {" & theNewKey & ":theNewValue}
	return theRecord
end" with parameters {theNewKey, theNewValue, theRecord})
	
end addKeyToRecordList

This probably isn’t going to be very fast for large or complex record lists, since not only does it have to iterate through them all, it also has to construct a script object for every item so it can rebuild the updated list. Unfortunately, this is required, because it’s really the only way to pass a string as a reference to a key. Ideally, applescript should support references like item named “blah” of theRecord or every key of theRecord. This seems like a huge hole that could be fixed with just a wee bit of work. In every other language, key-value coding is well supported… but not applescript.

Hope that gives you some insights…
j

Hi Jobu,

Thanks for replying.

I don’t understand how you delete a field from a record. Did you do that in your script?

Sorry, but I read it three times and can’t grasp how you can delete a field from a record.

I’ll read your post again tomorrow when I’m less inibriated.

The thing is, say the data is not predictable. For instance, someone may login from nowhere with some unpridictable data, so you ask the person, “Give me a label” for your data. After the person enters a label, you enter a field in the record with the person’s label and the data is entered as the fields value. This can be done.

This way you can access any data by the label without searching.

I guess that’s the ultimate goal.

But, what if the person’s stuff needs to be deleted. Then how can you delete a field from a record. Note that each field needs to be unique. You know that you can access fields of a record easily,and that’s the good part, but how can you delete fields efficiently? That’s the bad part.

I can make workarounds, but I’m thinking that there might be some trick in deleting fields of records. If anybody knows one then please tell.

Thanks,

You can’t delete properties from records any more than you can delete items from lists. You can create a new record containing all the properties of the old record minus the one you don’t want, though the new record’s structure generally needs to be known at compile-time because that’s when properties get defined.

There are hacks that allow records’ structures to be manipulated at run-time (LNS’s ‘List & Record Tools’ osax, AppleMods’ Record library), but these are fundamentally evil and subject to all sorts of caveats, limitations, uncertainties about reliability, etc. However, the only time I know of where you have to do this is when using LNS’s ‘XML Tools’ osax, which very stupidly stores element attributes as records instead of key-value lists, in which case you have to use their ‘List & Records Tools’ osax to deal with those. Most times when this question comes it, it’s because folk are using the wrong type of object for the job.

Case in point. Records are not associative lists, and trying to treat them as if they were is language abuse. If you want an associative list, use an associative list. AppleScript (unlike most other languages) may not provide a native associative list type, but they’re not that hard to write. AppleMods’ Types library contains a couple of good, robust ones you can use for free, so the easiest thing would be to just nab those.

has

Thanks for the confirmation. I couldn’t remember if there was a better way of deleting a field from a record. I fell into the trap again, trying to create dynamic fields.

gl,

I know that the task of removing a specified key-value pair from a record is most easily accomplished using AsObjC code, but I’m always more interested in a plain-AppleScript solution.

Here it is:
 

on run
	set sourceRecord to {a:1, b:2, c:{d:3}}
	set keyToRemove to "a"
	removeKey(sourceRecord, keyToRemove)
end run


--------------------------------------------- OTHER HAMDLERS -------------------------------------------------------------

on removeKey(theRecord, theKey)
	-- separate the keys and values
	tell (my recordLabelsAndValues(theRecord)) to set {theKeys, theValues} to {its recordLabels, its recordValues}
	-- build raw list
	set rawList to a reference to {}
	repeat with i from 1 to (count theKeys)
		if item i of theKeys is not theKey then tell rawList to set {end, end} to {item i of theKeys, item i of theValues}
	end repeat
	-- Create a record who is similar to records containing only user defined keys in AppleEvents
	set rawRecord to «class seld» of (record {«class usrf»:rawList} as record)
end removeKey


on recordLabelsAndValues(theRecord) -- written by user @bmose on MacScripter
	-- Returns the unpiped and piped forms of a record's labels and the text and value forms of a record's values
	-- Utility handler
	script util
		property recordLabels : {}
		property recordLabelsPiped : {}
		property recordValuesAsText : {}
		property recordValues : theRecord as list
		on representValueAsText(theValue)
			-- Parse a forced error message for the text representation of the input value
			try
				|| of {theValue}
			on error m
				set tid to AppleScript's text item delimiters
				try
					set AppleScript's text item delimiters to "{"
					set m to m's text items 2 thru -1 as text
					set AppleScript's text item delimiters to "}"
					set valueAsText to m's text items 1 thru -2 as text
				on error
					try
						-- Try an alternative method of generating a text representation from a forced error message if the first method fails
						{||:{theValue}} as null
					on error m
						try
							set AppleScript's text item delimiters to "{"
							set m to m's text items 3 thru -1 as text
							set AppleScript's text item delimiters to "}"
							set valueAsText to m's text items 1 thru -3 as text
						on error
							error "Could not get a text representation of the input value."
						end try
					end try
				end try
				set AppleScript's text item delimiters to tid
			end try
			return valueAsText
		end representValueAsText
	end script
	-- Perform the handler's actions inside a try block to capture any errors
	try
		set tid to AppleScript's text item delimiters
		-- Get the text representation of the input record
		set serializedRecord to util's representValueAsText(theRecord)
		-- Validate the text representation
		tell serializedRecord
			-- This test does not flag an input argument in the form of an Applescript list; however, a list will result in a parsing error in the code below
			if (it does not start with "{") or (it does not end with "}") then error "The input value is not a record."
			-- Handle the special case of an empty record
			if it = "{}" then return {recordLabels:{}, recordLabelsPiped:{}, recordValuesAsText:{}, recordValues:{}}
		end tell
		-- Remove the leading and trailing curly braces, and add a trailing ", " to the last record property to mimic the text pattern of the remaining properties
		set currSerializedRecord to (serializedRecord's text 2 thru -2) & ", "
		-- Parse the input record properties individually from first to last, using the text representation of the individual record values as text item delimiters
		repeat with currValue in util's recordValues
			-- Get the text representation of the current record property value
			set currSerializedValue to util's representValueAsText(currValue's contents)
			-- Consider the current record property label to be piped if the first character is a pipe
			set isPipedLabel to (currSerializedRecord starts with "|")
			-- Set the text item delimiter string to a colon (i.e., label-value separator), followed by the text representation of the record property value, followed by comma-space (i.e., property separator), optionally prefixing the entire string with a pipe if the label is piped
			if isPipedLabel then
				set currDelimiter to "|:" & currSerializedValue & ", "
				set invalidLabelChar to "|"
				set currSerializedRecord to currSerializedRecord's text 2 thru -1 -- "...text 2..." removes the leading pipe character
			else
				set currDelimiter to ":" & currSerializedValue & ", "
				set invalidLabelChar to ":"
			end if
			-- Extract the unpiped and piped versions of the current record property label
			set AppleScript's text item delimiters to currDelimiter
			tell (get currSerializedRecord's text items)
				-- Flag the input item as a non-record if (1) the text item delimiter string isn't found, (2) the property "label" contains an invalid character, or (3) the "label" is missing
				if (length = 1) or (item 1 contains invalidLabelChar) or (not isPipedLabel and (item 1 = "")) then error "The input argument theRecord does not appear to be a valid AppleScript record."
				-- Set the current record property label to the first text item, and the remaining record (still to be parsed) to the text beginning with the second text item (this will be the empty string if the current record property is the last property)
				set {currLabel, currLabelPiped} to {item 1, item 1}
				if isPipedLabel then set currLabelPiped to "|" & currLabelPiped & "|"
				set currSerializedRecord to rest as text
			end tell
			-- Save the results for the current record property
			set {end of util's recordLabels, end of util's recordLabelsPiped, end of util's recordValuesAsText} to {currLabel, currLabelPiped, currSerializedValue}
		end repeat
		if currSerializedRecord ≠ "" then error "The input argument theRecord does not appear to be a valid AppleScript record."
		-- Restore AppleScript's text item delimiters to their baseline value
		set AppleScript's text item delimiters to tid
		-- Return the results
		return {recordLabels:util's recordLabels as list, recordLabelsPiped:util's recordLabelsPiped as list, recordValuesAsText:util's recordValuesAsText as list, recordValues:util's recordValues as list}
	on error m number n
		set AppleScript's text item delimiters to tid
		if n = -128 then error number -128
		if n ≠ -2700 then set m to "(" & n & ") " & m -- -2700 = purposely thrown error
		error "Handler recordLabelsAndValues error:" & return & return & m
	end try
end recordLabelsAndValues