Metadata in a $LIST

Although the Meta-data in a string idea will work in some cases, it has a number of significant drawbacks. Some are implementation specific, others are just problems in general with using strings to delimit key-value pairs. The biggest issue with the implementation was that keys could collide with each other depending on the order in which they were inserted. For example, if you had a key

The first issue can fixed by adding a prefix-delimiter to each key. That way you always know you are looking for a "whole" key when $PIECEing or $FINDing on the string. The second issue is just a fundamental issue with delimited strings. There is always the possibility that one of those ugly characters will slip in there unless you are explicitly checking for and escaping the appropriate characters. In the age of URLs and entity codes, which contain plenty of equal-signs and semi-colons respectively, they aren't the best delimiters. It would be nice if we didn't have to worry about delimiters at all.

An Alternative Approach
Using $LISTs as opposed to delimited strings gets around the delimiting issue almost entirely. Instead of using semi-colons and equals to delimit keys and values, we use list positions to delimit them. The list structure will look like this: $LB(key1,val1,key2,val2,key3,val3,...,keyn,valn)

All keys hold odd-numbered positions in the list, all values hold even numbered positions. To retrieve the value for key i, you simply look at position i + 1 in the list.

Drawbacks to the $LIST Approach
Keep in mind that $LISTs in Cache are still just strings. So you still have a limit of 32K characters (just use $LENGTH on a list to see how long it really is), and you can STILL have issues with certain characters throwing off functions like $LISTLENGTH. The only real loss of functionality that we have is that we can no longer easily display our ENTIRE property set on the screen, without iterating through the individual elements.

A Sample Implementation
Generally I have tried to mirror the Metadata in a list implementation so that the two implementations can be directly compared. In some cases I have modified how the functions actually behave. If anyone finds any bugs in these, feel free to drop me an email.

FindKeyPos(lst,keyname)
FindKeyPos(lst,keyname) q:((keyname="")||(lst = "")) 0 s listPos = $ListFind(lst,keyname) While((listPos > 0) && (listPos # 2 = 0)) { s listPos = $ListFind(lst,keyname,listPos) }	q listPos
 * Arguments:
 * lst: The $LIST of key-value pairs.
 * keyname: A string containing some key value to find.
 * Returns: The integer position in lst of the key-value pair with key equal to keyname. Returns 0 if no matching key is found.

SetKey(lst,keyname,keyvalue)
Inserts the key-value pair (keyname,keyvalue) into the $LIST of key-value pairs lst. If a key-value pair already exists with the key equal to keyname, the value of that key-value pair will be replaced by keyvalue. If the keyname is the empty string, the returned $LIST will be equal to lst. SetKey(lst,keyname,keyvalue) q:keyname="" lst q:lst="" $LB(keyname,keyvalue) Set listPos = $$FindKeyPos(lst,keyname) i listPos = 0 q lst _ $LB(keyname,keyvalue) Set $List(lst,listPos + 1) = keyvalue q lst
 * Arguments:
 * lst: A $LIST of key-value pairs.
 * keyname: A string containing the key of the key-value pair.
 * keyvalue: A string containing the value of the key-value pair.
 * Returns: A $LIST of key-value pairs.

GetKey(lst,keyname)
Raises a "ZKey Not Found" error if keyname is not a valid key in lst. Exists should be used to check for the existence of the key keyname in lst before GetKey is called. GetKey(lst,keyname) Set listPos = $$FindKeyPos(lst,keyname) ZTRAP:listPos=0 "ZKey Not Found" q $List(lst,listPos + 1)
 * Arguments:
 * lst: A $LIST of key-value pairs.
 * keyname: The key of the key-value pair for which the value should be returned.
 * Returns: A string which is the value of the key-value pair where the key equals keyname.

Exists(lst,keyname)
Exists(lst,keyname) q $$FindKeyPos(lst,keyname)'=0
 * Arguments:
 * lst: A $LIST of key-value pairs.
 * keyname: The key to look for..
 * Returns: True if keyname matches the key of a key-value pair in lst, false otherwise.

CountKeys(lst)
CountKeys(lst) q $LL(lst) / 2
 * Arguments:
 * Returns: The number of keys currently in lst.

DeleteKey(lst,keyname)
Removes the key-value pair with the key equal to keyname from lst. DeleteKey(lst,keyname) q:lst="" "" Set listPos = $$FindKeyPos(lst,keyname) q:listPos=0 lst q $List(lst,1,listPos - 1) _ $List(lst,listPos + 2,$LL(lst))
 * Arguments:
 * lst: A $LIST of key-value pairs.
 * keyname: The key of the key-value pair that should be removed from lst.
 * Returns: A $LIST of key-value pairs.

MergeKeys(lst1,lst2)
Merges $LISTs lst1 and lst2 by taking all key-value pairs from lst1 where the key is not found in a key-value pair in lst2 and adding those pairs to lst2. The modified lst2 is returned. MergeKeys(lst1,lst2) q:(lst1="")&&(lst2="") "" For i=1:2:$LL(lst1) { If $$FindKeyPos(lst2,$List(lst1,i)) = 0 { Set lst2 = lst2 _ $List(lst1,i,i+1) }	}	q lst2
 * Arguments:
 * lst1: A $LIST of key-value pairs.
 * lst2: A $LIST of key-value pairs.
 * Returns: A $LIST of key-value pairs.

KeyOrder(lst,keyname)
This function is meant to mimic the $ORDER function for iterating over a global. If keyname is the empty string, or equals a key that is not found in lst, then the first key in lst will be returned (or the empty string, if lst is empty). KeyOrder(lst, keyname) q:lst="" "" S listPos = $$FindKeyPos(lst,keyname) If listPos = 0 { q $List(lst,1) }	If (listPos > $LL(lst)) { q "" }	q
 * Arguments:
 * lst: A $LIST of key-value pairs.
 * keyname: The key which should preceed the next key to retrieve.
 * Returns: The next valid key after keyname in lst, or the empty string if there are no other keys.