source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/html.py @ 42095c5

mainqndtest v1.1.1
Last change on this file since 42095c5 was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 88.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5| This file is part of the web2py Web Framework
6| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9Template helpers
10--------------------------------------------
11"""
12
13import cgi
14import os
15import re
16import copy
17import types
18import urllib
19import base64
20import itertools
21from pydal._compat import PY2, reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
22    urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
23from yatl import sanitizer
24import marshal
25
26from gluon import decoder
27from gluon.storage import Storage
28from gluon.utils import web2py_uuid, compare
29from gluon.highlight import highlight
30from gluon.validators import simple_hash
31
32
33def local_html_escape(data, quote=False):
34    """
35    Works with bytes.
36    Replace special characters "&", "<" and ">" to HTML-safe sequences.
37    If the optional flag quote is true (the default), the quotation mark
38    characters, both double quote (") and single quote (') characters are also
39    translated.
40    """
41    if PY2:
42        import cgi
43        data = cgi.escape(data, quote)
44        return data.replace("'", "&#x27;") if quote else data
45    else:
46        import html
47        if isinstance(data, str):
48            return html.escape(data, quote=quote)
49        data = data.replace(b"&", b"&amp;")  # Must be done first!
50        data = data.replace(b"<", b"&lt;")
51        data = data.replace(b">", b"&gt;")
52        if quote:
53            data = data.replace(b'"', b"&quot;")
54            data = data.replace(b'\'', b"&#x27;")
55        return data
56
57regex_crlf = re.compile('\r|\n')
58
59join = ''.join
60
61# name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing.
62entitydefs = dict([(k_v[0], to_bytes(unichr(k_v[1]))) for k_v in iteritems(name2codepoint)])
63entitydefs.setdefault('apos', to_bytes("'"))
64
65
66__all__ = [
67    'A',
68    'ASSIGNJS',
69    'B',
70    'BEAUTIFY',
71    'BODY',
72    'BR',
73    'BUTTON',
74    'CENTER',
75    'CAT',
76    'CODE',
77    'COL',
78    'COLGROUP',
79    'DIV',
80    'EM',
81    'EMBED',
82    'FIELDSET',
83    'FORM',
84    'H1',
85    'H2',
86    'H3',
87    'H4',
88    'H5',
89    'H6',
90    'HEAD',
91    'HR',
92    'HTML',
93    'I',
94    'IFRAME',
95    'IMG',
96    'INPUT',
97    'LABEL',
98    'LEGEND',
99    'LI',
100    'LINK',
101    'OL',
102    'UL',
103    'MARKMIN',
104    'MENU',
105    'META',
106    'OBJECT',
107    'ON',
108    'OPTION',
109    'P',
110    'PRE',
111    'SCRIPT',
112    'OPTGROUP',
113    'SELECT',
114    'SPAN',
115    'STRONG',
116    'STYLE',
117    'TABLE',
118    'TAG',
119    'TD',
120    'TEXTAREA',
121    'TH',
122    'THEAD',
123    'TBODY',
124    'TFOOT',
125    'TITLE',
126    'TR',
127    'TT',
128    'URL',
129    'XHTML',
130    'XML',
131    'xmlescape',
132    'embed64',
133]
134
135DEFAULT_PASSWORD_DISPLAY = '*' * 8
136
137
138def xmlescape(data, quote=True):
139    """
140    Returns an escaped string of the provided data
141
142    Args:
143        data: the data to be escaped
144        quote: optional (default False)
145    """
146
147    # first try the xml function
148    if hasattr(data, 'xml') and callable(data.xml):
149        return to_bytes(data.xml())
150
151    if not(isinstance(data, (text_type, bytes))):
152        # i.e., integers
153        data = str(data)
154    data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
155
156    # ... and do the escaping
157    data = local_html_escape(data, quote)
158    return data
159
160
161def call_as_list(f, *a, **b):
162    if not isinstance(f, (list, tuple)):
163        f = [f]
164    for item in f:
165        item(*a, **b)
166
167
168def truncate_string(text, length, dots='...'):
169    text = to_unicode(text)
170    if len(text) > length:
171        text = to_native(text[:length - len(dots)]) + dots
172    return text
173
174
175def URL(a=None,
176        c=None,
177        f=None,
178        r=None,
179        args=None,
180        vars=None,
181        anchor='',
182        extension=None,
183        env=None,
184        hmac_key=None,
185        hash_vars=True,
186        salt=None,
187        user_signature=None,
188        scheme=None,
189        host=None,
190        port=None,
191        encode_embedded_slash=False,
192        url_encode=True,
193        language=None
194        ):
195    """
196    generates a url '/a/c/f' corresponding to application a, controller c
197    and function f. If r=request is passed, a, c, f are set, respectively,
198    to r.application, r.controller, r.function.
199
200    The more typical usage is:
201
202        URL('index')
203
204    that generates a url for the index function
205    within the present application and controller.
206
207    Args:
208        a: application (default to current if r is given)
209        c: controller (default to current if r is given)
210        f: function (default to current if r is given)
211        r: request (optional)
212        args: any arguments (optional). Additional "path" elements
213        vars: any variables (optional). Querystring elements
214        anchor: anchorname, without # (optional)
215        extension: force an extension
216        hmac_key: key to use when generating hmac signature (optional)
217        hash_vars: which of the vars to include in our hmac signature
218            True (default) - hash all vars, False - hash none of the vars,
219            iterable - hash only the included vars ['key1','key2']
220        salt: salt hashing with this string
221        user_signature: signs automatically the URL in such way that only the
222            user can access the URL (use with `URL.verify` or
223            `auth.requires_signature()`)
224        scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
225        host: string to force absolute URL with host (True means http_host)
226        port: optional port number (forces absolute URL)
227        encode_embedded_slash: encode slash characters included in args
228        url_encode: encode characters included in vars
229
230    Raises:
231        SyntaxError: when no application, controller or function is available
232            or when a CRLF is found in the generated url
233
234    Examples:
235
236    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
237    ...     vars={'p':1, 'q':2}, anchor='1'))
238    '/a/c/f/x/y/z?p=1&q=2#1'
239
240    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
241    ...     vars={'p':(1,3), 'q':2}, anchor='1'))
242    '/a/c/f/x/y/z?p=1&p=3&q=2#1'
243
244    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
245    ...     vars={'p':(3,1), 'q':2}, anchor='1'))
246    '/a/c/f/x/y/z?p=3&p=1&q=2#1'
247
248    >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
249    '/a/c/f#1%2B2'
250
251    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
252    ...     vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
253    '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1'
254
255    >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z']))
256    '/a/c/f/w/x/y/z'
257
258    >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z'], encode_embedded_slash=True))
259    '/a/c/f/w%2Fx/y%2Fz'
260
261    >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=False))
262    '/a/c/f/%(id)d'
263
264    >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=True))
265    '/a/c/f/%25%28id%29d'
266
267    >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=False))
268    '/a/c/f?id=%(id)d'
269
270    >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=True))
271    '/a/c/f?id=%25%28id%29d'
272
273    >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=False))
274    '/a/c/f#%(id)d'
275
276    >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
277    '/a/c/f#%25%28id%29d'
278    """
279
280    from gluon.rewrite import url_out  # done here in case used not-in web2py
281
282    if args in (None, []):
283        args = []
284    vars = vars or {}
285    application = None
286    controller = None
287    function = None
288
289    if not isinstance(args, (list, tuple)):
290        args = [args]
291
292    if not r:
293        if a and not c and not f:
294            (f, a, c) = (a, c, f)
295        elif a and c and not f:
296            (c, f, a) = (a, c, f)
297        from gluon.globals import current
298        if hasattr(current, 'request'):
299            r = current.request
300
301    if r:
302        application = r.application
303        controller = r.controller
304        function = r.function
305        env = r.env
306        if extension is None and r.extension != 'html':
307            extension = r.extension
308    if a:
309        application = a
310    if c:
311        controller = c
312    if f:
313        if not isinstance(f, str):
314            if hasattr(f, '__name__'):
315                function = f.__name__
316            else:
317                raise SyntaxError(
318                    'when calling URL, function or function name required')
319        elif '/' in f:
320            if f.startswith("/"):
321                f = f[1:]
322            items = f.split('/')
323            function = f = items[0]
324            args = items[1:] + args
325        else:
326            function = f
327
328        # if the url gets a static resource, don't force extension
329        if controller == 'static':
330            extension = None
331            # add static version to url
332            from gluon.globals import current
333            if hasattr(current, 'response'):
334                response = current.response
335                if response.static_version and response.static_version_urls:
336                    args = [function] + args
337                    function = '_' + str(response.static_version)
338
339        if '.' in function:
340            function, extension = function.rsplit('.', 1)
341
342    function2 = '%s.%s' % (function, extension or 'html')
343
344    if not (application and controller and function):
345        raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function))
346
347    if args:
348        if url_encode:
349            if encode_embedded_slash:
350                other = '/' + '/'.join([urllib_quote(str(x), '') for x in args])
351            else:
352                other = args and urllib_quote('/' + '/'.join([str(x) for x in args]))
353        else:
354            other = args and ('/' + '/'.join([str(x) for x in args]))
355    else:
356        other = ''
357
358    if other.endswith('/'):
359        other += '/'  # add trailing slash to make last trailing empty arg explicit
360
361    list_vars = []
362    for (key, vals) in sorted(vars.items()):
363        if key == '_signature':
364            continue
365        if not isinstance(vals, (list, tuple)):
366            vals = [vals]
367        for val in vals:
368            list_vars.append((key, val))
369
370    if user_signature:
371        from gluon.globals import current
372        if current.session.auth:
373            hmac_key = current.session.auth.hmac_key
374
375    if hmac_key:
376        # generate an hmac signature of the vars & args so can later
377        # verify the user hasn't messed with anything
378
379        h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
380
381        # how many of the vars should we include in our hash?
382        if hash_vars is True:  # include them all
383            h_vars = list_vars
384        elif hash_vars is False:  # include none of them
385            h_vars = ''
386        else:  # include just those specified
387            if hash_vars and not isinstance(hash_vars, (list, tuple)):
388                hash_vars = [hash_vars]
389            h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
390
391        # re-assembling the same way during hash authentication
392        message = h_args + '?' + urlencode(sorted(h_vars))
393        sig = simple_hash(
394            message, hmac_key or '', salt or '', digest_alg='sha1')
395        # add the signature into vars
396        list_vars.append(('_signature', sig))
397
398    if list_vars:
399        if url_encode:
400            other += '?%s' % urlencode(list_vars)
401        else:
402            other += '?%s' % '&'.join(['%s=%s' % var[:2] for var in list_vars])
403    if anchor:
404        if url_encode:
405            other += '#' + urllib_quote(str(anchor))
406        else:
407            other += '#' + (str(anchor))
408    if extension:
409        function += '.' + extension
410
411    if regex_crlf.search(join([application, controller, function, other])):
412        raise SyntaxError('CRLF Injection Detected')
413
414    url = url_out(r, env, application, controller, function,
415                  args, other, scheme, host, port, language=language)
416    return url
417
418
419def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
420    """
421    Verifies that a request's args & vars have not been tampered with by the user
422
423    :param request: web2py's request object
424    :param hmac_key: the key to authenticate with, must be the same one previously
425                    used when calling URL()
426    :param hash_vars: which vars to include in our hashing. (Optional)
427                    Only uses the 1st value currently
428                    True (or undefined) means all, False none,
429                    an iterable just the specified keys
430
431    do not call directly. Use instead:
432
433    URL.verify(hmac_key='...')
434
435    the key has to match the one used to generate the URL.
436
437        >>> r = Storage()
438        >>> gv = Storage(p=(1,3),q=2,_signature='a32530f0d0caa80964bb92aad2bedf8a4486a31f')
439        >>> r.update(dict(application='a', controller='c', function='f', extension='html'))
440        >>> r['args'] = ['x', 'y', 'z']
441        >>> r['get_vars'] = gv
442        >>> verifyURL(r, 'key')
443        True
444        >>> verifyURL(r, 'kay')
445        False
446        >>> r.get_vars.p = (3, 1)
447        >>> verifyURL(r, 'key')
448        True
449        >>> r.get_vars.p = (3, 2)
450        >>> verifyURL(r, 'key')
451        False
452
453    """
454
455    if '_signature' not in request.get_vars:
456        return False  # no signature in the request URL
457
458    # check if user_signature requires
459    if user_signature:
460        from gluon.globals import current
461        if not current.session or not current.session.auth:
462            return False
463        hmac_key = current.session.auth.hmac_key
464    if not hmac_key:
465        return False
466
467    # get our sig from request.get_vars for later comparison
468    original_sig = request.get_vars._signature
469
470    # now generate a new hmac for the remaining args & vars
471    vars, args = request.get_vars, request.args
472
473    # remove the signature var since it was not part of our signed message
474    request.get_vars.pop('_signature')
475
476    # join all the args & vars into one long string
477
478    # always include all of the args
479    other = args and urllib_quote('/' + '/'.join([str(x) for x in args])) or ''
480    h_args = '/%s/%s/%s.%s%s' % (request.application,
481                                 request.controller,
482                                 request.function,
483                                 request.extension,
484                                 other)
485
486    # but only include those vars specified (allows more flexibility for use with
487    # forms or ajax)
488
489    list_vars = []
490    for (key, vals) in sorted(vars.items()):
491        if not isinstance(vals, (list, tuple)):
492            vals = [vals]
493        for val in vals:
494            list_vars.append((key, val))
495
496    # which of the vars are to be included?
497    if hash_vars is True:       # include them all
498        h_vars = list_vars
499    elif hash_vars is False:    # include none of them
500        h_vars = ''
501    else:                       # include just those specified
502        # wrap in a try - if the desired vars have been removed it'll fail
503        try:
504            if hash_vars and not isinstance(hash_vars, (list, tuple)):
505                hash_vars = [hash_vars]
506            h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
507        except:
508            # user has removed one of our vars! Immediate fail
509            return False
510    # build the full message string with both args & vars
511    message = h_args + '?' + urlencode(sorted(h_vars))
512
513    # hash with the hmac_key provided
514    sig = simple_hash(message, str(hmac_key), salt or '', digest_alg='sha1')
515
516    # put _signature back in get_vars just in case a second call to URL.verify is performed
517    # (otherwise it'll immediately return false)
518    request.get_vars['_signature'] = original_sig
519
520    # return whether or not the signature in the request matched the one we just generated
521    # (I.E. was the message the same as the one we originally signed)
522
523    return compare(original_sig, sig)
524
525URL.verify = verifyURL
526
527ON = True
528
529
530class XmlComponent(object):
531    """
532    Abstract root for all Html components
533    """
534
535    # TODO: move some DIV methods to here
536
537    def xml(self):
538        raise NotImplementedError
539
540    def __mul__(self, n):
541        return CAT(*[self for i in range(n)])
542
543    def __add__(self, other):
544        if isinstance(self, CAT):
545            components = self.components
546        else:
547            components = [self]
548        if isinstance(other, CAT):
549            components += other.components
550        else:
551            components += [other]
552        return CAT(*components)
553
554    def add_class(self, name):
555        """
556        add a class to _class attribute
557        """
558        c = self['_class']
559        classes = (set(c.split()) if c else set()) | set(name.split())
560        self['_class'] = ' '.join(classes) if classes else None
561        return self
562
563    def remove_class(self, name):
564        """
565        remove a class from _class attribute
566        """
567        c = self['_class']
568        classes = (set(c.split()) if c else set()) - set(name.split())
569        self['_class'] = ' '.join(classes) if classes else None
570        return self
571
572
573class XML(XmlComponent):
574    """
575    use it to wrap a string that contains XML/HTML so that it will not be
576    escaped by the template
577
578    Examples:
579
580    >>> XML('<h1>Hello</h1>').xml()
581    '<h1>Hello</h1>'
582    """
583
584    def __init__(
585        self,
586        text,
587        sanitize=False,
588        permitted_tags=[
589            'a',
590            'b',
591            'blockquote',
592            'br/',
593            'i',
594            'li',
595            'ol',
596            'ul',
597            'p',
598            'cite',
599            'code',
600            'pre',
601            'img/',
602            'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
603            'table', 'tr', 'td', 'div',
604            'strong', 'span',
605        ],
606        allowed_attributes={
607            'a': ['href', 'title', 'target'],
608            'img': ['src', 'alt'],
609            'blockquote': ['type'],
610            'td': ['colspan'],
611        },
612    ):
613        """
614        Args:
615            text: the XML text
616            sanitize: sanitize text using the permitted tags and allowed
617                attributes (default False)
618            permitted_tags: list of permitted tags (default: simple list of
619                tags)
620            allowed_attributes: dictionary of allowed attributed (default
621                for A, IMG and BlockQuote).
622                The key is the tag; the value is a list of allowed attributes.
623        """
624        if isinstance(text, unicodeT):
625            text = to_native(text.encode('utf8', 'xmlcharrefreplace'))
626        if sanitize:
627            text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
628        elif isinstance(text, bytes):
629            text = to_native(text)
630        elif not isinstance(text, str):
631            text = str(text)
632        self.text = text
633
634    def xml(self):
635        return to_bytes(self.text)
636
637    def __str__(self):
638        return self.text
639
640    __repr__ = __str__
641
642    def __add__(self, other):
643        return '%s%s' % (self, other)
644
645    def __radd__(self, other):
646        return '%s%s' % (other, self)
647
648    def __cmp__(self, other):
649        return cmp(str(self), str(other))
650
651    def __hash__(self):
652        return hash(str(self))
653
654#    why was this here? Break unpickling in sessions
655#    def __getattr__(self, name):
656#        return getattr(str(self), name)
657
658    def __getitem__(self, i):
659        return str(self)[i]
660
661    def __getslice__(self, i, j):
662        return str(self)[i:j]
663
664    def __iter__(self):
665        for c in str(self):
666            yield c
667
668    def __len__(self):
669        return len(str(self))
670
671    def flatten(self, render=None):
672        """
673        returns the text stored by the XML object rendered
674        by the `render` function
675        """
676        if render:
677            return render(self.text, None, {})
678        return self.text
679
680    def elements(self, *args, **kargs):
681        """
682        to be considered experimental since the behavior of this method
683        is questionable
684        another option could be `TAG(self.text).elements(*args, **kwargs)`
685        """
686        return []
687
688# ## important to allow safe session.flash=T(....)
689
690
691def XML_unpickle(data):
692    return XML(marshal.loads(data))
693
694
695def XML_pickle(data):
696    return XML_unpickle, (marshal.dumps(str(data)),)
697copyreg.pickle(XML, XML_pickle, XML_unpickle)
698
699
700@implements_bool
701class DIV(XmlComponent):
702    """
703    HTML helper, for easy generating and manipulating a DOM structure.
704    Little or no validation is done.
705
706    Behaves like a dictionary regarding updating of attributes.
707    Behaves like a list regarding inserting/appending components.
708
709    Examples:
710
711    >>> DIV('hello', 'world', _style='color:red;').xml()
712    '<div style=\"color:red;\">helloworld</div>'
713
714    All other HTML helpers are derived from `DIV`.
715
716    `_something="value"` attributes are transparently translated into
717    `something="value"` HTML attributes
718    """
719
720    # name of the tag, subclasses should update this
721    # tags ending with a '/' denote classes that cannot
722    # contain components
723    tag = 'div'
724
725    def __init__(self, *components, **attributes):
726        """
727        Args:
728            components: any components that should be nested in this element
729            attributes: any attributes you want to give to this element
730
731        Raises:
732            SyntaxError: when a stand alone tag receives components
733        """
734
735        if self.tag[-1:] == '/' and components:
736            raise SyntaxError('<%s> tags cannot have components'
737                              % self.tag)
738        if len(components) == 1 and isinstance(components[0], (list, tuple)):
739            self.components = list(components[0])
740        else:
741            self.components = list(components)
742        self.attributes = attributes
743        self._fixup()
744        # converts special attributes in components attributes
745        self.parent = None
746        for c in self.components:
747            self._setnode(c)
748        self._postprocessing()
749
750    def update(self, **kargs):
751        """
752        dictionary like updating of the tag attributes
753        """
754
755        for (key, value) in iteritems(kargs):
756            self[key] = value
757        return self
758
759    def append(self, value):
760        """
761        list style appending of components
762
763        Examples:
764
765        >>> a=DIV()
766        >>> a.append(SPAN('x'))
767        >>> print(a)
768        <div><span>x</span></div>
769        """
770        self._setnode(value)
771        ret = self.components.append(value)
772        self._fixup()
773        return ret
774
775    def insert(self, i, value):
776        """
777        List-style inserting of components
778
779        Examples:
780
781        >>> a=DIV()
782        >>> a.insert(0, SPAN('x'))
783        >>> print(a)
784        <div><span>x</span></div>
785        """
786        self._setnode(value)
787        ret = self.components.insert(i, value)
788        self._fixup()
789        return ret
790
791    def __getitem__(self, i):
792        """
793        Gets attribute with name 'i' or component #i.
794        If attribute 'i' is not found returns None
795
796        Args:
797            i: index. If i is a string: the name of the attribute
798                otherwise references to number of the component
799        """
800
801        if isinstance(i, str):
802            try:
803                return self.attributes[i]
804            except KeyError:
805                return None
806        else:
807            return self.components[i]
808
809    def get(self, i):
810        return self.attributes.get(i)
811
812    def __setitem__(self, i, value):
813        """
814        Sets attribute with name 'i' or component #i.
815
816        Args:
817            i: index. If i is a string: the name of the attribute
818                otherwise references to number of the component
819            value: the new value
820        """
821        self._setnode(value)
822        if isinstance(i, (str, unicodeT)):
823            self.attributes[i] = value
824        else:
825            self.components[i] = value
826
827    def __delitem__(self, i):
828        """
829        Deletes attribute with name 'i' or component #i.
830
831        Args:
832            i: index. If i is a string: the name of the attribute
833                otherwise references to number of the component
834        """
835
836        if isinstance(i, str):
837            del self.attributes[i]
838        else:
839            del self.components[i]
840
841    def __len__(self):
842        """
843        Returns the number of included components
844        """
845        return len(self.components)
846
847    def __bool__(self):
848        """
849        Always returns True
850        """
851        return True
852
853    def _fixup(self):
854        """
855        Handling of provided components.
856
857        Nothing to fixup yet. May be overridden by subclasses,
858        eg for wrapping some components in another component or blocking them.
859        """
860        return
861
862    def _wrap_components(self, allowed_parents,
863                         wrap_parent=None,
864                         wrap_lambda=None):
865        """
866        helper for _fixup. Checks if a component is in allowed_parents,
867        otherwise wraps it in wrap_parent
868
869        Args:
870            allowed_parents: (tuple) classes that the component should be an
871                instance of
872            wrap_parent: the class to wrap the component in, if needed
873            wrap_lambda: lambda to use for wrapping, if needed
874
875        """
876        components = []
877        for c in self.components:
878            if isinstance(c, (allowed_parents, CAT)):
879                pass
880            elif wrap_lambda:
881                c = wrap_lambda(c)
882            else:
883                c = wrap_parent(c)
884            if isinstance(c, DIV):
885                c.parent = self
886            components.append(c)
887        self.components = components
888
889    def _postprocessing(self):
890        """
891        Handling of attributes (normally the ones not prefixed with '_').
892
893        Nothing to postprocess yet. May be overridden by subclasses
894        """
895        return
896
897    def _traverse(self, status, hideerror=False):
898        # TODO: docstring
899        newstatus = status
900        for c in self.components:
901            if hasattr(c, '_traverse') and callable(c._traverse):
902                c.vars = self.vars
903                c.request_vars = self.request_vars
904                c.errors = self.errors
905                c.latest = self.latest
906                c.session = self.session
907                c.formname = self.formname
908                if not c.attributes.get('hideerror'):
909                    c['hideerror'] = hideerror or self.attributes.get('hideerror')
910                newstatus = c._traverse(status, hideerror) and newstatus
911
912        # for input, textarea, select, option
913        # deal with 'value' and 'validation'
914
915        name = self['_name']
916        if newstatus:
917            newstatus = self._validate()
918            self._postprocessing()
919        elif 'old_value' in self.attributes:
920            self['value'] = self['old_value']
921            self._postprocessing()
922        elif name and name in self.vars:
923            self['value'] = self.vars[name]
924            self._postprocessing()
925        if name:
926            self.latest[name] = self['value']
927        return newstatus
928
929    def _validate(self):
930        """
931        nothing to validate yet. May be overridden by subclasses
932        """
933        return True
934
935    def _setnode(self, value):
936        if isinstance(value, DIV):
937            value.parent = self
938
939    def _xml(self):
940        """
941        Helper for xml generation. Returns separately:
942        - the component attributes
943        - the generated xml of the inner components
944
945        Component attributes start with an underscore ('_') and
946        do not have a False or None value. The underscore is removed.
947        A value of True is replaced with the attribute name.
948
949        Returns:
950            tuple: (attributes, components)
951        """
952
953        # get the attributes for this component
954        # (they start with '_', others may have special meanings)
955        attr = []
956        for key, value in iteritems(self.attributes):
957            if key[:1] != '_':
958                continue
959            name = key[1:]
960            if value is True:
961                value = name
962            elif value is False or value is None:
963                continue
964            attr.append((name, value))
965        data = self.attributes.get('data', {})
966        for key, value in iteritems(data):
967            name = 'data-' + key
968            value = data[key]
969            attr.append((name, value))
970        attr.sort()
971        fa = b''
972        for name, value in attr:
973            fa += (b' %s="%s"') % (to_bytes(name), xmlescape(value, True))
974
975        # get the xml for the inner components
976        co = b''.join([xmlescape(component) for component in self.components])
977        return (fa, co)
978
979    def xml(self):
980        """
981        generates the xml for this component.
982        """
983
984        (fa, co) = self._xml()
985
986        if not self.tag:
987            return co
988
989        tagname = to_bytes(self.tag)
990        if tagname[-1:] == b'/':
991            # <tag [attributes] />
992            return b'<%s%s />' % (tagname[:-1], fa)
993
994        # else: <tag [attributes]>  inner components xml </tag>
995        xml_tag = b'<%s%s>%s</%s>' % (tagname, fa, co, tagname)
996        return xml_tag
997
998    def __str__(self):
999        """
1000        str(COMPONENT) returns COMPONENT.xml()
1001        """
1002        # In PY3 __str__ cannot return bytes (TypeError: __str__ returned non-string (type bytes))
1003        return to_native(self.xml())
1004
1005    def flatten(self, render=None):
1006        """
1007        Returns the text stored by the DIV object rendered by the render function
1008        the render function must take text, tagname, and attributes
1009        `render=None` is equivalent to `render=lambda text, tag, attr: text`
1010
1011        Examples:
1012
1013        >>> markdown = lambda text, tag=None, attributes={}: \
1014                        {None: re.sub('\s+',' ',text), \
1015                         'h1':'#'+text+'\\n\\n', \
1016                         'p':text+'\\n'}.get(tag,text)
1017        >>> a=TAG('<h1>Header</h1><p>this is a     test</p>')
1018        >>> a.flatten(markdown)
1019        '#Header\\n\\nthis is a test\\n'
1020        """
1021
1022        text = ''
1023        for c in self.components:
1024            if isinstance(c, XmlComponent):
1025                s = c.flatten(render)
1026            elif render:
1027                s = render(to_native(c))
1028            else:
1029                s = to_native(c)
1030            text += s
1031        if render:
1032            text = render(text, self.tag, self.attributes)
1033        return text
1034
1035    regex_tag = re.compile('^[\w\-\:]+')
1036    regex_id = re.compile('#([\w\-]+)')
1037    regex_class = re.compile('\.([\w\-]+)')
1038    regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]')
1039
1040    def elements(self, *args, **kargs):
1041        """
1042        Find all components that match the supplied attribute dictionary,
1043        or None if nothing could be found
1044
1045        All components of the components are searched.
1046
1047        Examples:
1048
1049        >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
1050        >>> for c in a.elements('span', first_only=True): c[0]='z'
1051        >>> print(a)
1052        <div><div><span>z</span>3<div><span>y</span></div></div></div>
1053        >>> for c in a.elements('span'): c[0]='z'
1054        >>> print(a)
1055        <div><div><span>z</span>3<div><span>z</span></div></div></div>
1056
1057        It also supports a syntax compatible with jQuery
1058
1059        Examples:
1060
1061        >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
1062        >>> for e in a.elements('div a#1-1, p.is'): print(e.flatten())
1063        hello
1064        world
1065        >>> for e in a.elements('#1-1'): print(e.flatten())
1066        hello
1067        >>> a.elements('a[u:v=$]')[0].xml()
1068        '<a id="1-1" u:v="$">hello</a>'
1069        >>> a=FORM( INPUT(_type='text'), SELECT(list(range(1))), TEXTAREA() )
1070        >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
1071        >>> a.xml()
1072        '<form action="#" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
1073
1074        Elements that are matched can also be replaced or removed by specifying
1075        a "replace" argument (note, a list of the original matching elements
1076        is still returned as usual).
1077
1078        Examples:
1079
1080        >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1081        >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
1082        >>> print(a)  # We should .xml() here instead of print
1083        <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
1084
1085        "replace" can be a callable, which will be passed the original element and
1086        should return a new element to replace it.
1087
1088        Examples:
1089
1090        >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1091        >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
1092        >>> print(a)
1093        <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
1094
1095        If replace=None, matching elements will be removed completely.
1096
1097        Examples:
1098
1099        >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1100        >>> b = a.elements('span', find='y', replace=None)
1101        >>> print(a)
1102        <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
1103
1104        If a "find_text" argument is specified, elements will be searched for text
1105        components that match find_text, and any matching text components will be
1106        replaced (find_text is ignored if "replace" is not also specified).
1107        Like the "find" argument, "find_text" can be a string or a compiled regex.
1108
1109        Examples:
1110
1111        >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1112        >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
1113        >>> print(a)
1114        <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
1115
1116        If other attributes are specified along with find_text, then only components
1117        that match the specified attributes will be searched for find_text.
1118
1119        Examples:
1120
1121        >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
1122        >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
1123        >>> print(a)
1124        <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
1125        """
1126        if len(args) == 1:
1127            args = [a.strip() for a in args[0].split(',')]
1128        if len(args) > 1:
1129            subset = [self.elements(a, **kargs) for a in args]
1130            return reduce(lambda a, b: a + b, subset, [])
1131        elif len(args) == 1:
1132            items = args[0].split()
1133            if len(items) > 1:
1134                subset = [a.elements(' '.join(
1135                    items[1:]), **kargs) for a in self.elements(items[0])]
1136                return reduce(lambda a, b: a + b, subset, [])
1137            else:
1138                item = items[0]
1139                if '#' in item or '.' in item or '[' in item:
1140                    match_tag = self.regex_tag.search(item)
1141                    match_id = self.regex_id.search(item)
1142                    match_class = self.regex_class.search(item)
1143                    match_attr = self.regex_attr.finditer(item)
1144                    args = []
1145                    if match_tag:
1146                        args = [match_tag.group()]
1147                    if match_id:
1148                        kargs['_id'] = match_id.group(1)
1149                    if match_class:
1150                        kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' %
1151                                                     match_class.group(1).replace('-', '\\-').replace(':', '\\:'))
1152                    for item in match_attr:
1153                        kargs['_' + item.group(1)] = item.group(2)
1154                    return self.elements(*args, **kargs)
1155        # make a copy of the components
1156        matches = []
1157        # check if the component has an attribute with the same
1158        # value as provided
1159        tag = to_native(getattr(self, 'tag')).replace('/', '')
1160        check = not (args and tag not in args)
1161        for (key, value) in iteritems(kargs):
1162            if key not in ['first_only', 'replace', 'find_text']:
1163                if isinstance(value, (str, int)):
1164                    if str(self[key]) != str(value):
1165                        check = False
1166                elif key in self.attributes:
1167                    if not value.search(str(self[key])):
1168                        check = False
1169                else:
1170                    check = False
1171        if 'find' in kargs:
1172            find = kargs['find']
1173            is_regex = not isinstance(find, (str, int))
1174            for c in self.components:
1175                if (isinstance(c, str) and ((is_regex and find.search(c)) or
1176                   (str(find) in c))):
1177                    check = True
1178        # if found, return the component
1179        if check:
1180            matches.append(self)
1181
1182        first_only = kargs.get('first_only', False)
1183        replace = kargs.get('replace', False)
1184        find_text = replace is not False and kargs.get('find_text', False)
1185        is_regex = not isinstance(find_text, (str, int, bool))
1186        find_components = not (check and first_only)
1187
1188        def replace_component(i):
1189            if replace is None:
1190                del self[i]
1191                return i
1192            else:
1193                self[i] = replace(self[i]) if callable(replace) else replace
1194                return i + 1
1195        # loop the components
1196        if find_text or find_components:
1197            i = 0
1198            while i < len(self.components):
1199                c = self[i]
1200                j = i + 1
1201                if check and find_text and isinstance(c, str) and \
1202                        ((is_regex and find_text.search(c)) or (str(find_text) in c)):
1203                    j = replace_component(i)
1204                elif find_components and isinstance(c, XmlComponent):
1205                    child_matches = c.elements(*args, **kargs)
1206                    if len(child_matches):
1207                        if not find_text and replace is not False and child_matches[0] is c:
1208                            j = replace_component(i)
1209                        if first_only:
1210                            return child_matches
1211                        matches.extend(child_matches)
1212                i = j
1213        return matches
1214
1215    def element(self, *args, **kargs):
1216        """
1217        Finds the first component that matches the supplied attribute dictionary,
1218        or None if nothing could be found
1219
1220        Also the components of the components are searched.
1221        """
1222        kargs['first_only'] = True
1223        elements = self.elements(*args, **kargs)
1224        if not elements:
1225            # we found nothing
1226            return None
1227        return elements[0]
1228
1229    def siblings(self, *args, **kargs):
1230        """
1231        Finds all sibling components that match the supplied argument list
1232        and attribute dictionary, or None if nothing could be found
1233        """
1234        sibs = [s for s in self.parent.components if not s == self]
1235        matches = []
1236        first_only = False
1237        if 'first_only' in kargs:
1238            first_only = kargs.pop('first_only')
1239        for c in sibs:
1240            try:
1241                check = True
1242                tag = getattr(c, 'tag').replace("/", "")
1243                if args and tag not in args:
1244                        check = False
1245                for (key, value) in iteritems(kargs):
1246                    if c[key] != value:
1247                            check = False
1248                if check:
1249                    matches.append(c)
1250                    if first_only:
1251                        break
1252            except:
1253                pass
1254        return matches
1255
1256    def sibling(self, *args, **kargs):
1257        """
1258        Finds the first sibling component that match the supplied argument list
1259        and attribute dictionary, or None if nothing could be found
1260        """
1261        kargs['first_only'] = True
1262        sibs = self.siblings(*args, **kargs)
1263        if not sibs:
1264            return None
1265        return sibs[0]
1266
1267
1268class CAT(DIV):
1269
1270    tag = ''
1271
1272
1273def TAG_unpickler(data):
1274    return pickle.loads(data)
1275
1276
1277def TAG_pickler(data):
1278    d = DIV()
1279    d.__dict__ = data.__dict__
1280    marshal_dump = pickle.dumps(d, pickle.HIGHEST_PROTOCOL)
1281    return (TAG_unpickler, (marshal_dump,))
1282
1283
1284class __tag_div__(DIV):
1285    def __init__(self, name, *a, **b):
1286        DIV.__init__(self, *a, **b)
1287        self.tag = name
1288
1289copyreg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
1290
1291
1292class __TAG__(XmlComponent):
1293
1294    """
1295    TAG factory
1296
1297    Examples:
1298
1299    >>> print(TAG.first(TAG.second('test'), _key = 3))
1300    <first key=\"3\"><second>test</second></first>
1301
1302    """
1303
1304    def __getitem__(self, name):
1305        return self.__getattr__(name)
1306
1307    def __getattr__(self, name):
1308        if name[-1:] == '_':
1309            name = name[:-1] + '/'
1310        return lambda *a, **b: __tag_div__(name, *a, **b)
1311
1312    def __call__(self, html):
1313        return web2pyHTMLParser(decoder.decoder(html)).tree
1314
1315TAG = __TAG__()
1316
1317
1318class HTML(DIV):
1319    """
1320    There are four predefined document type definitions.
1321    They can be specified in the 'doctype' parameter:
1322
1323    - 'strict' enables strict doctype
1324    - 'transitional' enables transitional doctype (default)
1325    - 'frameset' enables frameset doctype
1326    - 'html5' enables HTML 5 doctype
1327    - any other string will be treated as user's own doctype
1328
1329    'lang' parameter specifies the language of the document.
1330    Defaults to 'en'.
1331
1332    See also `DIV`
1333    """
1334
1335    tag = b'html'
1336
1337    strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1338    transitional = \
1339        b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1340    frameset = \
1341        b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1342    html5 = b'<!DOCTYPE HTML>\n'
1343
1344    def xml(self):
1345        lang = self['lang']
1346        if not lang:
1347            lang = 'en'
1348        self.attributes['_lang'] = lang
1349        doctype = self['doctype']
1350        if doctype is None:
1351            doctype = self.transitional
1352        elif doctype == 'strict':
1353            doctype = self.strict
1354        elif doctype == 'transitional':
1355            doctype = self.transitional
1356        elif doctype == 'frameset':
1357            doctype = self.frameset
1358        elif doctype == 'html5':
1359            doctype = self.html5
1360        elif doctype == '':
1361            doctype = b''
1362        else:
1363            doctype = b'%s\n' % to_bytes(doctype)
1364        (fa, co) = self._xml()
1365
1366        return b'%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1367
1368
1369class XHTML(DIV):
1370    """
1371    This is XHTML version of the HTML helper.
1372
1373    There are three predefined document type definitions.
1374    They can be specified in the 'doctype' parameter:
1375
1376    - 'strict' enables strict doctype
1377    - 'transitional' enables transitional doctype (default)
1378    - 'frameset' enables frameset doctype
1379    - any other string will be treated as user's own doctype
1380
1381    'lang' parameter specifies the language of the document and the xml document.
1382    Defaults to 'en'.
1383
1384    'xmlns' parameter specifies the xml namespace.
1385    Defaults to 'http://www.w3.org/1999/xhtml'.
1386
1387    See also `DIV`
1388    """
1389
1390    tag = b'html'
1391
1392    strict = b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1393    transitional = b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1394    frameset = b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1395    xmlns = 'http://www.w3.org/1999/xhtml'
1396
1397    def xml(self):
1398        xmlns = self['xmlns']
1399        if xmlns:
1400            self.attributes['_xmlns'] = xmlns
1401        else:
1402            self.attributes['_xmlns'] = self.xmlns
1403        lang = self['lang']
1404        if not lang:
1405            lang = 'en'
1406        self.attributes['_lang'] = lang
1407        self.attributes['_xml:lang'] = lang
1408        doctype = self['doctype']
1409        if doctype:
1410            if doctype == 'strict':
1411                doctype = self.strict
1412            elif doctype == 'transitional':
1413                doctype = self.transitional
1414            elif doctype == 'frameset':
1415                doctype = self.frameset
1416            else:
1417                doctype = b'%s\n' % to_bytes(doctype)
1418        else:
1419            doctype = self.transitional
1420        (fa, co) = self._xml()
1421        return b'%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1422
1423
1424class HEAD(DIV):
1425
1426    tag = 'head'
1427
1428
1429class TITLE(DIV):
1430
1431    tag = 'title'
1432
1433
1434class META(DIV):
1435
1436    tag = 'meta/'
1437
1438
1439class LINK(DIV):
1440
1441    tag = 'link/'
1442
1443
1444class SCRIPT(DIV):
1445
1446    tag = b'script'
1447    tagname = to_bytes(tag)
1448
1449    def xml(self):
1450        (fa, co) = self._xml()
1451        fa = to_bytes(fa)
1452        # no escaping of subcomponents
1453        co = b'\n'.join([to_bytes(component) for component in
1454                       self.components])
1455        if co:
1456            # <script [attributes]><!--//--><![CDATA[//><!--
1457            # script body
1458            # //--><!]]></script>
1459            # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
1460            return b'<%s%s><!--\n%s\n//--></%s>' % (self.tagname, fa, co, self.tagname)
1461        else:
1462            return DIV.xml(self)
1463
1464
1465class STYLE(DIV):
1466
1467    tag = 'style'
1468    tagname = to_bytes(tag)
1469
1470    def xml(self):
1471        (fa, co) = self._xml()
1472        fa = to_bytes(fa)
1473        # no escaping of subcomponents
1474        co = b'\n'.join([to_bytes(component) for component in
1475                       self.components])
1476        if co:
1477            # <style [attributes]><!--/*--><![CDATA[/*><!--*/
1478            # style body
1479            # /*]]>*/--></style>
1480            return b'<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tagname, fa, co, self.tagname)
1481        else:
1482            return DIV.xml(self)
1483
1484
1485class IMG(DIV):
1486
1487    tag = 'img/'
1488
1489
1490class SPAN(DIV):
1491
1492    tag = 'span'
1493
1494
1495class BODY(DIV):
1496
1497    tag = 'body'
1498
1499
1500class H1(DIV):
1501
1502    tag = 'h1'
1503
1504
1505class H2(DIV):
1506
1507    tag = 'h2'
1508
1509
1510class H3(DIV):
1511
1512    tag = 'h3'
1513
1514
1515class H4(DIV):
1516
1517    tag = 'h4'
1518
1519
1520class H5(DIV):
1521
1522    tag = 'h5'
1523
1524
1525class H6(DIV):
1526
1527    tag = 'h6'
1528
1529
1530class P(DIV):
1531    """
1532    Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1533
1534    see also `DIV`
1535    """
1536
1537    tag = 'p'
1538
1539    def xml(self):
1540        text = DIV.xml(self)
1541        if self['cr2br']:
1542            text = text.replace(b'\n', b'<br />')
1543        return text
1544
1545
1546class STRONG(DIV):
1547
1548    tag = 'strong'
1549
1550
1551class B(DIV):
1552
1553    tag = 'b'
1554
1555
1556class BR(DIV):
1557
1558    tag = 'br/'
1559
1560
1561class HR(DIV):
1562
1563    tag = 'hr/'
1564
1565
1566class A(DIV):
1567    """
1568    Generates an A() link.
1569    A() in web2py is really important and with the included web2py.js
1570    allows lots of Ajax interactions in the page
1571
1572    On top of "usual" `_attributes`, it takes
1573
1574    Args:
1575        callback: an url to call but not redirect to
1576        cid: if you want to load the _href into an element of the page (component)
1577            pass its id (without the #) here
1578        delete: element to delete after calling callback
1579        target: same thing as cid
1580        confirm: text to display upon a callback with a delete
1581        noconfirm: don't display alert upon a callback with delete
1582
1583    """
1584
1585    tag = 'a'
1586
1587    def xml(self):
1588        if not self.components and self['_href']:
1589            self.append(self['_href'])
1590        disable_needed = ['callback', 'cid', 'delete', 'component', 'target']
1591        disable_needed = any((self[attr] for attr in disable_needed))
1592        if disable_needed:
1593            self['_data-w2p_disable_with'] = self['_disable_with'] or 'default'
1594            self['_disable_with'] = None
1595        if self['callback'] and not self['_id']:
1596            self['_id'] = web2py_uuid()
1597        if self['delete']:
1598            self['_data-w2p_remove'] = self['delete']
1599        if self['target']:
1600            if self['target'] == '<self>':
1601                self['target'] = self['_id']
1602            self['_data-w2p_target'] = self['target']
1603        if self['component']:
1604            self['_data-w2p_method'] = 'GET'
1605            self['_href'] = self['component']
1606        elif self['callback']:
1607            self['_data-w2p_method'] = 'POST'
1608            self['_href'] = self['callback']
1609            if self['delete'] and not self['noconfirm']:
1610                if not self['confirm']:
1611                    self['_data-w2p_confirm'] = 'default'
1612                else:
1613                    self['_data-w2p_confirm'] = self['confirm']
1614        elif self['cid']:
1615            self['_data-w2p_method'] = 'GET'
1616            self['_data-w2p_target'] = self['cid']
1617            if self['pre_call']:
1618                self['_data-w2p_pre_call'] = self['pre_call']
1619        return DIV.xml(self)
1620
1621
1622class BUTTON(DIV):
1623
1624    tag = 'button'
1625
1626
1627class EM(DIV):
1628
1629    tag = 'em'
1630
1631
1632class EMBED(DIV):
1633
1634    tag = 'embed/'
1635
1636
1637class TT(DIV):
1638
1639    tag = 'tt'
1640
1641
1642class PRE(DIV):
1643
1644    tag = 'pre'
1645
1646
1647class CENTER(DIV):
1648
1649    tag = 'center'
1650
1651
1652class CODE(DIV):
1653
1654    """
1655    Displays code in HTML with syntax highlighting.
1656
1657    Args:
1658        language: indicates the language, otherwise PYTHON is assumed
1659        link: can provide a link
1660        styles: for styles
1661
1662    Examples:
1663
1664    {{=CODE(\"print('hello world')\", language='python', link=None,
1665        counter=1, styles={}, highlight_line=None)}}
1666
1667
1668    supported languages are
1669
1670        "python", "html_plain", "c", "cpp", "web2py", "html"
1671
1672    The "html" language interprets {{ and }} tags as "web2py" code,
1673    "html_plain" doesn't.
1674
1675    if a link='/examples/global/vars/' is provided web2py keywords are linked to
1676    the online docs.
1677
1678    the counter is used for line numbering, counter can be None or a prompt
1679    string.
1680    """
1681
1682    def xml(self):
1683        language = self['language'] or 'PYTHON'
1684        link = self['link']
1685        counter = self.attributes.get('counter', 1)
1686        highlight_line = self.attributes.get('highlight_line', None)
1687        context_lines = self.attributes.get('context_lines', None)
1688        styles = self['styles'] or {}
1689        return highlight(
1690            join(self.components),
1691            language=language,
1692            link=link,
1693            counter=counter,
1694            styles=styles,
1695            attributes=self.attributes,
1696            highlight_line=highlight_line,
1697            context_lines=context_lines,
1698        )
1699
1700
1701class LABEL(DIV):
1702
1703    tag = 'label'
1704
1705
1706class LI(DIV):
1707
1708    tag = 'li'
1709
1710
1711class UL(DIV):
1712    """
1713    UL Component.
1714
1715    If subcomponents are not LI-components they will be wrapped in a LI
1716
1717    """
1718
1719    tag = 'ul'
1720
1721    def _fixup(self):
1722        self._wrap_components(LI, LI)
1723
1724
1725class OL(UL):
1726
1727    tag = 'ol'
1728
1729
1730class TD(DIV):
1731
1732    tag = 'td'
1733
1734
1735class TH(DIV):
1736
1737    tag = 'th'
1738
1739
1740class TR(DIV):
1741    """
1742    TR Component.
1743
1744    If subcomponents are not TD/TH-components they will be wrapped in a TD
1745
1746    """
1747
1748    tag = 'tr'
1749
1750    def _fixup(self):
1751        self._wrap_components((TD, TH), TD)
1752
1753
1754class __TRHEAD__(DIV):
1755    """
1756    __TRHEAD__ Component, internal only
1757
1758    If subcomponents are not TD/TH-components they will be wrapped in a TH
1759
1760    """
1761
1762    tag = 'tr'
1763
1764    def _fixup(self):
1765        self._wrap_components((TD, TH), TH)
1766
1767
1768class THEAD(DIV):
1769
1770    tag = 'thead'
1771
1772    def _fixup(self):
1773        self._wrap_components((__TRHEAD__, TR), __TRHEAD__)
1774
1775
1776class TBODY(DIV):
1777
1778    tag = 'tbody'
1779
1780    def _fixup(self):
1781        self._wrap_components(TR, TR)
1782
1783
1784class TFOOT(DIV):
1785
1786    tag = 'tfoot'
1787
1788    def _fixup(self):
1789        self._wrap_components(TR, TR)
1790
1791
1792class COL(DIV):
1793
1794    tag = 'col/'
1795
1796
1797class COLGROUP(DIV):
1798
1799    tag = 'colgroup'
1800
1801
1802class TABLE(DIV):
1803    """
1804    TABLE Component.
1805
1806    If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1807    they will be wrapped in a TR
1808
1809    """
1810
1811    tag = 'table'
1812
1813    def _fixup(self):
1814        self._wrap_components((TR, TBODY, THEAD, TFOOT, COL, COLGROUP), TR)
1815
1816
1817class I(DIV):
1818
1819    tag = 'i'
1820
1821
1822class IFRAME(DIV):
1823
1824    tag = 'iframe'
1825
1826
1827class INPUT(DIV):
1828
1829    """
1830    INPUT Component
1831
1832    Takes two special attributes value= and requires=.
1833
1834    Args:
1835        value: used to pass the initial value for the input field.
1836            value differs from _value because it works for checkboxes, radio,
1837            textarea and select/option too.
1838            For a checkbox value should be '' or 'on'.
1839            For a radio or select/option value should be the _value
1840            of the checked/selected item.
1841
1842        requires: should be None, or a validator or a list of validators
1843            for the value of the field.
1844
1845    Examples:
1846
1847    >>> INPUT(_type='text', _name='name', value='Max').xml()
1848    '<input name=\"name\" type=\"text\" value=\"Max\" />'
1849
1850    >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml()
1851    '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />'
1852
1853    >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml()
1854    '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />'
1855
1856    >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml()
1857    '<input name=\"radio\" type=\"radio\" value=\"no\" />'
1858
1859
1860        """
1861
1862    tag = 'input/'
1863
1864    def _validate(self):
1865
1866        # # this only changes value, not _value
1867
1868        name = self['_name']
1869        if name is None or name == '':
1870            return True
1871        name = str(name)
1872        request_vars_get = self.request_vars.get
1873        if self['_type'] != 'checkbox':
1874            self['old_value'] = self['value'] or self['_value'] or ''
1875            value = request_vars_get(name, '')
1876            self['value'] = value if not hasattr(value, 'file') else None
1877        else:
1878            self['old_value'] = self['value'] or False
1879            value = request_vars_get(name)
1880            if isinstance(value, (tuple, list)):
1881                self['value'] = self['_value'] in value
1882            else:
1883                self['value'] = self['_value'] == value
1884        requires = self['requires']
1885        if requires:
1886            if not isinstance(requires, (list, tuple)):
1887                requires = [requires]
1888            for k, validator in enumerate(requires):
1889                try:
1890                    (value, errors) = validator(value)
1891                except:
1892                    import traceback
1893                    print(traceback.format_exc())
1894                    msg = "Validation error, field:%s %s" % (name, validator)
1895                    raise Exception(msg)
1896                if errors is not None:
1897                    self.vars[name] = value
1898                    self.errors[name] = errors
1899                    break
1900        if name not in self.errors:
1901            self.vars[name] = value
1902            return True
1903        return False
1904
1905    def _postprocessing(self):
1906        t = self['_type']
1907        if not t:
1908            t = self['_type'] = 'text'
1909        t = t.lower()
1910        value = self['value']
1911        if self['_value'] is None or isinstance(self['_value'], cgi.FieldStorage):
1912            _value = None
1913        else:
1914            _value = str(self['_value'])
1915        if '_checked' in self.attributes and 'value' not in self.attributes:
1916            pass
1917        elif t == 'checkbox':
1918            if not _value:
1919                _value = self['_value'] = 'on'
1920            if not value:
1921                value = []
1922            elif value is True:
1923                value = [_value]
1924            elif not isinstance(value, (list, tuple)):
1925                value = str(value).split('|')
1926            self['_checked'] = _value in value and 'checked' or None
1927        elif t == 'radio':
1928            if str(value) == str(_value):
1929                self['_checked'] = 'checked'
1930            else:
1931                self['_checked'] = None
1932        elif t == 'password' and value != DEFAULT_PASSWORD_DISPLAY:
1933            self['value'] = ''
1934        elif not t == 'submit':
1935            if value is None:
1936                self['value'] = _value
1937            elif not isinstance(value, list):
1938                self['_value'] = value
1939
1940    def xml(self):
1941        name = self.attributes.get('_name', None)
1942        if name and hasattr(self, 'errors') \
1943                and self.errors.get(name, None) \
1944                and self['hideerror'] is not True:
1945            self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
1946            return DIV.xml(self) + DIV(
1947                DIV(
1948                    self.errors[name], _class='error',
1949                    errors=None, _id='%s__error' % name),
1950                _class='error_wrapper').xml()
1951        else:
1952            if self['_class'] and self['_class'].endswith('invalidinput'):
1953                self['_class'] = self['_class'][:-12]
1954                if self['_class'] == '':
1955                    self['_class'] = None
1956            return DIV.xml(self)
1957
1958
1959class TEXTAREA(INPUT):
1960
1961    """
1962    Examples::
1963
1964        TEXTAREA(_name='sometext', value='blah ' * 100, requires=IS_NOT_EMPTY())
1965
1966    'blah blah blah ...' will be the content of the textarea field.
1967
1968    """
1969
1970    tag = 'textarea'
1971
1972    def _postprocessing(self):
1973        if '_rows' not in self.attributes:
1974            self['_rows'] = 10
1975        if '_cols' not in self.attributes:
1976            self['_cols'] = 40
1977        if self['value'] is not None:
1978            self.components = [self['value']]
1979        elif self.components:
1980            self['value'] = self.components[0]
1981
1982
1983class OPTION(DIV):
1984
1985    tag = 'option'
1986
1987    def _fixup(self):
1988        if '_value' not in self.attributes:
1989            self.attributes['_value'] = str(self.components[0])
1990
1991
1992class OBJECT(DIV):
1993
1994    tag = 'object'
1995
1996
1997class OPTGROUP(DIV):
1998
1999    tag = 'optgroup'
2000
2001    def _fixup(self):
2002        components = []
2003        for c in self.components:
2004            if isinstance(c, OPTION):
2005                components.append(c)
2006            else:
2007                components.append(OPTION(c, _value=str(c)))
2008        self.components = components
2009
2010
2011class SELECT(INPUT):
2012    """
2013    Examples:
2014
2015    >>> from validators import IS_IN_SET
2016    >>> SELECT('yes', 'no', _name='selector', value='yes',
2017    ...    requires=IS_IN_SET(['yes', 'no'])).xml()
2018    '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
2019
2020    """
2021
2022    tag = 'select'
2023
2024    def _fixup(self):
2025        components = []
2026        for c in self.components:
2027            if isinstance(c, (OPTION, OPTGROUP)):
2028                components.append(c)
2029            else:
2030                components.append(OPTION(c, _value=str(c)))
2031        self.components = components
2032
2033    def _postprocessing(self):
2034        component_list = []
2035        for c in self.components:
2036            if isinstance(c, OPTGROUP):
2037                component_list.append(c.components)
2038            else:
2039                component_list.append([c])
2040        options = itertools.chain(*component_list)
2041
2042        value = self['value']
2043        if value is not None:
2044            if not self['_multiple']:
2045                for c in options:  # my patch
2046                    if (value is not None) and (str(c['_value']) == str(value)):
2047                        c['_selected'] = 'selected'
2048                    else:
2049                        c['_selected'] = None
2050            else:
2051                if isinstance(value, (list, tuple)):
2052                    values = [str(item) for item in value]
2053                else:
2054                    values = [str(value)]
2055                for c in options:  # my patch
2056                    if (value is not None) and (str(c['_value']) in values):
2057                        c['_selected'] = 'selected'
2058                    else:
2059                        c['_selected'] = None
2060
2061
2062class FIELDSET(DIV):
2063
2064    tag = 'fieldset'
2065
2066
2067class LEGEND(DIV):
2068
2069    tag = 'legend'
2070
2071
2072class FORM(DIV):
2073
2074    """
2075    Examples:
2076
2077    >>> from validators import IS_NOT_EMPTY
2078    >>> form=FORM(INPUT(_name="test", requires=IS_NOT_EMPTY()))
2079    >>> form.xml()
2080    '<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>'
2081
2082
2083    a FORM is container for INPUT, TEXTAREA, SELECT and other helpers
2084
2085    form has one important method::
2086
2087        form.accepts(request.vars, session)
2088
2089    if form is accepted (and all validators pass) form.vars contains the
2090    accepted vars, otherwise form.errors contains the errors.
2091    in case of errors the form is modified to present the errors to the user.
2092    """
2093
2094    tag = 'form'
2095
2096    def __init__(self, *components, **attributes):
2097        DIV.__init__(self, *components, **attributes)
2098        self.vars = Storage()
2099        self.errors = Storage()
2100        self.latest = Storage()
2101        self.accepted = None  # none for not submitted
2102
2103    def assert_status(self, status, request_vars):
2104        return status
2105
2106    def accepts(self,
2107                request_vars,
2108                session=None,
2109                formname='default',
2110                keepvalues=False,
2111                onvalidation=None,
2112                hideerror=False,
2113                **kwargs
2114                ):
2115        """
2116        kwargs is not used but allows to specify the same interface for FORM and SQLFORM
2117        """
2118        if request_vars.__class__.__name__ == 'Request':
2119            request_vars = request_vars.post_vars
2120        self.errors.clear()
2121        self.request_vars = Storage()
2122        self.request_vars.update(request_vars)
2123        self.session = session
2124        self.formname = formname
2125        self.keepvalues = keepvalues
2126
2127        # if this tag is a form and we are in accepting mode (status=True)
2128        # check formname and formkey
2129
2130        status = True
2131        changed = False
2132        request_vars = self.request_vars
2133        if session is not None:
2134            formkey = request_vars._formkey
2135            keyname = '_formkey[%s]' % formname
2136            formkeys = list(session.get(keyname, []))
2137            # check if user tampering with form and void CSRF
2138            if not (formkey and formkeys and formkey in formkeys):
2139                status = False
2140            else:
2141                session[keyname].remove(formkey)
2142        if formname != request_vars._formname:
2143            status = False
2144        if status and session:
2145            # check if editing a record that has been modified by the server
2146            if hasattr(self, 'record_hash') and self.record_hash != formkey.split(':')[0]:
2147                status = False
2148                self.record_changed = changed = True
2149        status = self._traverse(status, hideerror)
2150        status = self.assert_status(status, request_vars)
2151        if onvalidation:
2152            if isinstance(onvalidation, dict):
2153                onsuccess = onvalidation.get('onsuccess', None)
2154                onfailure = onvalidation.get('onfailure', None)
2155                onchange = onvalidation.get('onchange', None)
2156                if [k for k in onvalidation if k not in ('onsuccess', 'onfailure', 'onchange')]:
2157                    raise RuntimeError('Invalid key in onvalidate dict')
2158                if onsuccess and status:
2159                    call_as_list(onsuccess, self)
2160                if onfailure and request_vars and not status:
2161                    call_as_list(onfailure, self)
2162                    status = len(self.errors) == 0
2163                if changed:
2164                    if onchange and self.record_changed and \
2165                            self.detect_record_change:
2166                        call_as_list(onchange, self)
2167            elif status:
2168                call_as_list(onvalidation, self)
2169        if self.errors:
2170            status = False
2171        if session is not None:
2172            if hasattr(self, 'record_hash'):
2173                formkey = self.record_hash + ':' + web2py_uuid()
2174            else:
2175                formkey = web2py_uuid()
2176            self.formkey = formkey
2177            keyname = '_formkey[%s]' % formname
2178            session[keyname] = list(session.get(keyname, []))[-9:] + [formkey]
2179        if status and not keepvalues:
2180            self._traverse(False, hideerror)
2181        self.accepted = status
2182        return status
2183
2184    def _postprocessing(self):
2185        if '_action' not in self.attributes:
2186            self['_action'] = '#'
2187        if '_method' not in self.attributes:
2188            self['_method'] = 'post'
2189        if '_enctype' not in self.attributes:
2190            self['_enctype'] = 'multipart/form-data'
2191
2192    def hidden_fields(self):
2193        c = []
2194        attr = self.attributes.get('hidden', {})
2195        if 'hidden' in self.attributes:
2196            c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in iteritems(attr)]
2197        if hasattr(self, 'formkey') and self.formkey:
2198            c.append(INPUT(_type='hidden', _name='_formkey', _value=self.formkey))
2199        if hasattr(self, 'formname') and self.formname:
2200            c.append(INPUT(_type='hidden', _name='_formname', _value=self.formname))
2201        return DIV(c, _style="display:none;")
2202
2203    def xml(self):
2204        newform = FORM(*self.components, **self.attributes)
2205        hidden_fields = self.hidden_fields()
2206        if hidden_fields.components:
2207            newform.append(hidden_fields)
2208        return DIV.xml(newform)
2209
2210    def validate(self, **kwargs):
2211        """
2212        This function validates the form,
2213        you can use it instead of directly form.accepts.
2214
2215        Usage:
2216        In controller::
2217
2218            def action():
2219                form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
2220                form.validate() #you can pass some args here - see below
2221                return dict(form=form)
2222
2223        This can receive a bunch of arguments
2224
2225        onsuccess = 'flash' - will show message_onsuccess in response.flash
2226                    None - will do nothing
2227                    can be a function (lambda form: pass)
2228        onfailure = 'flash' - will show message_onfailure in response.flash
2229                    None - will do nothing
2230                    can be a function (lambda form: pass)
2231        onchange = 'flash' - will show message_onchange in response.flash
2232                    None - will do nothing
2233                    can be a function (lambda form: pass)
2234
2235        message_onsuccess
2236        message_onfailure
2237        message_onchange
2238        next      = where to redirect in case of success
2239        any other kwargs will be passed for form.accepts(...)
2240        """
2241        from gluon.globals import current
2242        from gluon.http import redirect
2243        kwargs['request_vars'] = kwargs.get(
2244            'request_vars', current.request.post_vars)
2245        kwargs['session'] = kwargs.get('session', current.session)
2246        kwargs['dbio'] = kwargs.get('dbio', False)  # necessary for SQLHTML forms
2247
2248        onsuccess = kwargs.get('onsuccess', 'flash')
2249        onfailure = kwargs.get('onfailure', 'flash')
2250        onchange = kwargs.get('onchange', 'flash')
2251        message_onsuccess = kwargs.get('message_onsuccess',
2252                                       current.T("Success!"))
2253        message_onfailure = kwargs.get('message_onfailure',
2254                                       current.T("Errors in form, please check it out."))
2255        message_onchange = kwargs.get('message_onchange',
2256                                      current.T("Form consecutive submissions not allowed. " +
2257                                                "Try re-submitting or refreshing the form page."))
2258        next = kwargs.get('next', None)
2259        for key in ('message_onsuccess', 'message_onfailure', 'onsuccess',
2260                    'onfailure', 'next', 'message_onchange', 'onchange'):
2261            if key in kwargs:
2262                del kwargs[key]
2263
2264        if self.accepts(**kwargs):
2265            if onsuccess == 'flash':
2266                if next:
2267                    current.session.flash = message_onsuccess
2268                else:
2269                    current.response.flash = message_onsuccess
2270            elif callable(onsuccess):
2271                onsuccess(self)
2272            if next:
2273                if self.vars:
2274                    for key, value in iteritems(self.vars):
2275                        next = next.replace('[%s]' % key,
2276                                            urllib_quote(str(value)))
2277                    if not next.startswith('/'):
2278                        next = URL(next)
2279                redirect(next)
2280            return True
2281        elif self.errors:
2282            if onfailure == 'flash':
2283                current.response.flash = message_onfailure
2284            elif callable(onfailure):
2285                onfailure(self)
2286            return False
2287        elif hasattr(self, "record_changed"):
2288            if self.record_changed and self.detect_record_change:
2289                if onchange == 'flash':
2290                    current.response.flash = message_onchange
2291                elif callable(onchange):
2292                    onchange(self)
2293            return False
2294
2295    def process(self, **kwargs):
2296        """
2297        Perform the .validate() method but returns the form
2298
2299        Usage in controllers::
2300
2301            # directly on return
2302            def action():
2303                #some code here
2304                return dict(form=FORM(...).process(...))
2305
2306        You can use it with FORM, SQLFORM or FORM based plugins::
2307
2308            # response.flash messages
2309            def action():
2310                form = SQLFORM(db.table).process(message_onsuccess='Sucess!')
2311                return dict(form=form)
2312
2313            # callback function
2314            # callback receives True or False as first arg, and a list of args.
2315            def my_callback(status, msg):
2316                response.flash = "Success! "+msg if status else "Errors occured"
2317
2318            # after argument can be 'flash' to response.flash messages
2319            # or a function name to use as callback or None to do nothing.
2320            def action():
2321                return dict(form=SQLFORM(db.table).process(onsuccess=my_callback)
2322
2323
2324        """
2325        kwargs['dbio'] = kwargs.get('dbio', True)  # necessary for SQLHTML forms
2326        self.validate(**kwargs)
2327        return self
2328
2329    REDIRECT_JS = "window.location='%s';return false"
2330
2331    def add_button(self, value, url, _class=None):
2332        submit = self.element(_type='submit')
2333        _class = "%s w2p-form-button" % _class if _class else "w2p-form-button"
2334        submit.parent.append(
2335            TAG['button'](value, _class=_class,
2336                          _onclick=url if url.startswith('javascript:') else
2337                          self.REDIRECT_JS % url))
2338
2339    @staticmethod
2340    def confirm(text='OK', buttons=None, hidden=None):
2341        if not buttons:
2342            buttons = {}
2343        if not hidden:
2344            hidden = {}
2345        inputs = [INPUT(_type='button',
2346                        _value=name,
2347                        _onclick=FORM.REDIRECT_JS % link)
2348                  for name, link in iteritems(buttons)]
2349        inputs += [INPUT(_type='hidden',
2350                         _name=name,
2351                         _value=value)
2352                   for name, value in iteritems(hidden)]
2353        form = FORM(INPUT(_type='submit', _value=text), *inputs)
2354        form.process()
2355        return form
2356
2357    def as_dict(self, flat=False, sanitize=True):
2358        """EXPERIMENTAL
2359
2360        Sanitize is naive. It should catch any unsafe value
2361        for client retrieval.
2362        """
2363        SERIALIZABLE = (int, float, bool, basestring, long,
2364                        set, list, dict, tuple, Storage, type(None))
2365        UNSAFE = ("PASSWORD", "CRYPT")
2366        d = self.__dict__
2367
2368        def sanitizer(obj):
2369            if isinstance(obj, dict):
2370                for k in obj.keys():
2371                    if any([unsafe in str(k).upper() for unsafe in UNSAFE]):
2372                        # erease unsafe pair
2373                        obj.pop(k)
2374            else:
2375                # not implemented
2376                pass
2377            return obj
2378
2379        def flatten(obj):
2380            if isinstance(obj, (dict, Storage)):
2381                newobj = obj.copy()
2382            else:
2383                newobj = obj
2384            if sanitize:
2385                newobj = sanitizer(newobj)
2386            if flat:
2387                if type(obj) in SERIALIZABLE:
2388                    if isinstance(newobj, (dict, Storage)):
2389                        for k in newobj:
2390                            newk = flatten(k)
2391                            newobj[newk] = flatten(newobj[k])
2392                            if k != newk:
2393                                newobj.pop(k)
2394                        return newobj
2395                    elif isinstance(newobj, (list, tuple, set)):
2396                        return [flatten(item) for item in newobj]
2397                    else:
2398                        return newobj
2399                else:
2400                    return str(newobj)
2401            else:
2402                return newobj
2403        return flatten(d)
2404
2405    def as_json(self, sanitize=True):
2406        d = self.as_dict(flat=True, sanitize=sanitize)
2407        from gluon.serializers import json
2408        return json(d)
2409
2410    def as_yaml(self, sanitize=True):
2411        d = self.as_dict(flat=True, sanitize=sanitize)
2412        from gluon.serializers import yaml
2413        return yaml(d)
2414
2415    def as_xml(self, sanitize=True):
2416        d = self.as_dict(flat=True, sanitize=sanitize)
2417        from gluon.serializers import xml
2418        return xml(d)
2419
2420
2421class BEAUTIFY(DIV):
2422    """
2423    Turns any list, dictionary, etc into decent looking html.
2424
2425    Two special attributes are
2426
2427    - sorted: a function that takes the dict and returned sorted keys
2428    - keyfilter: a function that takes a key and returns its representation or
2429      None if the key is to be skipped.
2430      By default key[:1]=='_' is skipped.
2431
2432    Examples:
2433
2434    >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
2435    '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>'
2436
2437    """
2438
2439    tag = 'div'
2440
2441    @staticmethod
2442    def no_underscore(key):
2443        if key[:1] == '_':
2444            return None
2445        return key
2446
2447    def __init__(self, component, **attributes):
2448        self.components = [component]
2449        self.attributes = attributes
2450        sorter = attributes.get('sorted', sorted)
2451        keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
2452        components = []
2453        attributes = copy.copy(self.attributes)
2454        level = attributes['level'] = attributes.get('level', 6) - 1
2455        if '_class' in attributes:
2456            attributes['_class'] += 'i'
2457        if level == 0:
2458            return
2459        for c in self.components:
2460            if hasattr(c, 'value') and not callable(c.value) and not isinstance(c, cgi.FieldStorage):
2461                if c.value:
2462                    components.append(c.value)
2463            if hasattr(c, 'xml') and callable(c.xml):
2464                components.append(c)
2465                continue
2466            elif hasattr(c, 'keys') and callable(c.keys):
2467                rows = []
2468                try:
2469                    keys = (sorter and sorter(c)) or c
2470                    for key in keys:
2471                        if isinstance(key, (str, unicodeT)) and keyfilter:
2472                            filtered_key = keyfilter(key)
2473                        else:
2474                            filtered_key = str(key)
2475                        if filtered_key is None:
2476                            continue
2477                        value = c[key]
2478                        if isinstance(value, types.LambdaType):
2479                            continue
2480                        rows.append(
2481                            TR(
2482                                TD(filtered_key, _style='font-weight:bold;vertical-align:top;'),
2483                                TD(':', _style='vertical-align:top;'),
2484                                TD(BEAUTIFY(value, **attributes))))
2485                    components.append(TABLE(*rows, **attributes))
2486                    continue
2487                except:
2488                    pass
2489            if isinstance(c, str):
2490                components.append(str(c))
2491            elif isinstance(c, unicodeT):
2492                components.append(c.encode('utf8'))
2493            elif isinstance(c, (list, tuple)):
2494                items = [TR(TD(BEAUTIFY(item, **attributes)))
2495                         for item in c]
2496                components.append(TABLE(*items, **attributes))
2497            elif isinstance(c, cgi.FieldStorage):
2498                components.append('FieldStorage object')
2499            else:
2500                components.append(repr(c))
2501        self.components = components
2502
2503
2504class MENU(DIV):
2505    """
2506    Used to build menus
2507
2508    Args:
2509        _class: defaults to 'web2py-menu web2py-menu-vertical'
2510        ul_class: defaults to 'web2py-menu-vertical'
2511        li_class: defaults to 'web2py-menu-expand'
2512        li_first: defaults to 'web2py-menu-first'
2513        li_last: defaults to 'web2py-menu-last'
2514
2515    Use like::
2516
2517        menu = MENU([['name', False, URL(...), [submenu]], ...])
2518        {{=menu}}
2519
2520    """
2521
2522    tag = 'ul'
2523
2524    def __init__(self, data, **args):
2525        self.data = data
2526        self.attributes = args
2527        self.components = []
2528        if '_class' not in self.attributes:
2529            self['_class'] = 'web2py-menu web2py-menu-vertical'
2530        if 'ul_class' not in self.attributes:
2531            self['ul_class'] = 'web2py-menu-vertical'
2532        if 'li_class' not in self.attributes:
2533            self['li_class'] = 'web2py-menu-expand'
2534        if 'li_first' not in self.attributes:
2535            self['li_first'] = 'web2py-menu-first'
2536        if 'li_last' not in self.attributes:
2537            self['li_last'] = 'web2py-menu-last'
2538        if 'li_active' not in self.attributes:
2539            self['li_active'] = 'web2py-menu-active'
2540        if 'mobile' not in self.attributes:
2541            self['mobile'] = False
2542
2543    def serialize(self, data, level=0):
2544        if level == 0:
2545            ul = UL(**self.attributes)
2546        else:
2547            ul = UL(_class=self['ul_class'])
2548        for item in data:
2549            if isinstance(item, LI):
2550                ul.append(item)
2551            else:
2552                (name, active, link) = item[:3]
2553                if isinstance(link, DIV):
2554                    li = LI(link)
2555                elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
2556                    li = LI(DIV(name))
2557                elif isinstance(link, dict):
2558                    li = LI(A(name, **link))
2559                elif link:
2560                    li = LI(A(name, _href=link))
2561                elif not link and isinstance(name, A):
2562                    li = LI(name)
2563                else:
2564                    li = LI(A(name, _href='#',
2565                              _onclick='javascript:void(0);return false;'))
2566                if level == 0 and item == data[0]:
2567                    li['_class'] = self['li_first']
2568                elif level == 0 and item == data[-1]:
2569                    li['_class'] = self['li_last']
2570                if len(item) > 3 and item[3]:
2571                    li['_class'] = self['li_class']
2572                    li.append(self.serialize(item[3], level + 1))
2573                if active or ('active_url' in self.attributes and self['active_url'] == link):
2574                    if li['_class']:
2575                        li['_class'] = li['_class'] + ' ' + self['li_active']
2576                    else:
2577                        li['_class'] = self['li_active']
2578                if len(item) <= 4 or item[4] is True:
2579                    ul.append(li)
2580        return ul
2581
2582    def serialize_mobile(self, data, select=None, prefix=''):
2583        if not select:
2584            select = SELECT(**self.attributes)
2585        custom_items = []
2586        for item in data:
2587            # Custom item aren't serialized as mobile
2588            if len(item) >= 3 and (not item[0]) or (isinstance(item[0], DIV) and not (item[2])):
2589                # ex: ('', False, A('title', _href=URL(...), _title="title"))
2590                # ex: (A('title', _href=URL(...), _title="title"), False, None)
2591                custom_items.append(item)
2592            elif len(item) <= 4 or item[4] is True:
2593                select.append(OPTION(CAT(prefix, item[0]),
2594                                     _value=item[2], _selected=item[1]))
2595                if len(item) > 3 and len(item[3]):
2596                    self.serialize_mobile(
2597                        item[3], select, prefix=CAT(prefix, item[0], '/'))
2598        select['_onchange'] = 'window.location=this.value'
2599        # avoid to wrap the select if no custom items are present
2600        html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select
2601        return html
2602
2603    def xml(self):
2604        if self['mobile']:
2605            return self.serialize_mobile(self.data, 0).xml()
2606        else:
2607            return self.serialize(self.data, 0).xml()
2608
2609
2610def embed64(filename=None,
2611            file=None,
2612            data=None,
2613            extension='image/gif'
2614            ):
2615    """
2616    helper to encode the provided (binary) data into base64.
2617
2618    Args:
2619        filename: if provided, opens and reads this file in 'rb' mode
2620        file: if provided, reads this file
2621        data: if provided, uses the provided data
2622    """
2623
2624    if filename and os.path.exists(file):
2625        fp = open(filename, 'rb')
2626        data = fp.read()
2627        fp.close()
2628    data = base64.b64encode(data)
2629    return 'data:%s;base64,%s' % (extension, data)
2630
2631
2632# TODO: Check if this test() is still relevant now that we have gluon/tests/test_html.py
2633def test():
2634    """
2635    Example:
2636
2637    >>> from validators import *
2638    >>> print(DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml())
2639    <div><a href="/a/b/c">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2640    >>> print(DIV(UL("doc","cat","mouse")).xml())
2641    <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2642    >>> print(DIV(UL("doc", LI("cat", _class='feline'), 18)).xml())
2643    <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2644    >>> print(TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml())
2645    <table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table>
2646    >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2647    >>> print(form.xml())
2648    <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2649    >>> print(form.accepts({'myvar':'34'}, formname=None))
2650    False
2651    >>> print(form.xml())
2652    <form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error_wrapper"><div class="error" id="myvar__error">Invalid expression</div></div></form>
2653    >>> print(form.accepts({'myvar':'4'}, formname=None, keepvalues=True))
2654    True
2655    >>> print(form.xml())
2656    <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2657    >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2658    >>> print(form.accepts({'myvar':'dog'}, formname=None, keepvalues=True))
2659    True
2660    >>> print(form.xml())
2661    <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form>
2662    >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2663    >>> print(form.accepts({'myvar':'as df'}, formname=None))
2664    False
2665    >>> print(form.xml())
2666    <form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="as df" /><div class="error_wrapper"><div class="error" id="myvar__error">only alphanumeric!</div></div></form>
2667    >>> session={}
2668    >>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
2669    >>> isinstance(form.as_dict(), dict)
2670    True
2671    >>> "vars" in form.as_dict(flat=True)
2672    True
2673    >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
2674    True
2675    >>> if form.accepts({}, session,formname=None): print('passed')
2676    >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print('passed')
2677    """
2678    pass
2679
2680
2681class web2pyHTMLParser(HTMLParser):
2682    """
2683    obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2684    obj.tree contains the root of the tree, and tree can be manipulated
2685    """
2686
2687    def __init__(self, text, closed=('input', 'link')):
2688        HTMLParser.__init__(self)
2689        self.tree = self.parent = TAG['']()
2690        self.closed = closed
2691        self.last = None
2692        self.feed(text)
2693
2694    def handle_starttag(self, tagname, attrs):
2695        if tagname in self.closed:
2696            tagname += '/'
2697        tag = TAG[tagname]()
2698        for key, value in attrs:
2699            tag['_' + key] = value
2700        tag.parent = self.parent
2701        self.parent.append(tag)
2702        if not tag.tag.endswith('/'):
2703            self.parent = tag
2704        else:
2705            self.last = tag.tag[:-1]
2706
2707    def handle_data(self, data):
2708        if not isinstance(data, unicodeT):
2709            try:
2710                data = data.decode('utf8')
2711            except:
2712                data = data.decode('latin1')
2713        self.parent.append(data.encode('utf8', 'xmlcharref'))
2714
2715    def handle_charref(self, name):
2716        if name.startswith('x'):
2717            self.parent.append(unichr(int(name[1:], 16)).encode('utf8'))
2718        else:
2719            self.parent.append(unichr(int(name)).encode('utf8'))
2720
2721    def handle_entityref(self, name):
2722        self.parent.append(entitydefs[name])
2723
2724    def handle_endtag(self, tagname):
2725        # this deals with unbalanced tags
2726        if tagname == self.last:
2727            return
2728        while True:
2729            try:
2730                parent_tagname = self.parent.tag
2731                self.parent = self.parent.parent
2732            except:
2733                raise RuntimeError("unable to balance tag %s" % tagname)
2734            if parent_tagname[:len(tagname)] == tagname:
2735                break
2736
2737
2738def markdown_serializer(text, tag=None, attr=None):
2739    attr = attr or {}
2740    if tag is None:
2741        return re.sub('\s+', ' ', text)
2742    if tag == 'br':
2743        return '\n\n'
2744    if tag == 'h1':
2745        return '#' + text + '\n\n'
2746    if tag == 'h2':
2747        return '#' * 2 + text + '\n\n'
2748    if tag == 'h3':
2749        return '#' * 3 + text + '\n\n'
2750    if tag == 'h4':
2751        return '#' * 4 + text + '\n\n'
2752    if tag == 'p':
2753        return text + '\n\n'
2754    if tag == 'b' or tag == 'strong':
2755        return '**%s**' % text
2756    if tag == 'em' or tag == 'i':
2757        return '*%s*' % text
2758    if tag == 'tt' or tag == 'code':
2759        return '`%s`' % text
2760    if tag == 'a':
2761        return '[%s](%s)' % (text, attr.get('_href', ''))
2762    if tag == 'img':
2763        return '![%s](%s)' % (attr.get('_alt', ''), attr.get('_src', ''))
2764    return text
2765
2766
2767def markmin_serializer(text, tag=None, attr=None):
2768    attr = attr or {}
2769    # if tag is None: return re.sub('\s+',' ',text)
2770    if tag == 'br':
2771        return '\n\n'
2772    if tag == 'h1':
2773        return '# ' + text + '\n\n'
2774    if tag == 'h2':
2775        return '#' * 2 + ' ' + text + '\n\n'
2776    if tag == 'h3':
2777        return '#' * 3 + ' ' + text + '\n\n'
2778    if tag == 'h4':
2779        return '#' * 4 + ' ' + text + '\n\n'
2780    if tag == 'p':
2781        return text + '\n\n'
2782    if tag == 'li':
2783        return '\n- ' + text.replace('\n', ' ')
2784    if tag == 'tr':
2785        return text[3:].replace('\n', ' ') + '\n'
2786    if tag in ['table', 'blockquote']:
2787        return '\n-----\n' + text + '\n------\n'
2788    if tag in ['td', 'th']:
2789        return ' | ' + text
2790    if tag in ['b', 'strong', 'label']:
2791        return '**%s**' % text
2792    if tag in ['em', 'i']:
2793        return "''%s''" % text
2794    if tag in ['tt']:
2795        return '``%s``' % text.strip()
2796    if tag in ['code']:
2797        return '``\n%s``' % text
2798    if tag == 'a':
2799        return '[[%s %s]]' % (text, attr.get('_href', ''))
2800    if tag == 'img':
2801        return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
2802    return text
2803
2804
2805class MARKMIN(XmlComponent):
2806    """
2807    For documentation: http://web2py.com/examples/static/markmin.html
2808    """
2809    def __init__(self,
2810                 text, extra=None, allowed=None, sep='p',
2811                 url=None, environment=None, latex='google',
2812                 autolinks='default',
2813                 protolinks='default',
2814                 class_prefix='',
2815                 id_prefix='markmin_',
2816                 **kwargs):
2817        self.text = to_bytes(text)
2818        self.extra = extra or {}
2819        self.allowed = allowed or {}
2820        self.sep = sep
2821        self.url = URL if url is True else url
2822        self.environment = environment
2823        self.latex = latex
2824        self.autolinks = autolinks
2825        self.protolinks = protolinks
2826        self.class_prefix = class_prefix
2827        self.id_prefix = id_prefix
2828        self.kwargs = kwargs
2829
2830    def flatten(self):
2831        return self.text
2832
2833    def xml(self):
2834        from gluon.contrib.markmin.markmin2html import render
2835        html = render(self.text, extra=self.extra,
2836                      allowed=self.allowed, sep=self.sep, latex=self.latex,
2837                      URL=self.url, environment=self.environment,
2838                      autolinks=self.autolinks, protolinks=self.protolinks,
2839                      class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2840        return to_bytes(html) if not self.kwargs else to_bytes(DIV(XML(html), **self.kwargs).xml())
2841
2842    def __str__(self):
2843        # In PY3 __str__ cannot return bytes (TypeError: __str__ returned non-string (type bytes))
2844        return to_native(self.xml())
2845
2846
2847def ASSIGNJS(**kargs):
2848    """
2849    Example:
2850        ASSIGNJS(var1='1', var2='2') will return the following javascript variables assignations :
2851
2852            var var1 = "1";
2853            var var2 = "2";
2854
2855    Args:
2856        **kargs: Any keywords arguments and assigned values.
2857
2858    Returns:
2859        Javascript vars assignations for the key/value passed.
2860
2861    """
2862    from gluon.serializers import json
2863    s = ""
2864    for key, value in kargs.items():
2865        s += 'var %s = %s;\n' % (key, json(value))
2866    return XML(s)
2867
2868
2869if __name__ == '__main__':
2870    import doctest
2871    doctest.testmod()
Note: See TracBrowser for help on using the repository browser.