"""Translate a ``cgi.FieldStorage`` into a (possibly recursive) dict."""
__docformat__ = "restructuredtext"
# Created: Wed May 5 13:45:45 PDT 2004
# Author: Shannon -jj Behrens
# Email: jjinux@users.sourceforge.net
#
# Copyright (c) Shannon -jj Behrens. All rights reserved.
import re
import types
from aquarium.util.InternalLibrary import FormValueError
class FormDict(dict):
"""Translate a ``cgi.FieldStorage`` into a (possibly recursive) dict.
If you've used ``cgi.FieldStorage`` from the Python standard library,
you've probably translated it into a dict, which has a more friendly API.
Then, at some point (either before or after you wrote the translation), you
probably realized why ``cgi.FieldStorage`` is the way it is--because the
same field can have multiple values.
This code works around this requirement in a more friendly way that is
inspired by PHP. Specifically:
* A field named ``foo`` cannot have multiple values. If multiple values
are encountered, a aquarium.util.InternalLibrary.FormValueError_ is
raised (in the constructor).
* If you actually wish for ``foo`` to have multiple values, name the
HTML fields ``foo[]``. The key ``foo`` within this dict will
automatically have a list for its value.
* If your form had the elements ``field[1][1]=="bob"`` and
``field[1][2]=="junk"`` the resultant dict would be what you expect:
``{'field': {'1': {'1': 'bob'}, {'2': 'junk'}}``.
* Something of the form ``field[][0]`` is unsupported.
XXX Below here doesn't actually work and is more of a TODO:
* If you name an HTML field something like ``foo[bar]``, then the
key ``foo`` within this dict will automatically have a dict (actually a
FormDict) containing the key ``bar`` for its value.
* You can perform the above recursively. I.e. the following is
valid: ``foo[bar][][bat]``.
* In the same way that a simple field like ``foo`` cannot have multiple
values without raising a ``FormValueError``, if you mix field types, such
as naming one field ``foo[]`` and another ``foo[bar]``, you'll get a
``FormValueError``. It is expected that you should only get this error
if:
1) you as the programmer mess up.
2) the user is maliciously adding fields to your perfect form. Hence, an
exception is indeed appropriate.
The following attributes are used:
fieldStorage
This is the original ``cgi.FieldStorage`` object, just in case
you actually need it. I won't even use it outside of the constructor.
.. _aquarium.util.InternalLibrary.FormValueError:
aquarium.util.InternalLibrary.FormValueError-class.html
"""
def __init__(self, fieldStorage):
"""Accept a ``cgi.FieldStorage`` as an initializer."""
dict.__init__(self)
self.fieldStorage = fieldStorage
self.reIndex = re.compile(r"\[([\w\s]+)\]")
for key in fieldStorage.keys():
if key.endswith("[]"):
try:
self[key[:-len("[]")]] = [x.value
for x in fieldStorage[key]]
except TypeError:
self[key[:-len("[]")]] = [fieldStorage[key].value]
elif self.reIndex.search(key):
(idxs, baseKey) = self.extractIndexes(key)
if not self.has_key(baseKey):
self[baseKey] = {}
self[baseKey].update(
self.buildDict(idxs, fieldStorage[key].value,
self[baseKey]) )
else:
if isinstance(fieldStorage[key], types.ListType):
raise FormValueError("""\
There is more than one field named '%s'. Use '%s[]' instead.""" % (key, key))
self[key] = fieldStorage[key].value
def extractIndexes(self, key):
idxs = []
m = self.reIndex.search(key)
baseKey = key[:m.start(1)-1]
while m:
idxs.append(m.group(1))
key = key[m.end():]
m = self.reIndex.search(key)
return (idxs, baseKey)
def buildDict(self, idxs, val, thardict):
"""Takes a list of idxs to follow down the nested dicts contained
in thardict where it will insert val.
idxs
The list of indexes to follow through the dict
val
The value to insert at the end of the indexes
thardict
The dict to use. Keys will be created.
"""
if idxs:
if thardict.has_key(idxs[0]):
thardict[idxs[0]].update(self.buildDict(
idxs[1:], val, thardict[idxs[0]]))
else:
thardict[idxs[0]] = self.buildDict(
idxs[1:], val, {})
return thardict
else:
return val
|