web2py.gluon.html

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