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

Source Code for Module web2py.gluon.html

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