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