""" VariableDecode.py Ian Bicking Takes GET/POST variable dictionary, as might be returned by `cgi`, and turns them into lists and dictionaries. Keys (variable names) can have subkeys, with a ``.`` and can be numbered with ``-``, like ``a.b-3=something`` means that the value ``a`` is a dictionary with a key ``b``, and ``b`` is a list, the third(-ish) element with the value ``something``. Numbers are used to sort, missing numbers are ignored. This doesn't deal with multiple keys, like in a query string of ``id=10&id=20``, which returns something like ``{'id': ['10', '20']}``. That's left to someplace else to interpret. If you want to represent lists in this model, you use indexes, and the lists are explicitly ordered. """ import validators def variableDecode(d): """ Decodes the flat dictionary d into a nested structure. """ result = {} dictsToSort = {} knownLengths = {} for key, value in d.items(): keys = key.split('.') newKeys = [] wasRepetitionCount = False for key in keys: if key.endswith('--repetitions'): key = key[:-len('--repetitions')] newKeys.append(key) knownLengths[tuple(newKeys)] = int(value) wasRepetitionCount = True break elif '-' in key: key, index = key.split('-') newKeys.append(key) dictsToSort[tuple(newKeys)] = 1 newKeys.append(int(index)) else: newKeys.append(key) if wasRepetitionCount: continue place = result for i in range(len(newKeys)-1): try: if isinstance(place[newKeys[i]], (str, unicode, list)): place[newKeys[i]] = {None: place[newKeys[i]]} place = place[newKeys[i]] except KeyError: place[newKeys[i]] = {} place = place[newKeys[i]] if place.has_key(newKeys[-1]): if isinstance(place[newKeys[-1]], dict): place[newKeys[-1]][None] = value elif isinstance(place[newKeys[-1]], list): if isinstance(value, list): place[newKeys[-1]].extend(value) else: place[newKeys[-1]].append(value) else: if instance(value, list): place[newKeys[-1]] = [place[newKeys[-1]]] place[newKeys[-1]].extend(value) else: place[newKeys[-1]] = [place[newKeys[-1]], value] else: place[newKeys[-1]] = value toSortKeys = dictsToSort.keys() toSortKeys.sort(lambda a, b: -cmp(len(a), len(b))) for key in toSortKeys: toSort = result source = None lastKey = None for subKey in key: source = toSort lastKey = subKey toSort = toSort[subKey] if toSort.has_key(None): noneVals = [(0, x) for x in toSort[None]] del toSort[None] noneVals.extend(toSort.items()) toSort = noneVals else: toSort = toSort.items() toSort.sort() toSort = [v for k, v in toSort] if knownLengths.has_key(key): if len(toSort) < knownLengths[key]: toSort.extend(['']*(knownLengths[key] - len(toSort))) source[lastKey] = toSort return result def variableEncode(d, prepend='', result=None): """ Encodes a nested structure into a flat dictionary. """ if result is None: result = {} if isinstance(d, dict): for key, value in d.items(): if key is None: name = prepend elif not prepend: name = key else: name = "%s.%s" % (prepend, key) variableEncode(value, name, result) elif isinstance(d, list): for i in range(len(d)): variableEncode(d[i], "%s-%i" % (prepend, i), result) if prepend: repName = '%s--repetitions' % prepend else: repName = '__repetitions__' result[repName] = str(len(d)) else: result[prepend] = d return result class NestedVariables(validators.FancyValidator): protocol = ['http'] def _toPython(self, value, state): return variableDecode(value) def _fromPython(self, value, state): return variableEncode(value)