Package web2py :: Package gluon :: Module html |
[hide private]
[frames] | no frames]

# Source Code for Module web2py.gluon.html

       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  """
       9
      10  import cgi
      11  import os
      12  import re
      13  import copy
      14  import types
      15  import urllib
      16  import base64
      17  import sanitizer
      18  import rewrite
      19  import itertools
      20  import decoder
      21  import copy_reg
      22  import cPickle
      23  import marshal
      24  from HTMLParser import HTMLParser
      25  from htmlentitydefs import name2codepoint
      26  from contrib.markmin.markmin2html import render
      27
      28  from storage import Storage
      29  from highlight import highlight
      30  from utils import web2py_uuid, hmac_hash
      31
      32  import hmac
      33  import hashlib
      34
      35  regex_crlf = re.compile('\r|\n')
      36
      37  join = ''.join
      38
      39  __all__ = [
      40      'A',
      41      'B',
      42      'BEAUTIFY',
      43      'BODY',
      44      'BR',
      45      'BUTTON',
      46      'CENTER',
      47      'CAT',
      48      'CODE',
      49      'DIV',
      50      'EM',
      51      'EMBED',
      52      'FIELDSET',
      53      'FORM',
      54      'H1',
      55      'H2',
      56      'H3',
      57      'H4',
      58      'H5',
      59      'H6',
      60      'HEAD',
      61      'HR',
      62      'HTML',
      63      'I',
      64      'IFRAME',
      65      'IMG',
      66      'INPUT',
      67      'LABEL',
      68      'LEGEND',
      69      'LI',
      70      'LINK',
      71      'OL',
      72      'UL',
      73      'MARKMIN',
      74      'MENU',
      75      'META',
      76      'OBJECT',
      77      'ON',
      78      'OPTION',
      79      'P',
      80      'PRE',
      81      'SCRIPT',
      82      'OPTGROUP',
      83      'SELECT',
      84      'SPAN',
      85      'STYLE',
      86      'TABLE',
      87      'TAG',
      88      'TD',
      89      'TEXTAREA',
      90      'TH',
      91      'THEAD',
      92      'TBODY',
      93      'TFOOT',
      94      'TITLE',
      95      'TR',
      96      'TT',
      97      'URL',
      98      'XHTML',
      99      'XML',
     100      'xmlescape',
     101      'embed64',
     102      ]
     103
     104
     105 -def xmlescape(data, quote = True):
     106      """
     107      returns an escaped string of the provided data
     108
     109      :param data: the data to be escaped
     110      :param quote: optional (default False)
     111      """
     112
     113      # first try the xml function
     114      if hasattr(data,'xml') and callable(data.xml):
     115          return data.xml()
     116
     117      # otherwise, make it a string
     118      if not isinstance(data, (str, unicode)):
     119          data = str(data)
     120      elif isinstance(data, unicode):
     121          data = data.encode('utf8', 'xmlcharrefreplace')
     122
     123      # ... and do the escaping
     124      data = cgi.escape(data, quote).replace("'","&#x27;")
     125      return data
     126
     127
     128 -def URL(
     129      a=None,
     130      c=None,
     131      f=None,
     132      r=None,
     133      args=[],
     134      vars={},
     135      anchor='',
     136      extension=None,
     137      env=None,
     138      hmac_key=None,
     139      hash_vars=True,
     140      salt=None,
     141      user_signature=None,
     142      scheme=None,
     143      host=None,
     144      port=None,
     145      ):
     146      """
     147      generate a URL
     148
     149      example::
     150
     151          >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
     152          ...     vars={'p':1, 'q':2}, anchor='1'))
     153          '/a/c/f/x/y/z?p=1&q=2#1'
     154
     155          >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
     156          ...     vars={'p':(1,3), 'q':2}, anchor='1'))
     157          '/a/c/f/x/y/z?p=1&p=3&q=2#1'
     158
     159          >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
     160          ...     vars={'p':(3,1), 'q':2}, anchor='1'))
     161          '/a/c/f/x/y/z?p=3&p=1&q=2#1'
     162
     163          >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
     164          '/a/c/f#1%2B2'
     165
     166          >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
     167          ...     vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
     168          '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1'
     169
     170      generates a url '/a/c/f' corresponding to application a, controller c
     171      and function f. If r=request is passed, a, c, f are set, respectively,
     172      to r.application, r.controller, r.function.
     173
     174      The more typical usage is:
     175
     176      URL(r=request, f='index') that generates a url for the index function
     177      within the present application and controller.
     178
     179      :param a: application (default to current if r is given)
     180      :param c: controller (default to current if r is given)
     181      :param f: function (default to current if r is given)
     182      :param r: request (optional)
     183      :param args: any arguments (optional)
     184      :param vars: any variables (optional)
     185      :param anchor: anchorname, without # (optional)
     186      :param hmac_key: key to use when generating hmac signature (optional)
     187      :param hash_vars: which of the vars to include in our hmac signature
     188          True (default) - hash all vars, False - hash none of the vars,
     189          iterable - hash only the included vars ['key1','key2']
     190      :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
     191      :param host: string to force absolute URL with host (True means http_host)
     192      :param port: optional port number (forces absolute URL)
     193
     194      :raises SyntaxError: when no application, controller or function is
     195          available
     196      :raises SyntaxError: when a CRLF is found in the generated url
     197      """
     198
     199      if args in (None,[]): args = []
     200      vars = vars or {}
     201      application = None
     202      controller = None
     203      function = None
     204
     205      if not r:
     206          if a and not c and not f: (f,a,c)=(a,c,f)
     207          elif a and c and not f: (c,f,a)=(a,c,f)
     208          from globals import current
     209          if hasattr(current,'request'):
     210              r = current.request
     211      if r:
     212          application = r.application
     213          controller = r.controller
     214          function = r.function
     215          env = r.env
     216          if extension is None and r.extension != 'html':
     217              extension = r.extension
     218      if a:
     219          application = a
     220      if c:
     221          controller = c
     222      if f:
     223          if not isinstance(f, str):
     224              function = f.__name__
     225          elif '.' in f:
     226              function, extension = f.split('.', 1)
     227          else:
     228              function = f
     229
     230      function2 = '%s.%s' % (function,extension or 'html')
     231
     232      if not (application and controller and function):
     233          raise SyntaxError, 'not enough information to build the url'
     234
     235      if not isinstance(args, (list, tuple)):
     236          args = [args]
     237      other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
     238      if other.endswith('/'):
     239          other += '/'    # add trailing slash to make last trailing empty arg explicit
     240
     241      if vars.has_key('_signature'): vars.pop('_signature')
     242      list_vars = []
     243      for (key, vals) in sorted(vars.items()):
     244          if not isinstance(vals, (list, tuple)):
     245              vals = [vals]
     246          for val in vals:
     247              list_vars.append((key, val))
     248
     249      if user_signature:
     250          from globals import current
     251          if current.session.auth:
     252              hmac_key = current.session.auth.hmac_key
     253
     254      if hmac_key:
     255          # generate an hmac signature of the vars & args so can later
     256          # verify the user hasn't messed with anything
     257
     258          h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
     259
     260          # how many of the vars should we include in our hash?
     261          if hash_vars is True:       # include them all
     262              h_vars = list_vars
     263          elif hash_vars is False:    # include none of them
     264              h_vars = ''
     265          else:                       # include just those specified
     266              if hash_vars and not isinstance(hash_vars, (list, tuple)):
     267                  hash_vars = [hash_vars]
     268              h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
     269
     270          # re-assembling the same way during hash authentication
     271          message = h_args + '?' + urllib.urlencode(sorted(h_vars))
     272
     273          sig = hmac_hash(message,hmac_key,salt=salt)
     274          # add the signature into vars
     275          list_vars.append(('_signature', sig))
     276
     277      if list_vars:
     278          other += '?%s' % urllib.urlencode(list_vars)
     279      if anchor:
     280          other += '#' + urllib.quote(str(anchor))
     281      if extension:
     282          function += '.' + extension
     283
     284      if regex_crlf.search(join([application, controller, function, other])):
     285          raise SyntaxError, 'CRLF Injection Detected'
     286      url = rewrite.url_out(r, env, application, controller, function,
     287                            args, other, scheme, host, port)
     288      return url
     289
     290
     291 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
     292      """
     293      Verifies that a request's args & vars have not been tampered with by the user
     294
     295      :param request: web2py's request object
     296      :param hmac_key: the key to authenticate with, must be the same one previously
     297                      used when calling URL()
     298      :param hash_vars: which vars to include in our hashing. (Optional)
     299                      Only uses the 1st value currently
     300                      True (or undefined) means all, False none,
     301                      an iterable just the specified keys
     302
     303      do not call directly. Use instead:
     304
     305      URL.verify(hmac_key='...')
     306
     307      the key has to match the one used to generate the URL.
     308
     309          >>> r = Storage()
     310          >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6')
     311          >>> r.update(dict(application='a', controller='c', function='f'))
     312          >>> r['args'] = ['x', 'y', 'z']
     313          >>> r['get_vars'] = gv
     314          >>> verifyURL(r, 'key')
     315          True
     316          >>> verifyURL(r, 'kay')
     317          False
     318          >>> r.get_vars.p = (3, 1)
     319          >>> verifyURL(r, 'key')
     320          True
     321          >>> r.get_vars.p = (3, 2)
     322          >>> verifyURL(r, 'key')
     323          False
     324
     325      """
     326
     327      if not request.get_vars.has_key('_signature'):
     328          return False # no signature in the request URL
     329
     330      # check if user_signature requires
     331      if user_signature:
     332          from globals import current
     333          if not current.session:
     334              return False
     335          hmac_key = current.session.auth.hmac_key
     336      if not hmac_key:
     337          return False
     338
     339      # get our sig from request.get_vars for later comparison
     340      original_sig = request.get_vars._signature
     341
     342      # now generate a new hmac for the remaining args & vars
     343      vars, args = request.get_vars, request.args
     344
     345      # remove the signature var since it was not part of our signed message
     346      request.get_vars.pop('_signature')
     347
     348      # join all the args & vars into one long string
     349
     350      # always include all of the args
     351      other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
     352      h_args = '/%s/%s/%s.%s%s' % (request.application,
     353                                   request.controller,
     354                                   request.function,
     355                                   request.extension,
     356                                   other)
     357
     358      # but only include those vars specified (allows more flexibility for use with
     359      # forms or ajax)
     360
     361      list_vars = []
     362      for (key, vals) in sorted(vars.items()):
     363          if not isinstance(vals, (list, tuple)):
     364              vals = [vals]
     365          for val in vals:
     366              list_vars.append((key, val))
     367
     368      # which of the vars are to be included?
     369      if hash_vars is True:       # include them all
     370          h_vars = list_vars
     371      elif hash_vars is False:    # include none of them
     372          h_vars = ''
     373      else:                       # include just those specified
     374          # wrap in a try - if the desired vars have been removed it'll fail
     375          try:
     376              if hash_vars and not isinstance(hash_vars, (list, tuple)):
     377                  hash_vars = [hash_vars]
     378              h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
     379          except:
     380              # user has removed one of our vars! Immediate fail
     381              return False
     382      # build the full message string with both args & vars
     383      message = h_args + '?' + urllib.urlencode(sorted(h_vars))
     384
     385      # hash with the hmac_key provided
     386      sig = hmac_hash(message,str(hmac_key),salt=salt)
     387
     388      # put _signature back in get_vars just in case a second call to URL.verify is performed
     389      # (otherwise it'll immediately return false)
     390      request.get_vars['_signature'] = original_sig
     391
     392      # return whether or not the signature in the request matched the one we just generated
     393      # (I.E. was the message the same as the one we originally signed)
     394      return original_sig == sig
     395
     396  URL.verify = verifyURL
     397
     398  ON = True
     399
     400
     401 -class XmlComponent(object):
     402      """
     403      Abstract root for all Html components
     404      """
     405
     406      # TODO: move some DIV methods to here
     407
     408 -    def xml(self):
     409          raise NotImplementedError
     410
     411
     412 -class XML(XmlComponent):
     413      """
     414      use it to wrap a string that contains XML/HTML so that it will not be
     415      escaped by the template
     416
     417      example:
     418
     419      >>> XML('<h1>Hello</h1>').xml()
     420      '<h1>Hello</h1>'
     421      """
     422
     423 -    def __init__(
     424          self,
     425          text,
     426          sanitize = False,
     427          permitted_tags = [
     428              'a',
     429              'b',
     430              'blockquote',
     431              'br/',
     432              'i',
     433              'li',
     434              'ol',
     435              'ul',
     436              'p',
     437              'cite',
     438              'code',
     439              'pre',
     440              'img/',
     441              'h1','h2','h3','h4','h5','h6',
     442              'table','tr','td','div',
     443              ],
     444          allowed_attributes = {
     445              'a': ['href', 'title'],
     446              'img': ['src', 'alt'],
     447              'blockquote': ['type'],
     448              'td': ['colspan'],
     449              },
     450          ):
     451          """
     452          :param text: the XML text
     453          :param sanitize: sanitize text using the permitted tags and allowed
     454              attributes (default False)
     455          :param permitted_tags: list of permitted tags (default: simple list of
     456              tags)
     457          :param allowed_attributes: dictionary of allowed attributed (default
     458              for A, IMG and BlockQuote).
     459              The key is the tag; the value is a list of allowed attributes.
     460          """
     461
     462          if sanitize:
     463              text = sanitizer.sanitize(text, permitted_tags,
     464                      allowed_attributes)
     465          if isinstance(text, unicode):
     466              text = text.encode('utf8', 'xmlcharrefreplace')
     467          elif not isinstance(text, str):
     468              text = str(text)
     469          self.text = text
     470
     471 -    def xml(self):
     472          return self.text
     473
     474 -    def __str__(self):
     475          return self.xml()
     476
     477 -    def __add__(self,other):
     478          return '%s%s' % (self,other)
     479
     480 -    def __radd__(self,other):
     481          return '%s%s' % (other,self)
     482
     483 -    def __cmp__(self,other):
     484          return cmp(str(self),str(other))
     485
     486 -    def __hash__(self):
     487          return hash(str(self))
     488
     489 -    def __getattr__(self,name):
     490          return getattr(str(self),name)
     491
     492 -    def __getitem__(self,i):
     493          return str(self)[i]
     494
     495 -    def __getslice__(self,i,j):
     496          return str(self)[i:j]
     497
     498 -    def __iter__(self):
     499          for c in str(self): yield c
     500
     501 -    def __len__(self):
     502          return len(str(self))
     503
     504 -    def flatten(self,render=None):
     505          """
     506          return the text stored by the XML object rendered by the render function
     507          """
     508          if render:
     509              return render(self.text,None,{})
     510          return self.text
     511
     512 -    def elements(self, *args, **kargs):
     513          """
     514          to be considered experimental since the behavior of this method is questionable
     515          another options could be TAG(self.text).elements(*args,**kargs)
     516          """
     517          return []
     518
     519  ### important to allow safe session.flash=T(....)
     520 -def XML_unpickle(data):
     521      return marshal.loads(data)
     522 -def XML_pickle(data):
     523      return XML_unpickle, (marshal.dumps(str(data)),)
     524  copy_reg.pickle(XML, XML_pickle, XML_unpickle)
     525
     526
     527
     528 -class DIV(XmlComponent):
     529      """
     530      HTML helper, for easy generating and manipulating a DOM structure.
     531      Little or no validation is done.
     532
     533      Behaves like a dictionary regarding updating of attributes.
     534      Behaves like a list regarding inserting/appending components.
     535
     536      example::
     537
     538          >>> DIV('hello', 'world', _style='color:red;').xml()
     539          '<div style=\"color:red;\">helloworld</div>'
     540
     541      all other HTML helpers are derived from DIV.
     542
     543      _something=\"value\" attributes are transparently translated into
     544      something=\"value\" HTML attributes
     545      """
     546
     547      # name of the tag, subclasses should update this
     548      # tags ending with a '/' denote classes that cannot
     549      # contain components
     550      tag = 'div'
     551
     552 -    def __init__(self, *components, **attributes):
     553          """
     554          :param *components: any components that should be nested in this element
     555          :param **attributes: any attributes you want to give to this element
     556
     557          :raises SyntaxError: when a stand alone tag receives components
     558          """
     559
     560          if self.tag[-1:] == '/' and components:
     561              raise SyntaxError, '<%s> tags cannot have components'\
     562                   % self.tag
     563          if len(components) == 1 and isinstance(components[0], (list,tuple)):
     564              self.components = list(components[0])
     565          else:
     566              self.components = list(components)
     567          self.attributes = attributes
     568          self._fixup()
     569          # converts special attributes in components attributes
     570          self._postprocessing()
     571          self.parent = None
     572          for c in self.components:
     573              self._setnode(c)
     574
     575 -    def update(self, **kargs):
     576          """
     577          dictionary like updating of the tag attributes
     578          """
     579
     580          for (key, value) in kargs.items():
     581              self[key] = value
     582          return self
     583
     584 -    def append(self, value):
     585          """
     586          list style appending of components
     587
     588          >>> a=DIV()
     589          >>> a.append(SPAN('x'))
     590          >>> print a
     591          <div><span>x</span></div>
     592          """
     593          self._setnode(value)
     594          ret = self.components.append(value)
     595          self._fixup()
     596          return ret
     597
     598 -    def insert(self, i, value):
     599          """
     600          list style inserting of components
     601
     602          >>> a=DIV()
     603          >>> a.insert(0,SPAN('x'))
     604          >>> print a
     605          <div><span>x</span></div>
     606          """
     607          self._setnode(value)
     608          ret = self.components.insert(i, value)
     609          self._fixup()
     610          return ret
     611
     612 -    def __getitem__(self, i):
     613          """
     614          gets attribute with name 'i' or component #i.
     615          If attribute 'i' is not found returns None
     616
     617          :param i: index
     618             if i is a string: the name of the attribute
     619             otherwise references to number of the component
     620          """
     621
     622          if isinstance(i, str):
     623              try:
     624                  return self.attributes[i]
     625              except KeyError:
     626                  return None
     627          else:
     628              return self.components[i]
     629
     630 -    def __setitem__(self, i, value):
     631          """
     632          sets attribute with name 'i' or component #i.
     633
     634          :param i: index
     635             if i is a string: the name of the attribute
     636             otherwise references to number of the component
     637          :param value: the new value
     638          """
     639          self._setnode(value)
     640          if isinstance(i, (str, unicode)):
     641              self.attributes[i] = value
     642          else:
     643              self.components[i] = value
     644
     645 -    def __delitem__(self, i):
     646          """
     647          deletes attribute with name 'i' or component #i.
     648
     649          :param i: index
     650             if i is a string: the name of the attribute
     651             otherwise references to number of the component
     652          """
     653
     654          if isinstance(i, str):
     655              del self.attributes[i]
     656          else:
     657              del self.components[i]
     658
     659 -    def __len__(self):
     660          """
     661          returns the number of included components
     662          """
     663          return len(self.components)
     664
     665 -    def __nonzero__(self):
     666          """
     667          always return True
     668          """
     669          return True
     670
     671 -    def _fixup(self):
     672          """
     673          Handling of provided components.
     674
     675          Nothing to fixup yet. May be overridden by subclasses,
     676          eg for wrapping some components in another component or blocking them.
     677          """
     678          return
     679
     680 -    def _wrap_components(self, allowed_parents,
     681                           wrap_parent = None,
     682                           wrap_lambda = None):
     683          """
     684          helper for _fixup. Checks if a component is in allowed_parents,
     685          otherwise wraps it in wrap_parent
     686
     687          :param allowed_parents: (tuple) classes that the component should be an
     688              instance of
     689          :param wrap_parent: the class to wrap the component in, if needed
     690          :param wrap_lambda: lambda to use for wrapping, if needed
     691
     692          """
     693          components = []
     694          for c in self.components:
     695              if isinstance(c, allowed_parents):
     696                  pass
     697              elif wrap_lambda:
     698                  c = wrap_lambda(c)
     699              else:
     700                  c = wrap_parent(c)
     701              if isinstance(c,DIV):
     702                  c.parent = self
     703              components.append(c)
     704          self.components = components
     705
     706 -    def _postprocessing(self):
     707          """
     708          Handling of attributes (normally the ones not prefixed with '_').
     709
     710          Nothing to postprocess yet. May be overridden by subclasses
     711          """
     712          return
     713
     714 -    def _traverse(self, status, hideerror=False):
     715          # TODO: docstring
     716          newstatus = status
     717          for c in self.components:
     718              if hasattr(c, '_traverse') and callable(c._traverse):
     719                  c.vars = self.vars
     720                  c.request_vars = self.request_vars
     721                  c.errors = self.errors
     722                  c.latest = self.latest
     723                  c.session = self.session
     724                  c.formname = self.formname
     725                  c['hideerror']=hideerror
     726                  newstatus = c._traverse(status,hideerror) and newstatus
     727
     728          # for input, textarea, select, option
     729          # deal with 'value' and 'validation'
     730
     731          name = self['_name']
     732          if newstatus:
     733              newstatus = self._validate()
     734              self._postprocessing()
     735          elif 'old_value' in self.attributes:
     736              self['value'] = self['old_value']
     737              self._postprocessing()
     738          elif name and name in self.vars:
     739              self['value'] = self.vars[name]
     740              self._postprocessing()
     741          if name:
     742              self.latest[name] = self['value']
     743          return newstatus
     744
     745 -    def _validate(self):
     746          """
     747          nothing to validate yet. May be overridden by subclasses
     748          """
     749          return True
     750
     751 -    def _setnode(self,value):
     752          if isinstance(value,DIV):
     753              value.parent = self
     754
     755 -    def _xml(self):
     756          """
     757          helper for xml generation. Returns separately:
     758          - the component attributes
     759          - the generated xml of the inner components
     760
     761          Component attributes start with an underscore ('_') and
     762          do not have a False or None value. The underscore is removed.
     763          A value of True is replaced with the attribute name.
     764
     765          :returns: tuple: (attributes, components)
     766          """
     767
     768          # get the attributes for this component
     769          # (they start with '_', others may have special meanings)
     770          fa = ''
     771          for key in sorted(self.attributes):
     772              value = self[key]
     773              if key[:1] != '_':
     774                  continue
     775              name = key[1:]
     776              if value is True:
     777                  value = name
     778              elif value is False or value is None:
     779                  continue
     780              fa += ' %s="%s"' % (name, xmlescape(value, True))
     781
     782          # get the xml for the inner components
     783          co = join([xmlescape(component) for component in
     784                       self.components])
     785
     786          return (fa, co)
     787
     788 -    def xml(self):
     789          """
     790          generates the xml for this component.
     791          """
     792
     793          (fa, co) = self._xml()
     794
     795          if not self.tag:
     796              return co
     797
     798          if self.tag[-1:] == '/':
     799              # <tag [attributes] />
     800              return '<%s%s />' % (self.tag[:-1], fa)
     801
     802          # else: <tag [attributes]>  inner components xml </tag>
     803          return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
     804
     805 -    def __str__(self):
     806          """
     807          str(COMPONENT) returns equals COMPONENT.xml()
     808          """
     809
     810          return self.xml()
     811
     812 -    def flatten(self, render=None):
     813          """
     814          return the text stored by the DIV object rendered by the render function
     815          the render function must take text, tagname, and attributes
     816          render=None is equivalent to render=lambda text, tag, attr: text
     817
     818          >>> markdown = lambda text,tag=None,attributes={}: \
     819                          {None: re.sub('\s+',' ',text), \
     820                           'h1':'#'+text+'\\n\\n', \
     821                           'p':text+'\\n'}.get(tag,text)
     822          >>> a=TAG('<h1>Header</h1><p>this is a     test</p>')
     823          >>> a.flatten(markdown)
     824          '#Header\\n\\nthis is a test\\n'
     825          """
     826
     827          text = ''
     828          for c in self.components:
     829              if isinstance(c,XmlComponent):
     830                  s=c.flatten(render)
     831              elif render:
     832                  s=render(str(c))
     833              else:
     834                  s=str(c)
     835              text+=s
     836          if render:
     837              text = render(text,self.tag,self.attributes)
     838          return text
     839
     840      regex_tag=re.compile('^[\w\-\:]+')
     841      regex_id=re.compile('#([\w\-]+)')
     842      regex_class=re.compile('\.([\w\-]+)')
     843      regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]')
     844
     845
     846 -    def elements(self, *args, **kargs):
     847          """
     848          find all component that match the supplied attribute dictionary,
     849          or None if nothing could be found
     850
     851          All components of the components are searched.
     852
     853          >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
     854          >>> for c in a.elements('span',first_only=True): c[0]='z'
     855          >>> print a
     856          <div><div><span>z</span>3<div><span>y</span></div></div></div>
     857          >>> for c in a.elements('span'): c[0]='z'
     858          >>> print a
     859          <div><div><span>z</span>3<div><span>z</span></div></div></div>
     860
     861          It also supports a syntax compatible with jQuery
     862
     863          >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
     864          >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
     865          hello
     866          world
     867          >>> for e in a.elements('#1-1'): print e.flatten()
     868          hello
     869          >>> a.elements('a[u:v=$]')[0].xml()
     870          '<a id="1-1" u:v="$">hello</a>'
     871
     872          >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
     873          >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
     874          >>> a.xml()
     875          '<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>'
     876          """
     877          if len(args)==1:
     878              args = [a.strip() for a in args[0].split(',')]
     879          if len(args)>1:
     880              subset = [self.elements(a,**kargs) for a in args]
     881              return reduce(lambda a,b:a+b,subset,[])
     882          elif len(args)==1:
     883              items = args[0].split()
     884              if len(items)>1:
     885                  subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])]
     886                  return reduce(lambda a,b:a+b,subset,[])
     887              else:
     888                  item=items[0]
     889                  if '#' in item or '.' in item or '[' in item:
     890                      match_tag = self.regex_tag.search(item)
     891                      match_id = self.regex_id.search(item)
     892                      match_class = self.regex_class.search(item)
     893                      match_attr = self.regex_attr.finditer(item)
     894                      args = []
     895                      if match_tag: args = [match_tag.group()]
     896                      if match_id: kargs['_id'] = match_id.group(1)
     897                      if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \
     898                         match_class.group(1).replace('-','\\-').replace(':','\\:'))
     899                      for item in match_attr:
     900                          kargs['_'+item.group(1)]=item.group(2)
     901                      return self.elements(*args,**kargs)
     902          # make a copy of the components
     903          matches = []
     904          first_only = False
     905          if kargs.has_key("first_only"):
     906              first_only = kargs["first_only"]
     907              del kargs["first_only"]
     908          # check if the component has an attribute with the same
     909          # value as provided
     910          check = True
     911          tag = getattr(self,'tag').replace("/","")
     912          if args and tag not in args:
     913              check = False
     914          for (key, value) in kargs.items():
     915              if isinstance(value,(str,int)):
     916                  if self[key] != str(value):
     917                      check = False
     918              elif key in self.attributes:
     919                  if not value.search(str(self[key])):
     920                      check = False
     921              else:
     922                  check = False
     923          if 'find' in kargs:
     924              find = kargs['find']
     925              for c in self.components:
     926                  if isinstance(find,(str,int)):
     927                      if isinstance(c,str) and str(find) in c:
     928                          check = True
     929                  else:
     930                      if isinstance(c,str) and find.search(c):
     931                          check = True
     932          # if found, return the component
     933          if check:
     934              matches.append(self)
     935              if first_only:
     936                  return matches
     937          # loop the copy
     938          for c in self.components:
     939              if isinstance(c, XmlComponent):
     940                  kargs['first_only'] = first_only
     941                  child_matches = c.elements( *args,  **kargs )
     942                  if first_only  and len(child_matches) != 0:
     943                      return child_matches
     944                  matches.extend( child_matches )
     945          return matches
     946
     947
     948 -    def element(self, *args, **kargs):
     949          """
     950          find the first component that matches the supplied attribute dictionary,
     951          or None if nothing could be found
     952
     953          Also the components of the components are searched.
     954          """
     955          kargs['first_only'] = True
     956          elements = self.elements(*args, **kargs)
     957          if not elements:
     958              # we found nothing
     959              return None
     960          return elements[0]
     961
     962 -    def siblings(self,*args,**kargs):
     963          """
     964          find all sibling components that match the supplied argument list
     965          and attribute dictionary, or None if nothing could be found
     966          """
     967          sibs = [s for s in self.parent.components if not s == self]
     968          matches = []
     969          first_only = False
     970          if kargs.has_key("first_only"):
     971              first_only = kargs["first_only"]
     972              del kargs["first_only"]
     973          for c in sibs:
     974              try:
     975                  check = True
     976                  tag = getattr(c,'tag').replace("/","")
     977                  if args and tag not in args:
     978                          check = False
     979                  for (key, value) in kargs.items():
     980                      if c[key] != value:
     981                              check = False
     982                  if check:
     983                      matches.append(c)
     984                      if first_only: break
     985              except:
     986                  pass
     987          return matches
     988
     989 -    def sibling(self,*args,**kargs):
     990          """
     991          find the first sibling component that match the supplied argument list
     992          and attribute dictionary, or None if nothing could be found
     993          """
     994          kargs['first_only'] = True
     995          sibs = self.siblings(*args, **kargs)
     996          if not sibs:
     997              return None
     998          return sibs[0]
     999
    1000 -class CAT(DIV):
    1001
    1002      tag = ''
    1003
    1004 -def TAG_unpickler(data):
    1005      return cPickle.loads(data)
    1006
    1007 -def TAG_pickler(data):
    1008      d = DIV()
    1009      d.__dict__ = data.__dict__
    1010      marshal_dump = cPickle.dumps(d)
    1011      return (TAG_unpickler, (marshal_dump,))
    1012
    1013 -class __TAG__(XmlComponent):
    1014
    1015      """
    1016      TAG factory example::
    1017
    1018          >>> print TAG.first(TAG.second('test'), _key = 3)
    1019          <first key=\"3\"><second>test</second></first>
    1020
    1021      """
    1022
    1023 -    def __getitem__(self, name):
    1024          return self.__getattr__(name)
    1025
    1026 -    def __getattr__(self, name):
    1027          if name[-1:] == '_':
    1028              name = name[:-1] + '/'
    1029          if isinstance(name,unicode):
    1030              name = name.encode('utf-8')
    1031          class __tag__(DIV):
    1032              tag = name
    1033          copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
    1034          return lambda *a, **b: __tag__(*a, **b)
    1035
    1036 -    def __call__(self,html):
    1037          return web2pyHTMLParser(decoder.decoder(html)).tree
    1038
    1039  TAG = __TAG__()
    1040
    1041
    1042 -class HTML(DIV):
    1043      """
    1044      There are four predefined document type definitions.
    1045      They can be specified in the 'doctype' parameter:
    1046
    1047      -'strict' enables strict doctype
    1048      -'transitional' enables transitional doctype (default)
    1049      -'frameset' enables frameset doctype
    1050      -'html5' enables HTML 5 doctype
    1051      -any other string will be treated as user's own doctype
    1052
    1053      'lang' parameter specifies the language of the document.
    1054      Defaults to 'en'.
    1055
    1056      See also :class:`DIV`
    1057      """
    1058
    1059      tag = 'html'
    1060
    1061      strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
    1062      transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
    1063      frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
    1064      html5 = '<!DOCTYPE HTML>\n'
    1065
    1066 -    def xml(self):
    1067          lang = self['lang']
    1068          if not lang:
    1069              lang = 'en'
    1070          self.attributes['_lang'] = lang
    1071          doctype = self['doctype']
    1072          if doctype:
    1073              if doctype == 'strict':
    1074                  doctype = self.strict
    1075              elif doctype == 'transitional':
    1076                  doctype = self.transitional
    1077              elif doctype == 'frameset':
    1078                  doctype = self.frameset
    1079              elif doctype == 'html5':
    1080                  doctype = self.html5
    1081              else:
    1082                  doctype = '%s\n' % doctype
    1083          else:
    1084              doctype = self.transitional
    1085          (fa, co) = self._xml()
    1086          return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
    1087
    1088 -class XHTML(DIV):
    1089      """
    1090      This is XHTML version of the HTML helper.
    1091
    1092      There are three predefined document type definitions.
    1093      They can be specified in the 'doctype' parameter:
    1094
    1095      -'strict' enables strict doctype
    1096      -'transitional' enables transitional doctype (default)
    1097      -'frameset' enables frameset doctype
    1098      -any other string will be treated as user's own doctype
    1099
    1100      'lang' parameter specifies the language of the document and the xml document.
    1101      Defaults to 'en'.
    1102
    1103      'xmlns' parameter specifies the xml namespace.
    1104      Defaults to 'http://www.w3.org/1999/xhtml'.
    1105
    1106      See also :class:`DIV`
    1107      """
    1108
    1109      tag = 'html'
    1110
    1111      strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
    1112      transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
    1113      frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
    1114      xmlns = 'http://www.w3.org/1999/xhtml'
    1115
    1116 -    def xml(self):
    1117          xmlns = self['xmlns']
    1118          if xmlns:
    1119              self.attributes['_xmlns'] = xmlns
    1120          else:
    1121              self.attributes['_xmlns'] = self.xmlns
    1122          lang = self['lang']
    1123          if not lang:
    1124              lang = 'en'
    1125          self.attributes['_lang'] = lang
    1126          self.attributes['_xml:lang'] = lang
    1127          doctype = self['doctype']
    1128          if doctype:
    1129              if doctype == 'strict':
    1130                  doctype = self.strict
    1131              elif doctype == 'transitional':
    1132                  doctype = self.transitional
    1133              elif doctype == 'frameset':
    1134                  doctype = self.frameset
    1135              else:
    1136                  doctype = '%s\n' % doctype
    1137          else:
    1138              doctype = self.transitional
    1139          (fa, co) = self._xml()
    1140          return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
    1141
    1142
    1143 -class HEAD(DIV):
    1144
    1145      tag = 'head'
    1146
    1147 -class TITLE(DIV):
    1148
    1149      tag = 'title'
    1150
    1151
    1152 -class META(DIV):
    1153
    1154      tag = 'meta/'
    1155
    1156
    1157 -class LINK(DIV):
    1158
    1159      tag = 'link/'
    1160
    1161
    1162 -class SCRIPT(DIV):
    1163
    1164      tag = 'script'
    1165
    1166 -    def xml(self):
    1167          (fa, co) = self._xml()
    1168          # no escaping of subcomponents
    1169          co = '\n'.join([str(component) for component in
    1170                         self.components])
    1171          if co:
    1172              # <script [attributes]><!--//--><![CDATA[//><!--
    1173              # script body
    1174              # //--><!]]></script>
    1175              # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
    1176              return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
    1177          else:
    1178              return DIV.xml(self)
    1179
    1180
    1181 -class STYLE(DIV):
    1182
    1183      tag = 'style'
    1184
    1185 -    def xml(self):
    1186          (fa, co) = self._xml()
    1187          # no escaping of subcomponents
    1188          co = '\n'.join([str(component) for component in
    1189                         self.components])
    1190          if co:
    1191              # <style [attributes]><!--/*--><![CDATA[/*><!--*/
    1192              # style body
    1193              # /*]]>*/--></style>
    1194              return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
    1195          else:
    1196              return DIV.xml(self)
    1197
    1198
    1199 -class IMG(DIV):
    1200
    1201      tag = 'img/'
    1202
    1203
    1204 -class SPAN(DIV):
    1205
    1206      tag = 'span'
    1207
    1208
    1209 -class BODY(DIV):
    1210
    1211      tag = 'body'
    1212
    1213
    1214 -class H1(DIV):
    1215
    1216      tag = 'h1'
    1217
    1218
    1219 -class H2(DIV):
    1220
    1221      tag = 'h2'
    1222
    1223
    1224 -class H3(DIV):
    1225
    1226      tag = 'h3'
    1227
    1228
    1229 -class H4(DIV):
    1230
    1231      tag = 'h4'
    1232
    1233
    1234 -class H5(DIV):
    1235
    1236      tag = 'h5'
    1237
    1238
    1239 -class H6(DIV):
    1240
    1241      tag = 'h6'
    1242
    1243
    1244 -class P(DIV):
    1245      """
    1246      Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
    1247
    1248      see also :class:`DIV`
    1249      """
    1250
    1251      tag = 'p'
    1252
    1253 -    def xml(self):
    1254          text = DIV.xml(self)
    1255          if self['cr2br']:
    1256              text = text.replace('\n', '<br />')
    1257          return text
    1258
    1259
    1260 -class B(DIV):
    1261
    1262      tag = 'b'
    1263
    1264
    1265 -class BR(DIV):
    1266
    1267      tag = 'br/'
    1268
    1269
    1270 -class HR(DIV):
    1271
    1272      tag = 'hr/'
    1273
    1274
    1275 -class A(DIV):
    1276
    1277      tag = 'a'
    1278
    1279 -    def xml(self):
    1280          if self['callback']:
    1281              self['_onclick']="ajax('%s',[],'%s');return false;" % \
    1282                  (self['callback'],self['target'] or '')
    1283              self['_href'] = self['_href'] or '#null'
    1284          elif self['cid']:
    1285              self['_onclick']='web2py_component("%s","%s");return false;' % \
    1286                  (self['_href'],self['cid'])
    1287          return DIV.xml(self)
    1288
    1289
    1290 -class BUTTON(DIV):
    1291
    1292      tag = 'button'
    1293
    1294
    1295 -class EM(DIV):
    1296
    1297      tag = 'em'
    1298
    1299
    1300 -class EMBED(DIV):
    1301
    1302      tag = 'embed/'
    1303
    1304
    1305 -class TT(DIV):
    1306
    1307      tag = 'tt'
    1308
    1309
    1310 -class PRE(DIV):
    1311
    1312      tag = 'pre'
    1313
    1314
    1315 -class CENTER(DIV):
    1316
    1317      tag = 'center'
    1318
    1319
    1320 -class CODE(DIV):
    1321
    1322      """
    1323      displays code in HTML with syntax highlighting.
    1324
    1325      :param attributes: optional attributes:
    1326
    1327          - language: indicates the language, otherwise PYTHON is assumed
    1328          - link: can provide a link
    1329          - styles: for styles
    1330
    1331      Example::
    1332
    1333          {{=CODE(\"print 'hello world'\", language='python', link=None,
    1334              counter=1, styles={}, highlight_line=None)}}
    1335
    1336
    1337      supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
    1338      \"web2py\", \"html\".
    1339      The \"html\" language interprets {{ and }} tags as \"web2py\" code,
    1340      \"html_plain\" doesn't.
    1341
    1342      if a link='/examples/global/vars/' is provided web2py keywords are linked to
    1343      the online docs.
    1344
    1345      the counter is used for line numbering, counter can be None or a prompt
    1346      string.
    1347      """
    1348
    1349 -    def xml(self):
    1350          language = self['language'] or 'PYTHON'
    1351          link = self['link']
    1352          counter = self.attributes.get('counter', 1)
    1353          highlight_line = self.attributes.get('highlight_line', None)
    1354          styles = self['styles'] or {}
    1355          return highlight(
    1356              join(self.components),
    1357              language=language,
    1358              link=link,
    1359              counter=counter,
    1360              styles=styles,
    1361              attributes=self.attributes,
    1362              highlight_line=highlight_line,
    1363              )
    1364
    1365
    1366 -class LABEL(DIV):
    1367
    1368      tag = 'label'
    1369
    1370
    1371 -class LI(DIV):
    1372
    1373      tag = 'li'
    1374
    1375
    1376 -class UL(DIV):
    1377      """
    1378      UL Component.
    1379
    1380      If subcomponents are not LI-components they will be wrapped in a LI
    1381
    1382      see also :class:`DIV`
    1383      """
    1384
    1385      tag = 'ul'
    1386
    1387 -    def _fixup(self):
    1388          self._wrap_components(LI, LI)
    1389
    1390
    1391 -class OL(UL):
    1392
    1393      tag = 'ol'
    1394
    1395
    1396 -class TD(DIV):
    1397
    1398      tag = 'td'
    1399
    1400
    1401 -class TH(DIV):
    1402
    1403      tag = 'th'
    1404
    1405
    1406 -class TR(DIV):
    1407      """
    1408      TR Component.
    1409
    1410      If subcomponents are not TD/TH-components they will be wrapped in a TD
    1411
    1412      see also :class:`DIV`
    1413      """
    1414
    1415      tag = 'tr'
    1416
    1417 -    def _fixup(self):
    1418          self._wrap_components((TD, TH), TD)
    1419
    1420 -class THEAD(DIV):
    1421
    1422      tag = 'thead'
    1423
    1424 -    def _fixup(self):
    1425          self._wrap_components(TR, TR)
    1426
    1427
    1428 -class TBODY(DIV):
    1429
    1430      tag = 'tbody'
    1431
    1432 -    def _fixup(self):
    1433          self._wrap_components(TR, TR)
    1434
    1435
    1436 -class TFOOT(DIV):
    1437
    1438      tag = 'tfoot'
    1439
    1440 -    def _fixup(self):
    1441          self._wrap_components(TR, TR)
    1442
    1443
    1444 -class COL(DIV):
    1445
    1446      tag = 'col'
    1447
    1448
    1449 -class COLGROUP(DIV):
    1450
    1451      tag = 'colgroup'
    1452
    1453
    1454 -class TABLE(DIV):
    1455      """
    1456      TABLE Component.
    1457
    1458      If subcomponents are not TR/TBODY/THEAD/TFOOT-components
    1459      they will be wrapped in a TR
    1460
    1461      see also :class:`DIV`
    1462      """
    1463
    1464      tag = 'table'
    1465
    1466 -    def _fixup(self):
    1467          self._wrap_components((TR, TBODY, THEAD, TFOOT, COL, COLGROUP), TR)
    1468
    1469 -class I(DIV):
    1470
    1471      tag = 'i'
    1472
    1473 -class IFRAME(DIV):
    1474
    1475      tag = 'iframe'
    1476
    1477
    1478 -class INPUT(DIV):
    1479
    1480      """
    1481          INPUT Component
    1482
    1483          examples::
    1484
    1485              >>> INPUT(_type='text', _name='name', value='Max').xml()
    1486              '<input name=\"name\" type=\"text\" value=\"Max\" />'
    1487
    1488              >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml()
    1489              '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />'
    1490
    1491              >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml()
    1492              '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />'
    1493
    1494              >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml()
    1495              '<input name=\"radio\" type=\"radio\" value=\"no\" />'
    1496
    1497          the input helper takes two special attributes value= and requires=.
    1498
    1499          :param value: used to pass the initial value for the input field.
    1500              value differs from _value because it works for checkboxes, radio,
    1501              textarea and select/option too.
    1502
    1503              - for a checkbox value should be '' or 'on'.
    1504              - for a radio or select/option value should be the _value
    1505                  of the checked/selected item.
    1506
    1507          :param requires: should be None, or a validator or a list of validators
    1508              for the value of the field.
    1509          """
    1510
    1511      tag = 'input/'
    1512
    1513 -    def _validate(self):
    1514
    1515          # # this only changes value, not _value
    1516
    1517          name = self['_name']
    1518          if name is None or name == '':
    1519              return True
    1520          name = str(name)
    1521
    1522          if self['_type'] != 'checkbox':
    1523              self['old_value'] = self['value'] or self['_value'] or ''
    1524              value = self.request_vars.get(name, '')
    1525              self['value'] = value
    1526          else:
    1527              self['old_value'] = self['value'] or False
    1528              value = self.request_vars.get(name)
    1529              if isinstance(value, (tuple, list)):
    1530                  self['value'] = self['_value'] in value
    1531              else:
    1532                  self['value'] = self['_value'] == value
    1533          requires = self['requires']
    1534          if requires:
    1535              if not isinstance(requires, (list, tuple)):
    1536                  requires = [requires]
    1537              for validator in requires:
    1538                  (value, errors) = validator(value)
    1539                  if errors != None:
    1540                      self.vars[name] = value
    1541                      self.errors[name] = errors
    1542                      break
    1543          if not name in self.errors:
    1544              self.vars[name] = value
    1545              return True
    1546          return False
    1547
    1548 -    def _postprocessing(self):
    1549          t = self['_type']
    1550          if not t:
    1551              t = self['_type'] = 'text'
    1552          t = t.lower()
    1553          value = self['value']
    1554          if self['_value'] == None:
    1555              _value = None
    1556          else:
    1557              _value = str(self['_value'])
    1558          if t == 'checkbox':
    1559              if not _value:
    1560                  _value = self['_value'] = 'on'
    1561              if not value:
    1562                  value = []
    1563              elif value is True:
    1564                  value = [_value]
    1565              elif not isinstance(value,(list,tuple)):
    1566                  value = str(value).split('|')
    1567              self['_checked'] = _value in value and 'checked' or None
    1568          elif t == 'radio':
    1569              if str(value) == str(_value):
    1570                  self['_checked'] = 'checked'
    1571              else:
    1572                  self['_checked'] = None
    1573          elif t == 'text' or t == 'hidden':
    1574              if value != None:
    1575                  self['_value'] = value
    1576              else:
    1577                  self['value'] = _value
    1578
    1579 -    def xml(self):
    1580          name = self.attributes.get('_name', None)
    1581          if name and hasattr(self, 'errors') \
    1582                  and self.errors.get(name, None) \
    1583                  and self['hideerror'] != True:
    1584              return DIV.xml(self) + DIV(self.errors[name], _class='error',
    1585                  errors=None, _id='%s__error' % name).xml()
    1586          else:
    1587              return DIV.xml(self)
    1588
    1589
    1590 -class TEXTAREA(INPUT):
    1591
    1592      """
    1593      example::
    1594
    1595          TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
    1596
    1597      'blah blah blah ...' will be the content of the textarea field.
    1598      """
    1599
    1600      tag = 'textarea'
    1601
    1602 -    def _postprocessing(self):
    1603          if not '_rows' in self.attributes:
    1604              self['_rows'] = 10
    1605          if not '_cols' in self.attributes:
    1606              self['_cols'] = 40
    1607          if self['value'] != None:
    1608              self.components = [self['value']]
    1609          elif self.components:
    1610              self['value'] = self.components[0]
    1611
    1612
    1613 -class OPTION(DIV):
    1614
    1615      tag = 'option'
    1616
    1617 -    def _fixup(self):
    1618          if not '_value' in self.attributes:
    1619              self.attributes['_value'] = str(self.components[0])
    1620
    1621
    1622 -class OBJECT(DIV):
    1623
    1624      tag = 'object'
    1625
    1626 -class OPTGROUP(DIV):
    1627
    1628      tag = 'optgroup'
    1629
    1630 -    def _fixup(self):
    1631          components = []
    1632          for c in self.components:
    1633              if isinstance(c, OPTION):
    1634                  components.append(c)
    1635              else:
    1636                  components.append(OPTION(c, _value=str(c)))
    1637          self.components = components
    1638
    1639
    1640 -class SELECT(INPUT):
    1641
    1642      """
    1643      example::
    1644
    1645          >>> from validators import IS_IN_SET
    1646          >>> SELECT('yes', 'no', _name='selector', value='yes',
    1647          ...    requires=IS_IN_SET(['yes', 'no'])).xml()
    1648          '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
    1649
    1650      """
    1651
    1652      tag = 'select'
    1653
    1654 -    def _fixup(self):
    1655          components = []
    1656          for c in self.components:
    1657              if isinstance(c, (OPTION, OPTGROUP)):
    1658                  components.append(c)
    1659              else:
    1660                  components.append(OPTION(c, _value=str(c)))
    1661          self.components = components
    1662
    1663 -    def _postprocessing(self):
    1664          component_list = []
    1665          for c in self.components:
    1666              if isinstance(c, OPTGROUP):
    1667                  component_list.append(c.components)
    1668              else:
    1669                  component_list.append([c])
    1670          options = itertools.chain(*component_list)
    1671
    1672          value = self['value']
    1673          if value != None:
    1674              if not self['_multiple']:
    1675                  for c in options: # my patch
    1676                      if value and str(c['_value'])==str(value):
    1677                          c['_selected'] = 'selected'
    1678                      else:
    1679                          c['_selected'] = None
    1680              else:
    1681                  if isinstance(value,(list,tuple)):
    1682                      values = [str(item) for item in value]
    1683                  else:
    1684                      values = [str(value)]
    1685                  for c in options: # my patch
    1686                      if value and str(c['_value']) in values:
    1687                          c['_selected'] = 'selected'
    1688                      else:
    1689                          c['_selected'] = None
    1690
    1691
    1692 -class FIELDSET(DIV):
    1693
    1694      tag = 'fieldset'
    1695
    1696
    1697 -class LEGEND(DIV):
    1698
    1699      tag = 'legend'
    1700
    1701
    1702 -class FORM(DIV):
    1703
    1704      """
    1705      example::
    1706
    1707          >>> from validators import IS_NOT_EMPTY
    1708          >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
    1709          >>> form.xml()
    1710          '<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>'
    1711
    1712      a FORM is container for INPUT, TEXTAREA, SELECT and other helpers
    1713
    1714      form has one important method::
    1715
    1716          form.accepts(request.vars, session)
    1717
    1718      if form is accepted (and all validators pass) form.vars contains the
    1719      accepted vars, otherwise form.errors contains the errors.
    1720      in case of errors the form is modified to present the errors to the user.
    1721      """
    1722
    1723      tag = 'form'
    1724
    1725 -    def __init__(self, *components, **attributes):
    1726          DIV.__init__(self, *components,  **attributes)
    1727          self.vars = Storage()
    1728          self.errors = Storage()
    1729          self.latest = Storage()
    1730
    1731 -    def accepts(
    1732          self,
    1733          vars,
    1734          session=None,
    1735          formname='default',
    1736          keepvalues=False,
    1737          onvalidation=None,
    1738          hideerror=False,
    1739          ):
    1740          if vars.__class__.__name__ == 'Request':
    1741              vars=vars.post_vars
    1742          self.errors.clear()
    1743          self.request_vars = Storage()
    1744          self.request_vars.update(vars)
    1745          self.session = session
    1746          self.formname = formname
    1747          self.keepvalues = keepvalues
    1748
    1749          # if this tag is a form and we are in accepting mode (status=True)
    1750          # check formname and formkey
    1751
    1752          status = True
    1753          if self.session:
    1754              formkey = self.session.get('_formkey[%s]' % self.formname, None)
    1755              # check if user tampering with form and void CSRF
    1756              if formkey != self.request_vars._formkey:
    1757                  status = False
    1758          if self.formname != self.request_vars._formname:
    1759              status = False
    1760          if status and self.session:
    1761              # check if editing a record that has been modified by the server
    1762              if hasattr(self,'record_hash') and self.record_hash != formkey:
    1763                  status = False
    1764                  self.record_changed = True
    1765          status = self._traverse(status,hideerror)
    1766          if onvalidation:
    1767              if isinstance(onvalidation, dict):
    1768                  onsuccess = onvalidation.get('onsuccess', None)
    1769                  onfailure = onvalidation.get('onfailure', None)
    1770                  if onsuccess and status:
    1771                      onsuccess(self)
    1772                  if onfailure and vars and not status:
    1773                      onfailure(self)
    1774                      status = len(self.errors) == 0
    1775              elif status:
    1776                  if isinstance(onvalidation, (list, tuple)):
    1777                      [f(self) for f in onvalidation]
    1778                  else:
    1779                      onvalidation(self)
    1780          if self.errors:
    1781              status = False
    1782          if session != None:
    1783              if hasattr(self,'record_hash'):
    1784                  formkey = self.record_hash
    1785              else:
    1786                  formkey = web2py_uuid()
    1787              self.formkey = session['_formkey[%s]' % formname] = formkey
    1788          if status and not keepvalues:
    1789              self._traverse(False,hideerror)
    1790          return status
    1791
    1792 -    def _postprocessing(self):
    1793          if not '_action' in self.attributes:
    1794              self['_action'] = ''
    1795          if not '_method' in self.attributes:
    1796              self['_method'] = 'post'
    1797          if not '_enctype' in self.attributes:
    1798              self['_enctype'] = 'multipart/form-data'
    1799
    1800 -    def hidden_fields(self):
    1801          c = []
    1802          if 'hidden' in self.attributes:
    1803              for (key, value) in self.attributes.get('hidden',{}).items():
    1804                  c.append(INPUT(_type='hidden', _name=key, _value=value))
    1805
    1806          if hasattr(self, 'formkey') and self.formkey:
    1807              c.append(INPUT(_type='hidden', _name='_formkey',
    1808                       _value=self.formkey))
    1809          if hasattr(self, 'formname') and self.formname:
    1810              c.append(INPUT(_type='hidden', _name='_formname',
    1811                       _value=self.formname))
    1812          return DIV(c, _class="hidden")
    1813
    1814 -    def xml(self):
    1815          newform = FORM(*self.components, **self.attributes)
    1816          hidden_fields = self.hidden_fields()
    1817          if hidden_fields.components:
    1818              newform.append(hidden_fields)
    1819          return DIV.xml(newform)
    1820
    1821 -    def validate(self,
    1822                   values=None,
    1823                   session=None,
    1824                   formname='default',
    1825                   keepvalues=False,
    1826                   onvalidation=None,
    1827                   hideerror=False,
    1828                   onsuccess='flash',
    1829                   onfailure='flash',
    1830                   message_onsuccess=None,
    1831                   message_onfailure=None,
    1832                   ):
    1833          """
    1834          This function validates the form,
    1835          you can use it instead of directly form.accepts.
    1836
    1837          Usage:
    1838          In controller
    1839
    1840          def action():
    1841              form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
    1842              form.validate() #you can pass some args here - see below
    1843              return dict(form=form)
    1844
    1845          This can receive a bunch of arguments
    1846
    1847          onsuccess = 'flash' - will show message_onsuccess in response.flash
    1848                      None - will do nothing
    1849                      can be a function (lambda form: pass)
    1850          onfailure = 'flash' - will show message_onfailure in response.flash
    1851                      None - will do nothing
    1852                      can be a function (lambda form: pass)
    1853
    1854          values = values to test the validation - dictionary, response.vars, session or other - Default to (request.vars, session)
    1855          message_onsuccess
    1856          message_onfailure
    1857          """
    1858          from gluon import current
    1859          if not session: session = current.session
    1860          if not values: values = current.request.post_vars
    1861
    1862          message_onsuccess = message_onsuccess or current.T("Success!")
    1863          message_onfailure = message_onfailure or \
    1864              current.T("Errors in form, please check it out.")
    1865
    1866          if self.accepts(values, session):
    1867              if onsuccess == 'flash':
    1868                  current.response.flash = message_onsuccess
    1869              elif callable(onsuccess):
    1870                  onsuccess(self)
    1871              return True
    1872          elif self.errors:
    1873              if onfailure == 'flash':
    1874                  current.response.flash = message_onfailure
    1875              elif callable(onfailure):
    1876                  onfailure(self)
    1877              return False
    1878
    1879 -    def process(self, values=None, session=None, **args):
    1880          """
    1881          Perform the .validate() method but returns the form
    1882
    1883          Usage in controllers:
    1884          # directly on return
    1885          def action():
    1886              #some code here
    1887              return dict(form=FORM(...).process(...))
    1888
    1889          You can use it with FORM, SQLFORM or FORM based plugins
    1890
    1891          Examples:
    1892          #response.flash messages
    1893          def action():
    1894              form = SQLFORM(db.table).process(message_onsuccess='Sucess!')
    1895              retutn dict(form=form)
    1896
    1897          # callback function
    1898          # callback receives True or False as first arg, and a list of args.
    1899          def my_callback(status, msg):
    1900             response.flash = "Success! "+msg if status else "Errors occured"
    1901
    1902          # after argument can be 'flash' to response.flash messages
    1903          # or a function name to use as callback or None to do nothing.
    1904          def action():
    1905              return dict(form=SQLFORM(db.table).process(onsuccess=my_callback)
    1906          """
    1907          self.validate(values=values, session=session, **args)
    1908          return self
    1909
    1910
    1911 -class BEAUTIFY(DIV):
    1912
    1913      """
    1914      example::
    1915
    1916          >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
    1917          '<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;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>'
    1918
    1919      turns any list, dictionary, etc into decent looking html.
    1920      Two special attributes are
    1921      :sorted: a function that takes the dict and returned sorted keys
    1922      :keyfilter: a funciton that takes a key and returns its representation
    1923                  or None if the key is to be skipped. By default key[:1]=='_' is skipped.
    1924      """
    1925
    1926      tag = 'div'
    1927
    1928      @staticmethod
    1929 -    def no_underscore(key):
    1930          if key[:1]=='_':
    1931              return None
    1932          return key
    1933
    1934 -    def __init__(self, component, **attributes):
    1935          self.components = [component]
    1936          self.attributes = attributes
    1937          sorter = attributes.get('sorted',sorted)
    1938          keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore)
    1939          components = []
    1940          attributes = copy.copy(self.attributes)
    1941          level = attributes['level'] = attributes.get('level',6) - 1
    1942          if '_class' in attributes:
    1943              attributes['_class'] += 'i'
    1944          if level == 0:
    1945              return
    1946          for c in self.components:
    1947              if hasattr(c,'xml') and callable(c.xml):
    1948                  components.append(c)
    1949                  continue
    1950              elif hasattr(c,'keys') and callable(c.keys):
    1951                  rows = []
    1952                  try:
    1953                      keys = (sorter and sorter(c)) or c
    1954                      for key in keys:
    1955                          if isinstance(key,(str,unicode)) and keyfilter:
    1956                              filtered_key = keyfilter(key)
    1957                          else:
    1958                              filtered_key = str(key)
    1959                          if filtered_key is None:
    1960                              continue
    1961                          value = c[key]
    1962                          if type(value) == types.LambdaType:
    1963                              continue
    1964                          rows.append(TR(TD(filtered_key, _style='font-weight:bold;'),
    1965                                         TD(':',_valign='top'),
    1966                                         TD(BEAUTIFY(value, **attributes))))
    1967                      components.append(TABLE(*rows, **attributes))
    1968                      continue
    1969                  except:
    1970                      pass
    1971              if isinstance(c, str):
    1972                  components.append(str(c))
    1973              elif isinstance(c, unicode):
    1974                  components.append(c.encode('utf8'))
    1975              elif isinstance(c, (list, tuple)):
    1976                  items = [TR(TD(BEAUTIFY(item, **attributes)))
    1977                           for item in c]
    1978                  components.append(TABLE(*items, **attributes))
    1979              elif isinstance(c, cgi.FieldStorage):
    1980                  components.append('FieldStorage object')
    1981              else:
    1982                  components.append(repr(c))
    1983          self.components = components
    1984
    1985
    2043
    2044
    2045 -def embed64(
    2046      filename = None,
    2047      file = None,
    2048      data = None,
    2049      extension = 'image/gif',
    2050      ):
    2051      """
    2052      helper to encode the provided (binary) data into base64.
    2053
    2054      :param filename: if provided, opens and reads this file in 'rb' mode
    2055      :param file: if provided, reads this file
    2056      :param data: if provided, uses the provided data
    2057      """
    2058
    2059      if filename and os.path.exists(file):
    2060          fp = open(filename, 'rb')
    2061          data = fp.read()
    2062          fp.close()
    2063      data = base64.b64encode(data)
    2064      return 'data:%s;base64,%s' % (extension, data)
    2065
    2066
    2067 -def test():
    2068      """
    2069      Example:
    2070
    2071      >>> from validators import *
    2072      >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
    2073      <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
    2074      >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
    2075      <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
    2076      >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
    2077      <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
    2078      >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
    2079      <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>
    2080      >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
    2081      >>> print form.xml()
    2082      <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
    2083      >>> print form.accepts({'myvar':'34'}, formname=None)
    2084      False
    2085      >>> print form.xml()
    2086      <form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form>
    2087      >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
    2088      True
    2089      >>> print form.xml()
    2090      <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
    2091      >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
    2092      >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
    2093      True
    2094      >>> print form.xml()
    2095      <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>
    2096      >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
    2097      >>> print form.accepts({'myvar':'as df'}, formname=None)
    2098      False
    2099      >>> print form.xml()
    2100      <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"as df\" /><div class=\"error\" id=\"myvar__error\">only alphanumeric!</div></form>
    2101      >>> session={}
    2102      >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
    2103      >>> if form.accepts({}, session,formname=None): print 'passed'
    2104      >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
    2105      """
    2106      pass
    2107
    2108
    2109 -class web2pyHTMLParser(HTMLParser):
    2110      """
    2111      obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
    2112      obj.tree contains the root of the tree, and tree can be manipulated
    2113
    2114      >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree)
    2115      'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz'
    2116      >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
    2117      '<div>a<span>b</span></div>c'
    2118      >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
    2119      >>> tree.element(_a='b')['_c']=5
    2120      >>> str(tree)
    2121      'hello<div a="b" c="5">world</div>'
    2122      """
    2123 -    def __init__(self,text,closed=('input','link')):
    2124          HTMLParser.__init__(self)
    2125          self.tree = self.parent = TAG['']()
    2126          self.closed = closed
    2127          self.tags = [x for x in __all__ if isinstance(eval(x),DIV)]
    2128          self.last = None
    2129          self.feed(text)
    2130 -    def handle_starttag(self, tagname, attrs):
    2131          if tagname.upper() in self.tags:
    2132              tag=eval(tagname.upper())
    2133          else:
    2134              if tagname in self.closed: tagname+='/'
    2135              tag = TAG[tagname]()
    2136          for key,value in attrs: tag['_'+key]=value
    2137          tag.parent = self.parent
    2138          self.parent.append(tag)
    2139          if not tag.tag.endswith('/'):
    2140              self.parent=tag
    2141          else:
    2142              self.last = tag.tag[:-1]
    2143 -    def handle_data(self,data):
    2144          try:
    2145              self.parent.append(data.encode('utf8','xmlcharref'))
    2146          except:
    2147              self.parent.append(data.decode('latin1').encode('utf8','xmlcharref'))
    2148 -    def handle_charref(self,name):
    2149          if name[1].lower()=='x':
    2150              self.parent.append(unichr(int(name[2:], 16)).encode('utf8'))
    2151          else:
    2152              self.parent.append(unichr(int(name[1:], 10)).encode('utf8'))
    2153 -    def handle_entityref(self,name):
    2154          self.parent.append(unichr(name2codepoint[name]).encode('utf8'))
    2155 -    def handle_endtag(self, tagname):
    2156          # this deals with unbalanced tags
    2157          if tagname==self.last:
    2158              return
    2159          while True:
    2160              try:
    2161                  parent_tagname=self.parent.tag
    2162                  self.parent = self.parent.parent
    2163              except:
    2164                  raise RuntimeError, "unable to balance tag %s" % tagname
    2165              if parent_tagname[:len(tagname)]==tagname: break
    2166
    2167 -def markdown_serializer(text,tag=None,attr={}):
    2168      if tag is None: return re.sub('\s+',' ',text)
    2169      if tag=='br': return '\n\n'
    2170      if tag=='h1': return '#'+text+'\n\n'
    2171      if tag=='h2': return '#'*2+text+'\n\n'
    2172      if tag=='h3': return '#'*3+text+'\n\n'
    2173      if tag=='h4': return '#'*4+text+'\n\n'
    2174      if tag=='p': return text+'\n\n'
    2175      if tag=='b' or tag=='strong': return '**%s**' % text
    2176      if tag=='em' or tag=='i': return '*%s*' % text
    2177      if tag=='tt' or tag=='code': return '`%s`' % text
    2178      if tag=='a': return '[%s](%s)' % (text,attr.get('_href',''))
    2179      if tag=='img': return '![%s](%s)' % (attr.get('_alt',''),attr.get('_src',''))
    2180      return text
    2181
    2182 -def markmin_serializer(text,tag=None,attr={}):
    2183      # if tag is None: return re.sub('\s+',' ',text)
    2184      if tag=='br': return '\n\n'
    2185      if tag=='h1': return '# '+text+'\n\n'
    2186      if tag=='h2': return '#'*2+' '+text+'\n\n'
    2187      if tag=='h3': return '#'*3+' '+text+'\n\n'
    2188      if tag=='h4': return '#'*4+' '+text+'\n\n'
    2189      if tag=='p': return text+'\n\n'
    2190      if tag=='li': return '\n- '+text.replace('\n',' ')
    2191      if tag=='tr': return text[3:].replace('\n',' ')+'\n'
    2192      if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n'
    2193      if tag in ['td','th']: return ' | '+text
    2194      if tag in ['b','strong','label']: return '**%s**' % text
    2195      if tag in ['em','i']: return "''%s''" % text
    2196      if tag in ['tt']: return '``%s``' % text.strip()
    2197      if tag in ['code']: return '``\n%s``' % text
    2198      if tag=='a': return '[[%s %s]]' % (text,attr.get('_href',''))
    2199      if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src',''))
    2200      return text
    2201
    2202
    2203 -class MARKMIN(XmlComponent):
    2204      """
    2205      For documentation: http://web2py.com/examples/static/markmin.html
    2206      """
    2207 -    def __init__(self, text, extra={}, allowed={}, sep='p'):
    2208          self.text = text
    2209          self.extra = extra
    2210          self.allowed = allowed
    2211          self.sep = sep
    2212
    2213 -    def xml(self):
    2214          """
    2215          calls the gluon.contrib.markmin render function to convert the wiki syntax
    2216          """
    2217          return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep)
    2218
    2219 -    def __str__(self):
    2220          return self.xml()
    2221
    2222 -    def flatten(self,render=None):
    2223          """
    2224          return the text stored by the MARKMIN object rendered by the render function
    2225          """
    2226          return self.text
    2227
    2228 -    def elements(self, *args, **kargs):
    2229          """
    2230          to be considered experimental since the behavior of this method is questionable
    2231          another options could be TAG(self.text).elements(*args,**kargs)
    2232          """
    2233          return [self.text]
    2234
    2235
    2236  if __name__ == '__main__':
    2237      import doctest
    2238      doctest.testmod()
    2239