0001"""Utility functions used by various web helpers"""
0002import cgi
0003import copy
0004import sys
0005from UserDict import DictMixin
0006from xml.sax.saxutils import XMLGenerator
0007
0008def html_escape(s):
0009    """HTML-escape a string or object
0010    
0011    This converts any non-string objects passed into it to strings
0012    (actually, using ``unicode()``).  All values returned are
0013    non-unicode strings (using ``&#num;`` entities for all non-ASCII
0014    characters).
0015    
0016    None is treated specially, and returns the empty string.
0017    """
0018    if s is None:
0019        return ''
0020    if not isinstance(s, basestring):
0021        if hasattr(s, '__unicode__'):
0022            s = unicode(s)
0023        else:
0024            s = str(s)
0025    s = cgi.escape(s, True)
0026    if isinstance(s, unicode):
0027        s = s.encode('ascii', 'xmlcharrefreplace')
0028    return s
0029
0030class Partial(object):
0031    """partial object, which will be in Python 2.5"""
0032    def __init__(*args, **kw):
0033        self = args[0]
0034        self.fn, self.args, self.kw = (args[1], args[2:], kw)
0035
0036    def __call__(self, *args, **kw):
0037        if kw and self.kw:
0038            d = self.kw.copy()
0039            d.update(kw)
0040        else:
0041            d = kw or self.kw
0042        return self.fn(*(self.args + args), **d)
0043
0044class SimplerXMLGenerator(XMLGenerator):
0045    def addQuickElement(self, name, contents=None, attrs={}):
0046        """Convenience method for adding an element with no children"""
0047        self.startElement(name, attrs)
0048        if contents is not None:
0049            self.characters(contents)
0050        self.endElement(name)
0051
0052class UnicodeMultiDict(DictMixin):
0053    """
0054    A MultiDict wrapper that decodes returned values to unicode on the
0055    fly. Decoding is not applied to assigned values.
0056
0057    The key/value contents are assumed to be ``str``/``strs`` or
0058    ``str``/``FieldStorages`` (as is returned by the paste.request.parse_
0059    functions).
0060
0061    Can optionally also decode keys when the ``decode_keys`` argument is
0062    True.
0063
0064    ``FieldStorage`` instances are cloned, and the clone's ``filename``
0065    variable is decoded. Its ``name`` variable is decoded when ``decode_keys``
0066    is enabled.
0067
0068    """
0069    def __init__(self, multi=None, encoding=None, errors='strict',
0070                 decode_keys=False):
0071        self.multi = multi
0072        if encoding is None:
0073            encoding = sys.getdefaultencoding()
0074        self.encoding = encoding
0075        self.errors = errors
0076        self.decode_keys = decode_keys
0077
0078    def _decode_key(self, key):
0079        if self.decode_keys:
0080            key = key.decode(self.encoding, self.errors)
0081        return key
0082
0083    def _decode_value(self, value):
0084        """
0085        Decode the specified value to unicode. Assumes value is a ``str`` or
0086        `FieldStorage`` object.
0087
0088        ``FieldStorage`` objects are specially handled.
0089        """
0090        if isinstance(value, cgi.FieldStorage):
0091            # decode FieldStorage's field name and filename
0092            value = copy.copy(value)
0093            if self.decode_keys:
0094                value.name = value.name.decode(self.encoding, self.errors)
0095            value.filename = value.filename.decode(self.encoding, self.errors)
0096        else:
0097            try:
0098                value = value.decode(self.encoding, self.errors)
0099            except AttributeError:
0100                pass
0101        return value
0102
0103    def __getitem__(self, key):
0104        return self._decode_value(self.multi.__getitem__(key))
0105
0106    def __setitem__(self, key, value):
0107        self.multi.__setitem__(key, value)
0108
0109    def add(self, key, value):
0110        """
0111        Add the key and value, not overwriting any previous value.
0112        """
0113        self.multi.add(key, value)
0114
0115    def getall(self, key):
0116        """
0117        Return a list of all values matching the key (may be an empty list)
0118        """
0119        return [self._decode_value(v) for v in self.multi.getall(key)]
0120
0121    def getone(self, key):
0122        """
0123        Get one value matching the key, raising a KeyError if multiple
0124        values were found.
0125        """
0126        return self._decode_value(self.multi.getone(key))
0127
0128    def mixed(self):
0129        """
0130        Returns a dictionary where the values are either single
0131        values, or a list of values when a key/value appears more than
0132        once in this dictionary.  This is similar to the kind of
0133        dictionary often used to represent the variables in a web
0134        request.
0135        """
0136        unicode_mixed = {}
0137        for key, value in self.multi.mixed().iteritems():
0138            if isinstance(value, list):
0139                value = [self._decode_value(value) for value in value]
0140            else:
0141                value = self._decode_value(value)
0142            unicode_mixed[self._decode_key(key)] = value
0143        return unicode_mixed
0144
0145    def dict_of_lists(self):
0146        """
0147        Returns a dictionary where each key is associated with a
0148        list of values.
0149        """
0150        unicode_dict = {}
0151        for key, value in self.multi.dict_of_lists().iteritems():
0152            value = [self._decode_value(value) for value in value]
0153            unicode_dict[self._decode_key(key)] = value
0154        return unicode_dict
0155
0156    def __delitem__(self, key):
0157        self.multi.__delitem__(key)
0158
0159    def __contains__(self, key):
0160        return self.multi.__contains__(key)
0161
0162    has_key = __contains__
0163
0164    def clear(self):
0165        self.multi.clear()
0166
0167    def copy(self):
0168        return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
0169
0170    def setdefault(self, key, default=None):
0171        return self._decode_value(self.multi.setdefault(key, default))
0172
0173    def pop(self, key, *args):
0174        return self._decode_value(self.multi.pop(key, *args))
0175
0176    def popitem(self):
0177        k, v = self.multi.popitem()
0178        return (self._decode_key(k), self._decode_value(v))
0179
0180    def __repr__(self):
0181        items = ', '.join(['(%r, %r)' % v for v in self.items()])
0182        return '%s([%s])' % (self.__class__.__name__, items)
0183
0184    def __len__(self):
0185        return self.multi.__len__()
0186
0187    ##
0188    ## All the iteration:
0189    ##
0190
0191    def keys(self):
0192        return [self._decode_key(k) for k in self.multi.iterkeys()]
0193
0194    def iterkeys(self):
0195        for k in self.multi.iterkeys():
0196            yield self._decode_key(k)
0197
0198    __iter__ = iterkeys
0199
0200    def items(self):
0201        return [(self._decode_key(k), self._decode_value(v)) for                       k, v in self.multi.iteritems()]
0203
0204    def iteritems(self):
0205        for k, v in self.multi.iteritems():
0206            yield (self._decode_key(k), self._decode_value(v))
0207
0208    def values(self):
0209        return [self._decode_value(v) for v in self.multi.itervalues()]
0210
0211    def itervalues(self):
0212        for v in self.multi.itervalues():
0213            yield self._decode_value(v)