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