This module provides some basic functions that are useful
in HTML and web development.

You can safely import * from WebUtils.Funcs if you like.


__all__ = [
    'htmlEncode', 'htmlEncodeStr', 'htmlDecode', 'urlEncode', 'urlDecode',
    'htmlForDict', 'requestURI', 'normURL']

htmlForNone = '-' # used by htmlEncode

htmlCodes = (
    ('&', '&'),
    ('<', '&lt;'),
    ('>', '&gt;'),
    ('"', '&quot;'),
    # ['\n', '<br>'],

htmlCodesReversed = tuple(reversed(htmlCodes))

def htmlEncode(what, codes=htmlCodes):
    """Return the HTML encoded version of the given object.

    The optional 'codes' parameter allows passing custom translations.

    if what is None:
        return htmlForNone
    if hasattr(what, 'html'):
        # allow objects to specify their own translation to html
        # via a method, property or attribute
        ht = what.html
        if callable(ht):
            ht = ht()
        return ht
    what = str(what)
    return htmlEncodeStr(what, codes)

def htmlEncodeStr(s, codes=htmlCodes):
    """Return the HTML encoded version of the given string.

    This is useful to display a plain ASCII text string on a web page.

    The optional 'codes' parameter allows passing custom translations.

    for c, e in codes:
        s = s.replace(c, e)
    return s

def htmlDecode(s, codes=htmlCodesReversed):
    """Return the ASCII decoded version of the given HTML string.

    This does NOT remove normal HTML tags like <p>.
    It is the inverse of htmlEncode().

    The optional 'codes' parameter allows passing custom translations.

    for c, e in codes:
        s = s.replace(e, c)
    return s

# Aliases for URL encoding and decoding functions:
from urllib import quote_plus as urlEncode, unquote_plus as urlDecode

def htmlForDict(d, addSpace=None, filterValueCallBack=None,
        maxValueLength=None, topHeading=None, isEncoded=None):
    """Return an HTML string with a table where each row is a key-value pair."""
    if not d:
        return ''
    # A really great (er, bad) example of hardcoding.  :-)
    html = ['<table class="NiceTable">\n']
    if topHeading:
        html.append('<tr class="TopHeading"><th')
        html.append((isinstance(topHeading, tuple)
            and '>%s</th><th>%s' or ' colspan="2">%s') % topHeading)
    for key in sorted(d):
        value = d[key]
        if addSpace and key in addSpace:
            target = addSpace[key]
            value = (target + ' ').join(value.split(target))
        if filterValueCallBack:
            value = filterValueCallBack(value, key, d)
        if maxValueLength and not isEncoded:
            value = str(value)
            if len(value) > maxValueLength:
                value = value[:maxValueLength-3] + '...'
        key = htmlEncode(key)
        if not isEncoded:
            value = htmlEncode(value)
        html.append('<tr><th align="left">%s</th><td>%s</td></tr>\n'
            % (key, value))
    return ''.join(html)

def requestURI(env):
    """Return the request URI for a given CGI-style dictionary.

    Uses REQUEST_URI if available, otherwise constructs and returns it

    uri = env.get('REQUEST_URI')
    if uri is None:
        uri = env.get('SCRIPT_URL')
        if uri is None:
            uri = env.get('SCRIPT_NAME', '') + env.get('PATH_INFO', '')
        query = env.get('QUERY_STRING', '')
        if query != '':
            uri += '?' + query
    return uri

def normURL(path):
    """Normalizes a URL path, like os.path.normpath.

    Acts on a URL independant of operating system environment.

    if not path:
    initialslash = path[0] == '/'
    lastslash = path[-1] == '/'
    comps = path.split('/')
    newcomps = []
    for comp in comps:
        if comp in ('', '.'):
        if comp != '..':
        elif newcomps:
    path = '/'.join(newcomps)
    if path and lastslash:
        path += '/'
    if initialslash:
        path = '/' + path
    return path