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

Source Code for Module web2py.gluon.html

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