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

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