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 storage import Storage 
  28  from utils import web2py_uuid, simple_hash, compare 
  29  from 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 c['hideerror'] = hideerror or \ 845 self.attributes.get('hideerror', False) 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 check = True 1079 tag = getattr(self, 'tag').replace('/', '') 1080 if args and tag not in args: 1081 check = False 1082 for (key, value) in kargs.iteritems(): 1083 if key not in ['first_only', 'replace', 'find_text']: 1084 if isinstance(value, (str, int)): 1085 if self[key] != str(value): 1086 check = False 1087 elif key in self.attributes: 1088 if not value.search(str(self[key])): 1089 check = False 1090 else: 1091 check = False 1092 if 'find' in kargs: 1093 find = kargs['find'] 1094 is_regex = not isinstance(find, (str, int)) 1095 for c in self.components: 1096 if (isinstance(c, str) and ((is_regex and find.search(c)) or 1097 (str(find) in c))): 1098 check = True 1099 # if found, return the component 1100 if check: 1101 matches.append(self) 1102 1103 first_only = kargs.get('first_only', False) 1104 replace = kargs.get('replace', False) 1105 find_text = replace is not False and kargs.get('find_text', False) 1106 is_regex = not isinstance(find_text, (str, int, bool)) 1107 find_components = not (check and first_only) 1108 1109 def replace_component(i): 1110 if replace is None: 1111 del self[i] 1112 elif callable(replace): 1113 self[i] = replace(self[i]) 1114 else: 1115 self[i] = replace
1116 # loop the components 1117 if find_text or find_components: 1118 for i, c in enumerate(self.components): 1119 if check and find_text and isinstance(c, str) and \ 1120 ((is_regex and find_text.search(c)) or (str(find_text) in c)): 1121 replace_component(i) 1122 if find_components and isinstance(c, XmlComponent): 1123 child_matches = c.elements(*args, **kargs) 1124 if len(child_matches): 1125 if not find_text and replace is not False and child_matches[0] is c: 1126 replace_component(i) 1127 if first_only: 1128 return child_matches 1129 matches.extend(child_matches) 1130 return matches
1131
1132 - def element(self, *args, **kargs):
1133 """ 1134 find the first component that matches the supplied attribute dictionary, 1135 or None if nothing could be found 1136 1137 Also the components of the components are searched. 1138 """ 1139 kargs['first_only'] = True 1140 elements = self.elements(*args, **kargs) 1141 if not elements: 1142 # we found nothing 1143 return None 1144 return elements[0]
1145
1146 - def siblings(self, *args, **kargs):
1147 """ 1148 find all sibling components that match the supplied argument list 1149 and attribute dictionary, or None if nothing could be found 1150 """ 1151 sibs = [s for s in self.parent.components if not s == self] 1152 matches = [] 1153 first_only = False 1154 if 'first_only' in kargs: 1155 first_only = kargs.pop('first_only') 1156 for c in sibs: 1157 try: 1158 check = True 1159 tag = getattr(c, 'tag').replace("/", "") 1160 if args and tag not in args: 1161 check = False 1162 for (key, value) in kargs.iteritems(): 1163 if c[key] != value: 1164 check = False 1165 if check: 1166 matches.append(c) 1167 if first_only: 1168 break 1169 except: 1170 pass 1171 return matches
1172
1173 - def sibling(self, *args, **kargs):
1174 """ 1175 find the first sibling component that match the supplied argument list 1176 and attribute dictionary, or None if nothing could be found 1177 """ 1178 kargs['first_only'] = True 1179 sibs = self.siblings(*args, **kargs) 1180 if not sibs: 1181 return None 1182 return sibs[0]
1183
1184 1185 -class CAT(DIV):
1186 1187 tag = ''
1188
1189 1190 -def TAG_unpickler(data):
1191 return cPickle.loads(data)
1192
1193 1194 -def TAG_pickler(data):
1195 d = DIV() 1196 d.__dict__ = data.__dict__ 1197 marshal_dump = cPickle.dumps(d) 1198 return (TAG_unpickler, (marshal_dump,))
1199
1200 1201 -class __tag__(DIV):
1202 - def __init__(self,name,*a,**b):
1203 DIV.__init__(self,*a,**b) 1204 self.tag = name
1205 1206 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
1207 1208 -class __TAG__(XmlComponent):
1209 1210 """ 1211 TAG factory example:: 1212 1213 >>> print TAG.first(TAG.second('test'), _key = 3) 1214 <first key=\"3\"><second>test</second></first> 1215 1216 """ 1217
1218 - def __getitem__(self, name):
1219 return self.__getattr__(name)
1220
1221 - def __getattr__(self, name):
1222 if name[-1:] == '_': 1223 name = name[:-1] + '/' 1224 if isinstance(name, unicode): 1225 name = name.encode('utf-8') 1226 return lambda *a,**b: __tag__(name,*a,**b)
1227
1228 - def __call__(self, html):
1229 return web2pyHTMLParser(decoder.decoder(html)).tree
1230 1231 TAG = __TAG__()
1232 1233 1234 -class HTML(DIV):
1235 """ 1236 There are four predefined document type definitions. 1237 They can be specified in the 'doctype' parameter: 1238 1239 -'strict' enables strict doctype 1240 -'transitional' enables transitional doctype (default) 1241 -'frameset' enables frameset doctype 1242 -'html5' enables HTML 5 doctype 1243 -any other string will be treated as user's own doctype 1244 1245 'lang' parameter specifies the language of the document. 1246 Defaults to 'en'. 1247 1248 See also :class:`DIV` 1249 """ 1250 1251 tag = 'html' 1252 1253 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' 1254 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' 1255 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' 1256 html5 = '<!DOCTYPE HTML>\n' 1257
1258 - def xml(self):
1259 lang = self['lang'] 1260 if not lang: 1261 lang = 'en' 1262 self.attributes['_lang'] = lang 1263 doctype = self['doctype'] 1264 if doctype is None: 1265 doctype = self.transitional 1266 elif doctype == 'strict': 1267 doctype = self.strict 1268 elif doctype == 'transitional': 1269 doctype = self.transitional 1270 elif doctype == 'frameset': 1271 doctype = self.frameset 1272 elif doctype == 'html5': 1273 doctype = self.html5 1274 elif doctype == '': 1275 doctype = '' 1276 else: 1277 doctype = '%s\n' % doctype 1278 (fa, co) = self._xml() 1279 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1280
1281 1282 -class XHTML(DIV):
1283 """ 1284 This is XHTML version of the HTML helper. 1285 1286 There are three predefined document type definitions. 1287 They can be specified in the 'doctype' parameter: 1288 1289 -'strict' enables strict doctype 1290 -'transitional' enables transitional doctype (default) 1291 -'frameset' enables frameset doctype 1292 -any other string will be treated as user's own doctype 1293 1294 'lang' parameter specifies the language of the document and the xml document. 1295 Defaults to 'en'. 1296 1297 'xmlns' parameter specifies the xml namespace. 1298 Defaults to 'http://www.w3.org/1999/xhtml'. 1299 1300 See also :class:`DIV` 1301 """ 1302 1303 tag = 'html' 1304 1305 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' 1306 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' 1307 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n' 1308 xmlns = 'http://www.w3.org/1999/xhtml' 1309
1310 - def xml(self):
1311 xmlns = self['xmlns'] 1312 if xmlns: 1313 self.attributes['_xmlns'] = xmlns 1314 else: 1315 self.attributes['_xmlns'] = self.xmlns 1316 lang = self['lang'] 1317 if not lang: 1318 lang = 'en' 1319 self.attributes['_lang'] = lang 1320 self.attributes['_xml:lang'] = lang 1321 doctype = self['doctype'] 1322 if doctype: 1323 if doctype == 'strict': 1324 doctype = self.strict 1325 elif doctype == 'transitional': 1326 doctype = self.transitional 1327 elif doctype == 'frameset': 1328 doctype = self.frameset 1329 else: 1330 doctype = '%s\n' % doctype 1331 else: 1332 doctype = self.transitional 1333 (fa, co) = self._xml() 1334 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1335
1336 1337 -class HEAD(DIV):
1338 1339 tag = 'head'
1340
1341 1342 -class TITLE(DIV):
1343 1344 tag = 'title'
1345
1346 1347 -class META(DIV):
1348 1349 tag = 'meta/'
1350 1355
1356 1357 -class SCRIPT(DIV):
1358 1359 tag = 'script' 1360
1361 - def xml(self):
1362 (fa, co) = self._xml() 1363 # no escaping of subcomponents 1364 co = '\n'.join([str(component) for component in 1365 self.components]) 1366 if co: 1367 # <script [attributes]><!--//--><![CDATA[//><!-- 1368 # script body 1369 # //--><!]]></script> 1370 # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag) 1371 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag) 1372 else: 1373 return DIV.xml(self)
1374
1375 1376 -class STYLE(DIV):
1377 1378 tag = 'style' 1379
1380 - def xml(self):
1381 (fa, co) = self._xml() 1382 # no escaping of subcomponents 1383 co = '\n'.join([str(component) for component in 1384 self.components]) 1385 if co: 1386 # <style [attributes]><!--/*--><![CDATA[/*><!--*/ 1387 # style body 1388 # /*]]>*/--></style> 1389 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag) 1390 else: 1391 return DIV.xml(self)
1392
1393 1394 -class IMG(DIV):
1395 1396 tag = 'img/'
1397
1398 1399 -class SPAN(DIV):
1400 1401 tag = 'span'
1402
1403 1404 -class BODY(DIV):
1405 1406 tag = 'body'
1407
1408 1409 -class H1(DIV):
1410 1411 tag = 'h1'
1412
1413 1414 -class H2(DIV):
1415 1416 tag = 'h2'
1417
1418 1419 -class H3(DIV):
1420 1421 tag = 'h3'
1422
1423 1424 -class H4(DIV):
1425 1426 tag = 'h4'
1427
1428 1429 -class H5(DIV):
1430 1431 tag = 'h5'
1432
1433 1434 -class H6(DIV):
1435 1436 tag = 'h6'
1437
1438 1439 -class P(DIV):
1440 """ 1441 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided. 1442 1443 see also :class:`DIV` 1444 """ 1445 1446 tag = 'p' 1447
1448 - def xml(self):
1449 text = DIV.xml(self) 1450 if self['cr2br']: 1451 text = text.replace('\n', '<br />') 1452 return text
1453
1454 1455 -class STRONG(DIV):
1456 1457 tag = 'strong'
1458
1459 1460 -class B(DIV):
1461 1462 tag = 'b'
1463
1464 1465 -class BR(DIV):
1466 1467 tag = 'br/'
1468
1469 1470 -class HR(DIV):
1471 1472 tag = 'hr/'
1473
1474 1475 -class A(DIV):
1476 1477 tag = 'a' 1478
1479 - def xml(self):
1480 if not self.components and self['_href']: 1481 self.append(self['_href']) 1482 if self['delete']: 1483 d = "jQuery(this).closest('%s').remove();" % self['delete'] 1484 else: 1485 d = '' 1486 if self['component']: 1487 self['_onclick'] = "web2py_component('%s','%s');%sreturn false;" % \ 1488 (self['component'], self['target'] or '', d) 1489 self['_href'] = self['_href'] or '#null' 1490 elif self['callback']: 1491 returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) {e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault();}" 1492 if d and not self['noconfirm']: 1493 self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want to delete this object?')){ajax('%s',[],'%s');%s};%s" % \ 1494 (self['callback'], self['target'] or '', d, returnfalse) 1495 else: 1496 self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \ 1497 (self['callback'], self['target'] or '', d) 1498 self['_href'] = self['_href'] or '#null' 1499 elif self['cid']: 1500 pre = self['pre_call'] + ';' if self['pre_call'] else '' 1501 self['_onclick'] = '%sweb2py_component("%s","%s");%sreturn false;' % \ 1502 (pre,self['_href'], self['cid'], d) 1503 return DIV.xml(self)
1504
1505 1506 -class BUTTON(DIV):
1507 1508 tag = 'button'
1509
1510 1511 -class EM(DIV):
1512 1513 tag = 'em'
1514
1515 1516 -class EMBED(DIV):
1517 1518 tag = 'embed/'
1519
1520 1521 -class TT(DIV):
1522 1523 tag = 'tt'
1524
1525 1526 -class PRE(DIV):
1527 1528 tag = 'pre'
1529
1530 1531 -class CENTER(DIV):
1532 1533 tag = 'center'
1534
1535 1536 -class CODE(DIV):
1537 1538 """ 1539 displays code in HTML with syntax highlighting. 1540 1541 :param attributes: optional attributes: 1542 1543 - language: indicates the language, otherwise PYTHON is assumed 1544 - link: can provide a link 1545 - styles: for styles 1546 1547 Example:: 1548 1549 {{=CODE(\"print 'hello world'\", language='python', link=None, 1550 counter=1, styles={}, highlight_line=None)}} 1551 1552 1553 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\", 1554 \"web2py\", \"html\". 1555 The \"html\" language interprets {{ and }} tags as \"web2py\" code, 1556 \"html_plain\" doesn't. 1557 1558 if a link='/examples/global/vars/' is provided web2py keywords are linked to 1559 the online docs. 1560 1561 the counter is used for line numbering, counter can be None or a prompt 1562 string. 1563 """ 1564
1565 - def xml(self):
1566 language = self['language'] or 'PYTHON' 1567 link = self['link'] 1568 counter = self.attributes.get('counter', 1) 1569 highlight_line = self.attributes.get('highlight_line', None) 1570 context_lines = self.attributes.get('context_lines', None) 1571 styles = self['styles'] or {} 1572 return highlight( 1573 join(self.components), 1574 language=language, 1575 link=link, 1576 counter=counter, 1577 styles=styles, 1578 attributes=self.attributes, 1579 highlight_line=highlight_line, 1580 context_lines=context_lines, 1581 )
1582
1583 1584 -class LABEL(DIV):
1585 1586 tag = 'label'
1587
1588 1589 -class LI(DIV):
1590 1591 tag = 'li'
1592
1593 1594 -class UL(DIV):
1595 """ 1596 UL Component. 1597 1598 If subcomponents are not LI-components they will be wrapped in a LI 1599 1600 see also :class:`DIV` 1601 """ 1602 1603 tag = 'ul' 1604
1605 - def _fixup(self):
1606 self._wrap_components(LI, LI)
1607
1608 1609 -class OL(UL):
1610 1611 tag = 'ol'
1612
1613 1614 -class TD(DIV):
1615 1616 tag = 'td'
1617
1618 1619 -class TH(DIV):
1620 1621 tag = 'th'
1622
1623 1624 -class TR(DIV):
1625 """ 1626 TR Component. 1627 1628 If subcomponents are not TD/TH-components they will be wrapped in a TD 1629 1630 see also :class:`DIV` 1631 """ 1632 1633 tag = 'tr' 1634
1635 - def _fixup(self):
1636 self._wrap_components((TD, TH), TD)
1637
1638 1639 -class THEAD(DIV):
1640 1641 tag = 'thead' 1642
1643 - def _fixup(self):
1644 self._wrap_components(TR, TR)
1645
1646 1647 -class TBODY(DIV):
1648 1649 tag = 'tbody' 1650
1651 - def _fixup(self):
1652 self._wrap_components(TR, TR)
1653
1654 1655 -class TFOOT(DIV):
1656 1657 tag = 'tfoot' 1658
1659 - def _fixup(self):
1660 self._wrap_components(TR, TR)
1661
1662 1663 -class COL(DIV):
1664 1665 tag = 'col'
1666
1667 1668 -class COLGROUP(DIV):
1669 1670 tag = 'colgroup'
1671
1672 1673 -class TABLE(DIV):
1674 """ 1675 TABLE Component. 1676 1677 If subcomponents are not TR/TBODY/THEAD/TFOOT-components 1678 they will be wrapped in a TR 1679 1680 see also :class:`DIV` 1681 """ 1682 1683 tag = 'table' 1684
1685 - def _fixup(self):
1687
1688 1689 -class I(DIV):
1690 1691 tag = 'i'
1692
1693 1694 -class IFRAME(DIV):
1695 1696 tag = 'iframe'
1697
1698 1699 -class INPUT(DIV):
1700 1701 """ 1702 INPUT Component 1703 1704 examples:: 1705 1706 >>> INPUT(_type='text', _name='name', value='Max').xml() 1707 '<input name=\"name\" type=\"text\" value=\"Max\" />' 1708 1709 >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() 1710 '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />' 1711 1712 >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() 1713 '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />' 1714 1715 >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() 1716 '<input name=\"radio\" type=\"radio\" value=\"no\" />' 1717 1718 the input helper takes two special attributes value= and requires=. 1719 1720 :param value: used to pass the initial value for the input field. 1721 value differs from _value because it works for checkboxes, radio, 1722 textarea and select/option too. 1723 1724 - for a checkbox value should be '' or 'on'. 1725 - for a radio or select/option value should be the _value 1726 of the checked/selected item. 1727 1728 :param requires: should be None, or a validator or a list of validators 1729 for the value of the field. 1730 """ 1731 1732 tag = 'input/' 1733
1734 - def _validate(self):
1735 1736 # # this only changes value, not _value 1737 1738 name = self['_name'] 1739 if name is None or name == '': 1740 return True 1741 name = str(name) 1742 request_vars_get = self.request_vars.get 1743 if self['_type'] != 'checkbox': 1744 self['old_value'] = self['value'] or self['_value'] or '' 1745 value = request_vars_get(name, '') 1746 self['value'] = value if not hasattr(value,'file') else None 1747 else: 1748 self['old_value'] = self['value'] or False 1749 value = request_vars_get(name) 1750 if isinstance(value, (tuple, list)): 1751 self['value'] = self['_value'] in value 1752 else: 1753 self['value'] = self['_value'] == value 1754 requires = self['requires'] 1755 if requires: 1756 if not isinstance(requires, (list, tuple)): 1757 requires = [requires] 1758 for validator in requires: 1759 (value, errors) = validator(value) 1760 if not errors is None: 1761 self.vars[name] = value 1762 self.errors[name] = errors 1763 break 1764 if not name in self.errors: 1765 self.vars[name] = value 1766 return True 1767 return False
1768
1769 - def _postprocessing(self):
1770 t = self['_type'] 1771 if not t: 1772 t = self['_type'] = 'text' 1773 t = t.lower() 1774 value = self['value'] 1775 if self['_value'] is None or isinstance(self['_value'],cgi.FieldStorage): 1776 _value = None 1777 else: 1778 _value = str(self['_value']) 1779 if '_checked' in self.attributes and not 'value' in self.attributes: 1780 pass 1781 elif t == 'checkbox': 1782 if not _value: 1783 _value = self['_value'] = 'on' 1784 if not value: 1785 value = [] 1786 elif value is True: 1787 value = [_value] 1788 elif not isinstance(value, (list, tuple)): 1789 value = str(value).split('|') 1790 self['_checked'] = _value in value and 'checked' or None 1791 elif t == 'radio': 1792 if str(value) == str(_value): 1793 self['_checked'] = 'checked' 1794 else: 1795 self['_checked'] = None 1796 elif not t == 'submit': 1797 if value is None: 1798 self['value'] = _value 1799 elif not isinstance(value, list): 1800 self['_value'] = value
1801
1802 - def xml(self):
1803 name = self.attributes.get('_name', None) 1804 if name and hasattr(self, 'errors') \ 1805 and self.errors.get(name, None) \ 1806 and self['hideerror'] != True: 1807 self['_class'] = (self['_class'] and self['_class'] 1808 + ' ' or '') + 'invalidinput' 1809 return DIV.xml(self) + DIV( 1810 DIV( 1811 self.errors[name], _class='error', 1812 errors=None, _id='%s__error' % name), 1813 _class='error_wrapper').xml() 1814 else: 1815 if self['_class'] and self['_class'].endswith('invalidinput'): 1816 self['_class'] = self['_class'][:-12] 1817 if self['_class'] == '': 1818 self['_class'] = None 1819 return DIV.xml(self)
1820
1821 1822 -class TEXTAREA(INPUT):
1823 1824 """ 1825 example:: 1826 1827 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY()) 1828 1829 'blah blah blah ...' will be the content of the textarea field. 1830 """ 1831 1832 tag = 'textarea' 1833
1834 - def _postprocessing(self):
1835 if not '_rows' in self.attributes: 1836 self['_rows'] = 10 1837 if not '_cols' in self.attributes: 1838 self['_cols'] = 40 1839 if not self['value'] is None: 1840 self.components = [self['value']] 1841 elif self.components: 1842 self['value'] = self.components[0]
1843
1844 1845 -class OPTION(DIV):
1846 1847 tag = 'option' 1848
1849 - def _fixup(self):
1850 if not '_value' in self.attributes: 1851 self.attributes['_value'] = str(self.components[0])
1852
1853 1854 -class OBJECT(DIV):
1855 1856 tag = 'object'
1857
1858 1859 -class OPTGROUP(DIV):
1860 1861 tag = 'optgroup' 1862
1863 - def _fixup(self):
1864 components = [] 1865 for c in self.components: 1866 if isinstance(c, OPTION): 1867 components.append(c) 1868 else: 1869 components.append(OPTION(c, _value=str(c))) 1870 self.components = components
1871
1872 1873 -class SELECT(INPUT):
1874 1875 """ 1876 example:: 1877 1878 >>> from validators import IS_IN_SET 1879 >>> SELECT('yes', 'no', _name='selector', value='yes', 1880 ... requires=IS_IN_SET(['yes', 'no'])).xml() 1881 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>' 1882 1883 """ 1884 1885 tag = 'select' 1886
1887 - def _fixup(self):
1888 components = [] 1889 for c in self.components: 1890 if isinstance(c, (OPTION, OPTGROUP)): 1891 components.append(c) 1892 else: 1893 components.append(OPTION(c, _value=str(c))) 1894 self.components = components
1895
1896 - def _postprocessing(self):
1897 component_list = [] 1898 for c in self.components: 1899 if isinstance(c, OPTGROUP): 1900 component_list.append(c.components) 1901 else: 1902 component_list.append([c]) 1903 options = itertools.chain(*component_list) 1904 1905 value = self['value'] 1906 if not value is None: 1907 if not self['_multiple']: 1908 for c in options: # my patch 1909 if ((value is not None) and 1910 (str(c['_value']) == str(value))): 1911 c['_selected'] = 'selected' 1912 else: 1913 c['_selected'] = None 1914 else: 1915 if isinstance(value, (list, tuple)): 1916 values = [str(item) for item in value] 1917 else: 1918 values = [str(value)] 1919 for c in options: # my patch 1920 if ((value is not None) and 1921 (str(c['_value']) in values)): 1922 c['_selected'] = 'selected' 1923 else: 1924 c['_selected'] = None
1925
1926 1927 -class FIELDSET(DIV):
1928 1929 tag = 'fieldset'
1930
1931 1932 -class LEGEND(DIV):
1933 1934 tag = 'legend'
1935
1936 1937 -class FORM(DIV):
1938 1939 """ 1940 example:: 1941 1942 >>> from validators import IS_NOT_EMPTY 1943 >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) 1944 >>> form.xml() 1945 '<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>' 1946 1947 a FORM is container for INPUT, TEXTAREA, SELECT and other helpers 1948 1949 form has one important method:: 1950 1951 form.accepts(request.vars, session) 1952 1953 if form is accepted (and all validators pass) form.vars contains the 1954 accepted vars, otherwise form.errors contains the errors. 1955 in case of errors the form is modified to present the errors to the user. 1956 """ 1957 1958 tag = 'form' 1959
1960 - def __init__(self, *components, **attributes):
1961 DIV.__init__(self, *components, **attributes) 1962 self.vars = Storage() 1963 self.errors = Storage() 1964 self.latest = Storage() 1965 self.accepted = None # none for not submitted
1966
1967 - def assert_status(self, status, request_vars):
1968 return status
1969
1970 - def accepts( 1971 self, 1972 request_vars, 1973 session=None, 1974 formname='default', 1975 keepvalues=False, 1976 onvalidation=None, 1977 hideerror=False, 1978 **kwargs 1979 ):
1980 """ 1981 kwargs is not used but allows to specify the same interface for FORM and SQLFORM 1982 """ 1983 if request_vars.__class__.__name__ == 'Request': 1984 request_vars = request_vars.post_vars 1985 self.errors.clear() 1986 self.request_vars = Storage() 1987 self.request_vars.update(request_vars) 1988 self.session = session 1989 self.formname = formname 1990 self.keepvalues = keepvalues 1991 1992 # if this tag is a form and we are in accepting mode (status=True) 1993 # check formname and formkey 1994 1995 status = True 1996 changed = False 1997 request_vars = self.request_vars 1998 if session is not None: 1999 formkey = session.get('_formkey[%s]' % formname, None) 2000 # check if user tampering with form and void CSRF 2001 if not formkey or formkey != request_vars._formkey: 2002 status = False 2003 if formname != request_vars._formname: 2004 status = False 2005 if status and session: 2006 # check if editing a record that has been modified by the server 2007 if hasattr(self, 'record_hash') and self.record_hash != formkey: 2008 status = False 2009 self.record_changed = changed = True 2010 status = self._traverse(status, hideerror) 2011 status = self.assert_status(status, request_vars) 2012 if onvalidation: 2013 if isinstance(onvalidation, dict): 2014 onsuccess = onvalidation.get('onsuccess', None) 2015 onfailure = onvalidation.get('onfailure', None) 2016 onchange = onvalidation.get('onchange', None) 2017 if [k for k in onvalidation if not k in ( 2018 'onsuccess','onfailure','onchange')]: 2019 raise RuntimeError('Invalid key in onvalidate dict') 2020 if onsuccess and status: 2021 call_as_list(onsuccess,self) 2022 if onfailure and request_vars and not status: 2023 call_as_list(onfailure,self) 2024 status = len(self.errors) == 0 2025 if changed: 2026 if onchange and self.record_changed and \ 2027 self.detect_record_change: 2028 call_as_list(onchange,self) 2029 elif status: 2030 call_as_list(onvalidation, self) 2031 if self.errors: 2032 status = False 2033 if not session is None: 2034 if hasattr(self, 'record_hash'): 2035 formkey = self.record_hash 2036 else: 2037 formkey = web2py_uuid() 2038 self.formkey = session['_formkey[%s]' % formname] = formkey 2039 if status and not keepvalues: 2040 self._traverse(False, hideerror) 2041 self.accepted = status 2042 return status
2043
2044 - def _postprocessing(self):
2045 if not '_action' in self.attributes: 2046 self['_action'] = '#' 2047 if not '_method' in self.attributes: 2048 self['_method'] = 'post' 2049 if not '_enctype' in self.attributes: 2050 self['_enctype'] = 'multipart/form-data'
2051
2052 - def hidden_fields(self):
2053 c = [] 2054 attr = self.attributes.get('hidden', {}) 2055 if 'hidden' in self.attributes: 2056 c = [INPUT(_type='hidden', _name=key, _value=value) 2057 for (key, value) in attr.iteritems()] 2058 if hasattr(self, 'formkey') and self.formkey: 2059 c.append(INPUT(_type='hidden', _name='_formkey', 2060 _value=self.formkey)) 2061 if hasattr(self, 'formname') and self.formname: 2062 c.append(INPUT(_type='hidden', _name='_formname', 2063 _value=self.formname)) 2064 return DIV(c, _style="display:none;")
2065
2066 - def xml(self):
2067 newform = FORM(*self.components, **self.attributes) 2068 hidden_fields = self.hidden_fields() 2069 if hidden_fields.components: 2070 newform.append(hidden_fields) 2071 return DIV.xml(newform)
2072
2073 - def validate(self, **kwargs):
2074 """ 2075 This function validates the form, 2076 you can use it instead of directly form.accepts. 2077 2078 Usage: 2079 In controller 2080 2081 def action(): 2082 form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) 2083 form.validate() #you can pass some args here - see below 2084 return dict(form=form) 2085 2086 This can receive a bunch of arguments 2087 2088 onsuccess = 'flash' - will show message_onsuccess in response.flash 2089 None - will do nothing 2090 can be a function (lambda form: pass) 2091 onfailure = 'flash' - will show message_onfailure in response.flash 2092 None - will do nothing 2093 can be a function (lambda form: pass) 2094 onchange = 'flash' - will show message_onchange in response.flash 2095 None - will do nothing 2096 can be a function (lambda form: pass) 2097 2098 message_onsuccess 2099 message_onfailure 2100 message_onchange 2101 next = where to redirect in case of success 2102 any other kwargs will be passed for form.accepts(...) 2103 """ 2104 from gluon import current, redirect 2105 kwargs['request_vars'] = kwargs.get( 2106 'request_vars', current.request.post_vars) 2107 kwargs['session'] = kwargs.get('session', current.session) 2108 kwargs['dbio'] = kwargs.get('dbio', False) 2109 # necessary for SQLHTML forms 2110 2111 onsuccess = kwargs.get('onsuccess', 'flash') 2112 onfailure = kwargs.get('onfailure', 'flash') 2113 onchange = kwargs.get('onchange', 'flash') 2114 message_onsuccess = kwargs.get('message_onsuccess', 2115 current.T("Success!")) 2116 message_onfailure = kwargs.get('message_onfailure', 2117 current.T("Errors in form, please check it out.")) 2118 message_onchange = kwargs.get('message_onchange', 2119 current.T("Form consecutive submissions not allowed. " + 2120 "Try re-submitting or refreshing the form page.")) 2121 next = kwargs.get('next', None) 2122 for key in ('message_onsuccess', 'message_onfailure', 'onsuccess', 2123 'onfailure', 'next', 'message_onchange', 'onchange'): 2124 if key in kwargs: 2125 del kwargs[key] 2126 2127 if self.accepts(**kwargs): 2128 if onsuccess == 'flash': 2129 if next: 2130 current.session.flash = message_onsuccess 2131 else: 2132 current.response.flash = message_onsuccess 2133 elif callable(onsuccess): 2134 onsuccess(self) 2135 if next: 2136 if self.vars: 2137 for key, value in self.vars.iteritems(): 2138 next = next.replace('[%s]' % key, 2139 urllib.quote(str(value))) 2140 if not next.startswith('/'): 2141 next = URL(next) 2142 redirect(next) 2143 return True 2144 elif self.errors: 2145 if onfailure == 'flash': 2146 current.response.flash = message_onfailure 2147 elif callable(onfailure): 2148 onfailure(self) 2149 return False 2150 elif hasattr(self, "record_changed"): 2151 if self.record_changed and self.detect_record_change: 2152 if onchange == 'flash': 2153 current.response.flash = message_onchange 2154 elif callable(onchange): 2155 onchange(self) 2156 return False
2157
2158 - def process(self, **kwargs):
2159 """ 2160 Perform the .validate() method but returns the form 2161 2162 Usage in controllers: 2163 # directly on return 2164 def action(): 2165 #some code here 2166 return dict(form=FORM(...).process(...)) 2167 2168 You can use it with FORM, SQLFORM or FORM based plugins 2169 2170 Examples: 2171 #response.flash messages 2172 def action(): 2173 form = SQLFORM(db.table).process(message_onsuccess='Sucess!') 2174 retutn dict(form=form) 2175 2176 # callback function 2177 # callback receives True or False as first arg, and a list of args. 2178 def my_callback(status, msg): 2179 response.flash = "Success! "+msg if status else "Errors occured" 2180 2181 # after argument can be 'flash' to response.flash messages 2182 # or a function name to use as callback or None to do nothing. 2183 def action(): 2184 return dict(form=SQLFORM(db.table).process(onsuccess=my_callback) 2185 """ 2186 kwargs['dbio'] = kwargs.get('dbio', True) 2187 # necessary for SQLHTML forms 2188 self.validate(**kwargs) 2189 return self
2190 2191 REDIRECT_JS = "window.location='%s';return false" 2192
2193 - def add_button(self, value, url, _class=None):
2194 submit = self.element('input[type=submit]') 2195 submit.parent.append( 2196 INPUT(_type="button", _value=value, _class=_class, 2197 _onclick=self.REDIRECT_JS % url))
2198 2199 @staticmethod
2200 - def confirm(text='OK', buttons=None, hidden=None):
2201 if not buttons: 2202 buttons = {} 2203 if not hidden: 2204 hidden = {} 2205 inputs = [INPUT(_type='button', 2206 _value=name, 2207 _onclick=FORM.REDIRECT_JS % link) 2208 for name, link in buttons.iteritems()] 2209 inputs += [INPUT(_type='hidden', 2210 _name=name, 2211 _value=value) 2212 for name, value in hidden.iteritems()] 2213 form = FORM(INPUT(_type='submit', _value=text), *inputs) 2214 form.process() 2215 return form
2216
2217 - def as_dict(self, flat=False, sanitize=True):
2218 """EXPERIMENTAL 2219 2220 Sanitize is naive. It should catch any unsafe value 2221 for client retrieval. 2222 """ 2223 SERIALIZABLE = (int, float, bool, basestring, long, 2224 set, list, dict, tuple, Storage, type(None)) 2225 UNSAFE = ("PASSWORD", "CRYPT") 2226 d = self.__dict__ 2227 2228 def sanitizer(obj): 2229 if isinstance(obj, dict): 2230 for k in obj.keys(): 2231 if any([unsafe in str(k).upper() for 2232 unsafe in UNSAFE]): 2233 # erease unsafe pair 2234 obj.pop(k) 2235 else: 2236 # not implemented 2237 pass 2238 return obj
2239 2240 def flatten(obj): 2241 if isinstance(obj, (dict, Storage)): 2242 newobj = obj.copy() 2243 else: 2244 newobj = obj 2245 if sanitize: 2246 newobj = sanitizer(newobj) 2247 if flat: 2248 if type(obj) in SERIALIZABLE: 2249 if isinstance(newobj, (dict, Storage)): 2250 for k in newobj: 2251 newk = flatten(k) 2252 newobj[newk] = flatten(newobj[k]) 2253 if k != newk: 2254 newobj.pop(k) 2255 return newobj 2256 elif isinstance(newobj, (list, tuple, set)): 2257 return [flatten(item) for item in newobj] 2258 else: 2259 return newobj 2260 else: return str(newobj) 2261 else: return newobj
2262 return flatten(d) 2263
2264 - def as_json(self, sanitize=True):
2265 d = self.as_dict(flat=True, sanitize=sanitize) 2266 from serializers import json 2267 return json(d)
2268
2269 - def as_yaml(self, sanitize=True):
2270 d = self.as_dict(flat=True, sanitize=sanitize) 2271 from serializers import yaml 2272 return yaml(d)
2273
2274 - def as_xml(self, sanitize=True):
2275 d = self.as_dict(flat=True, sanitize=sanitize) 2276 from serializers import xml 2277 return xml(d)
2278
2279 2280 -class BEAUTIFY(DIV):
2281 2282 """ 2283 example:: 2284 2285 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() 2286 '<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 valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' 2287 2288 turns any list, dictionary, etc into decent looking html. 2289 Two special attributes are 2290 :sorted: a function that takes the dict and returned sorted keys 2291 :keyfilter: a funciton that takes a key and returns its representation 2292 or None if the key is to be skipped. By default key[:1]=='_' is skipped. 2293 """ 2294 2295 tag = 'div' 2296 2297 @staticmethod
2298 - def no_underscore(key):
2299 if key[:1] == '_': 2300 return None 2301 return key
2302
2303 - def __init__(self, component, **attributes):
2304 self.components = [component] 2305 self.attributes = attributes 2306 sorter = attributes.get('sorted', sorted) 2307 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore) 2308 components = [] 2309 attributes = copy.copy(self.attributes) 2310 level = attributes['level'] = attributes.get('level', 6) - 1 2311 if '_class' in attributes: 2312 attributes['_class'] += 'i' 2313 if level == 0: 2314 return 2315 for c in self.components: 2316 if hasattr(c, 'value') and not callable(c.value): 2317 if c.value: 2318 components.append(c.value) 2319 if hasattr(c, 'xml') and callable(c.xml): 2320 components.append(c) 2321 continue 2322 elif hasattr(c, 'keys') and callable(c.keys): 2323 rows = [] 2324 try: 2325 keys = (sorter and sorter(c)) or c 2326 for key in keys: 2327 if isinstance(key, (str, unicode)) and keyfilter: 2328 filtered_key = keyfilter(key) 2329 else: 2330 filtered_key = str(key) 2331 if filtered_key is None: 2332 continue 2333 value = c[key] 2334 if isinstance(value, types.LambdaType): 2335 continue 2336 rows.append( 2337 TR( 2338 TD(filtered_key, _style='font-weight:bold;vertical-align:top'), 2339 TD(':', _valign='top'), 2340 TD(BEAUTIFY(value, **attributes)))) 2341 components.append(TABLE(*rows, **attributes)) 2342 continue 2343 except: 2344 pass 2345 if isinstance(c, str): 2346 components.append(str(c)) 2347 elif isinstance(c, unicode): 2348 components.append(c.encode('utf8')) 2349 elif isinstance(c, (list, tuple)): 2350 items = [TR(TD(BEAUTIFY(item, **attributes))) 2351 for item in c] 2352 components.append(TABLE(*items, **attributes)) 2353 elif isinstance(c, cgi.FieldStorage): 2354 components.append('FieldStorage object') 2355 else: 2356 components.append(repr(c)) 2357 self.components = components
2358 2454
2455 2456 -def embed64( 2457 filename=None, 2458 file=None, 2459 data=None, 2460 extension='image/gif', 2461 ):
2462 """ 2463 helper to encode the provided (binary) data into base64. 2464 2465 :param filename: if provided, opens and reads this file in 'rb' mode 2466 :param file: if provided, reads this file 2467 :param data: if provided, uses the provided data 2468 """ 2469 2470 if filename and os.path.exists(file): 2471 fp = open(filename, 'rb') 2472 data = fp.read() 2473 fp.close() 2474 data = base64.b64encode(data) 2475 return 'data:%s;base64,%s' % (extension, data)
2476
2477 2478 -def test():
2479 """ 2480 Example: 2481 2482 >>> from validators import * 2483 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() 2484 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div> 2485 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() 2486 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div> 2487 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() 2488 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div> 2489 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml() 2490 <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> 2491 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10'))) 2492 >>> print form.xml() 2493 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form> 2494 >>> print form.accepts({'myvar':'34'}, formname=None) 2495 False 2496 >>> print form.xml() 2497 <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> 2498 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) 2499 True 2500 >>> print form.xml() 2501 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form> 2502 >>> form=FORM(SELECT('cat', 'dog', _name='myvar')) 2503 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True) 2504 True 2505 >>> print form.xml() 2506 <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> 2507 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!'))) 2508 >>> print form.accepts({'myvar':'as df'}, formname=None) 2509 False 2510 >>> print form.xml() 2511 <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> 2512 >>> session={} 2513 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) 2514 >>> isinstance(form.as_dict(), dict) 2515 True 2516 >>> form.as_dict(flat=True).has_key("vars") 2517 True 2518 >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0 2519 True 2520 >>> if form.accepts({}, session,formname=None): print 'passed' 2521 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed' 2522 """ 2523 pass
2524
2525 2526 -class web2pyHTMLParser(HTMLParser):
2527 """ 2528 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers. 2529 obj.tree contains the root of the tree, and tree can be manipulated 2530 2531 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree) 2532 'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz' 2533 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree) 2534 '<div>a<span>b</span></div>c' 2535 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree 2536 >>> tree.element(_a='b')['_c']=5 2537 >>> str(tree) 2538 'hello<div a="b" c="5">world</div>' 2539 """
2540 - def __init__(self, text, closed=('input', 'link')):
2541 HTMLParser.__init__(self) 2542 self.tree = self.parent = TAG['']() 2543 self.closed = closed 2544 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)] 2545 self.last = None 2546 self.feed(text)
2547
2548 - def handle_starttag(self, tagname, attrs):
2549 if tagname.upper() in self.tags: 2550 tag = eval(tagname.upper()) 2551 else: 2552 if tagname in self.closed: 2553 tagname += '/' 2554 tag = TAG[tagname]() 2555 for key, value in attrs: 2556 tag['_' + key] = value 2557 tag.parent = self.parent 2558 self.parent.append(tag) 2559 if not tag.tag.endswith('/'): 2560 self.parent = tag 2561 else: 2562 self.last = tag.tag[:-1]
2563
2564 - def handle_data(self, data):
2565 if not isinstance(data, unicode): 2566 try: 2567 data = data.decode('utf8') 2568 except: 2569 data = data.decode('latin1') 2570 self.parent.append(data.encode('utf8', 'xmlcharref'))
2571
2572 - def handle_charref(self, name):
2573 if name.startswith('x'): 2574 self.parent.append(unichr(int(name[1:], 16)).encode('utf8')) 2575 else: 2576 self.parent.append(unichr(int(name)).encode('utf8'))
2577
2578 - def handle_entityref(self, name):
2579 self.parent.append(entitydefs[name])
2580
2581 - def handle_endtag(self, tagname):
2582 # this deals with unbalanced tags 2583 if tagname == self.last: 2584 return 2585 while True: 2586 try: 2587 parent_tagname = self.parent.tag 2588 self.parent = self.parent.parent 2589 except: 2590 raise RuntimeError("unable to balance tag %s" % tagname) 2591 if parent_tagname[:len(tagname)] == tagname: break
2592
2593 2594 -def markdown_serializer(text, tag=None, attr=None):
2595 attr = attr or {} 2596 if tag is None: 2597 return re.sub('\s+', ' ', text) 2598 if tag == 'br': 2599 return '\n\n' 2600 if tag == 'h1': 2601 return '#' + text + '\n\n' 2602 if tag == 'h2': 2603 return '#' * 2 + text + '\n\n' 2604 if tag == 'h3': 2605 return '#' * 3 + text + '\n\n' 2606 if tag == 'h4': 2607 return '#' * 4 + text + '\n\n' 2608 if tag == 'p': 2609 return text + '\n\n' 2610 if tag == 'b' or tag == 'strong': 2611 return '**%s**' % text 2612 if tag == 'em' or tag == 'i': 2613 return '*%s*' % text 2614 if tag == 'tt' or tag == 'code': 2615 return '`%s`' % text 2616 if tag == 'a': 2617 return '[%s](%s)' % (text, attr.get('_href', '')) 2618 if tag == 'img': 2619 return '![%s](%s)' % (attr.get('_alt', ''), attr.get('_src', '')) 2620 return text
2621
2622 2623 -def markmin_serializer(text, tag=None, attr=None):
2624 attr = attr or {} 2625 # if tag is None: return re.sub('\s+',' ',text) 2626 if tag == 'br': 2627 return '\n\n' 2628 if tag == 'h1': 2629 return '# ' + text + '\n\n' 2630 if tag == 'h2': 2631 return '#' * 2 + ' ' + text + '\n\n' 2632 if tag == 'h3': 2633 return '#' * 3 + ' ' + text + '\n\n' 2634 if tag == 'h4': 2635 return '#' * 4 + ' ' + text + '\n\n' 2636 if tag == 'p': 2637 return text + '\n\n' 2638 if tag == 'li': 2639 return '\n- ' + text.replace('\n', ' ') 2640 if tag == 'tr': 2641 return text[3:].replace('\n', ' ') + '\n' 2642 if tag in ['table', 'blockquote']: 2643 return '\n-----\n' + text + '\n------\n' 2644 if tag in ['td', 'th']: 2645 return ' | ' + text 2646 if tag in ['b', 'strong', 'label']: 2647 return '**%s**' % text 2648 if tag in ['em', 'i']: 2649 return "''%s''" % text 2650 if tag in ['tt']: 2651 return '``%s``' % text.strip() 2652 if tag in ['code']: 2653 return '``\n%s``' % text 2654 if tag == 'a': 2655 return '[[%s %s]]' % (text, attr.get('_href', '')) 2656 if tag == 'img': 2657 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', '')) 2658 return text
2659
2660 2661 -class MARKMIN(XmlComponent):
2662 """ 2663 For documentation: http://web2py.com/examples/static/markmin.html 2664 """
2665 - def __init__(self, text, extra=None, allowed=None, sep='p', 2666 url=None, environment=None, latex='google', 2667 autolinks='default', 2668 protolinks='default', 2669 class_prefix='', 2670 id_prefix='markmin_'):
2671 self.text = text 2672 self.extra = extra or {} 2673 self.allowed = allowed or {} 2674 self.sep = sep 2675 self.url = URL if url == True else url 2676 self.environment = environment 2677 self.latex = latex 2678 self.autolinks = autolinks 2679 self.protolinks = protolinks 2680 self.class_prefix = class_prefix 2681 self.id_prefix = id_prefix
2682
2683 - def xml(self):
2684 """ 2685 calls the gluon.contrib.markmin render function to convert the wiki syntax 2686 """ 2687 from contrib.markmin.markmin2html import render 2688 return render(self.text, extra=self.extra, 2689 allowed=self.allowed, sep=self.sep, latex=self.latex, 2690 URL=self.url, environment=self.environment, 2691 autolinks=self.autolinks, protolinks=self.protolinks, 2692 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2693
2694 - def __str__(self):
2695 return self.xml()
2696
2697 - def flatten(self, render=None):
2698 """ 2699 return the text stored by the MARKMIN object rendered by the render function 2700 """ 2701 return self.text
2702
2703 - def elements(self, *args, **kargs):
2704 """ 2705 to be considered experimental since the behavior of this method is questionable 2706 another options could be TAG(self.text).elements(*args,**kargs) 2707 """ 2708 return [self.text]
2709 2710 2711 if __name__ == '__main__': 2712 import doctest 2713 doctest.testmod() 2714