0001"""
0002Takes GET/POST variable dictionary, as might be returned by ``cgi``,
0003and turns them into lists and dictionaries.
0004
0005Keys (variable names) can have subkeys, with a ``.`` and
0006can be numbered with ``-``, like ``a.b-3=something`` means that
0007the value ``a`` is a dictionary with a key ``b``, and ``b``
0008is a list, the third(-ish) element with the value ``something``.
0009Numbers are used to sort, missing numbers are ignored.
0010
0011This doesn't deal with multiple keys, like in a query string of
0012``id=10&id=20``, which returns something like ``{'id': ['10',
0013'20']}``. That's left to someplace else to interpret. If you want to
0014represent lists in this model, you use indexes, and the lists are
0015explicitly ordered.
0016
0017If you want to change the character that determines when to split for
0018a dict or list, both variable_decode and variable_encode take dict_char
0019and list_char keyword args. For example, to have the GET/POST variables,
0020``a_1=something`` as a list, you would use a list_char='_'.
0021"""
0022
0023import api
0024
0025__all__ = ['variable_decode', 'variable_encode', 'NestedVariables']
0026
0027def variable_decode(d, dict_char='.', list_char='-'):
0028 """
0029 Decodes the flat dictionary d into a nested structure.
0030 """
0031 result = {}
0032 dicts_to_sort = {}
0033 known_lengths = {}
0034 for key, value in d.items():
0035 keys = key.split(dict_char)
0036 new_keys = []
0037 was_repetition_count = False
0038 for key in keys:
0039 if key.endswith('--repetitions'):
0040 key = key[:-len('--repetitions')]
0041 new_keys.append(key)
0042 known_lengths[tuple(new_keys)] = int(value)
0043 was_repetition_count = True
0044 break
0045 elif list_char in key:
0046 key, index = key.split(list_char)
0047 new_keys.append(key)
0048 dicts_to_sort[tuple(new_keys)] = 1
0049 new_keys.append(int(index))
0050 else:
0051 new_keys.append(key)
0052 if was_repetition_count:
0053 continue
0054
0055 place = result
0056 for i in range(len(new_keys)-1):
0057 try:
0058 if not isinstance(place[new_keys[i]], dict):
0059 place[new_keys[i]] = {None: place[new_keys[i]]}
0060 place = place[new_keys[i]]
0061 except KeyError:
0062 place[new_keys[i]] = {}
0063 place = place[new_keys[i]]
0064 if place.has_key(new_keys[-1]):
0065 if isinstance(place[new_keys[-1]], dict):
0066 place[new_keys[-1]][None] = value
0067 elif isinstance(place[new_keys[-1]], list):
0068 if isinstance(value, list):
0069 place[new_keys[-1]].extend(value)
0070 else:
0071 place[new_keys[-1]].append(value)
0072 else:
0073 if isinstance(value, list):
0074 place[new_keys[-1]] = [place[new_keys[-1]]]
0075 place[new_keys[-1]].extend(value)
0076 else:
0077 place[new_keys[-1]] = [place[new_keys[-1]], value]
0078 else:
0079 place[new_keys[-1]] = value
0080
0081 to_sort_keys = dicts_to_sort.keys()
0082 to_sort_keys.sort(lambda a, b: -cmp(len(a), len(b)))
0083 for key in to_sort_keys:
0084 to_sort = result
0085 source = None
0086 last_key = None
0087 for sub_key in key:
0088 source = to_sort
0089 last_key = sub_key
0090 to_sort = to_sort[sub_key]
0091 if to_sort.has_key(None):
0092 noneVals = [(0, x) for x in to_sort[None]]
0093 del to_sort[None]
0094 noneVals.extend(to_sort.items())
0095 to_sort = noneVals
0096 else:
0097 to_sort = to_sort.items()
0098 to_sort.sort()
0099 to_sort = [v for k, v in to_sort]
0100 if known_lengths.has_key(key):
0101 if len(to_sort) < known_lengths[key]:
0102 to_sort.extend(['']*(known_lengths[key] - len(to_sort)))
0103 source[last_key] = to_sort
0104
0105 return result
0106
0107def variable_encode(d, prepend='', result=None, add_repetitions=True,
0108 dict_char='.', list_char='-'):
0109 """
0110 Encodes a nested structure into a flat dictionary.
0111 """
0112 if result is None:
0113 result = {}
0114 if isinstance(d, dict):
0115 for key, value in d.items():
0116 if key is None:
0117 name = prepend
0118 elif not prepend:
0119 name = key
0120 else:
0121 name = "%s%s%s" % (prepend, dict_char, key)
0122 variable_encode(value, name, result, add_repetitions,
0123 dict_char=dict_char, list_char=list_char)
0124 elif isinstance(d, list):
0125 for i in range(len(d)):
0126 variable_encode(d[i], "%s%s%i" % (prepend, list_char, i), result,
0127 add_repetitions, dict_char=dict_char, list_char=list_char)
0128 if add_repetitions:
0129 if prepend:
0130 repName = '%s--repetitions' % prepend
0131 else:
0132 repName = '__repetitions__'
0133 result[repName] = str(len(d))
0134 else:
0135 result[prepend] = d
0136 return result
0137
0138class NestedVariables(api.FancyValidator):
0139
0140 def _to_python(self, value, state):
0141 return variable_decode(value)
0142
0143 def _from_python(self, value, state):
0144 return variable_encode(value)