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

Source Code for Module Dropbox.web2py.gluon.html

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3   
   4  """ 
   5  This file is part of the web2py Web Framework 
   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
   8  """ 
   9   
  10  import cgi 
  11  import os 
  12  import re 
  13  import copy 
  14  import types 
  15  import urllib 
  16  import base64 
  17  import sanitizer 
  18  import 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', 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.xml()
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 - def __getattr__(self, name):
604 return getattr(str(self), name)
605
606 - def __getitem__(self, i):
607 return str(self)[i]
608
609 - def __getslice__(self, i, j):
610 return str(self)[i:j]
611
612 - def __iter__(self):
613 for c in str(self): 614 yield c
615
616 - def __len__(self):
617 return len(str(self))
618
619 - def flatten(self, render=None):
620 """ 621 return the text stored by the XML object rendered by the render function 622 """ 623 if render: 624 return render(self.text, None, {}) 625 return self.text
626
627 - def elements(self, *args, **kargs):
628 """ 629 to be considered experimental since the behavior of this method is questionable 630 another options could be TAG(self.text).elements(*args,**kargs) 631 """ 632 return []
633
634 ### important to allow safe session.flash=T(....) 635 636 637 -def XML_unpickle(data):
638 return marshal.loads(data)
639
640 641 -def XML_pickle(data):
642 return XML_unpickle, (marshal.dumps(str(data)),)
643 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
644 645 646 -class DIV(XmlComponent):
647 """ 648 HTML helper, for easy generating and manipulating a DOM structure. 649 Little or no validation is done. 650 651 Behaves like a dictionary regarding updating of attributes. 652 Behaves like a list regarding inserting/appending components. 653 654 example:: 655 656 >>> DIV('hello', 'world', _style='color:red;').xml() 657 '<div style=\"color:red;\">helloworld</div>' 658 659 all other HTML helpers are derived from DIV. 660 661 _something=\"value\" attributes are transparently translated into 662 something=\"value\" HTML attributes 663 """ 664 665 # name of the tag, subclasses should update this 666 # tags ending with a '/' denote classes that cannot 667 # contain components 668 tag = 'div' 669
670 - def __init__(self, *components, **attributes):
671 """ 672 :param *components: any components that should be nested in this element 673 :param **attributes: any attributes you want to give to this element 674 675 :raises SyntaxError: when a stand alone tag receives components 676 """ 677 678 if self.tag[-1:] == '/' and components: 679 raise SyntaxError('<%s> tags cannot have components' 680 % self.tag) 681 if len(components) == 1 and isinstance(components[0], (list, tuple)): 682 self.components = list(components[0]) 683 else: 684 self.components = list(components) 685 self.attributes = attributes 686 self._fixup() 687 # converts special attributes in components attributes 688 self.parent = None 689 for c in self.components: 690 self._setnode(c) 691 self._postprocessing()
692
693 - def update(self, **kargs):
694 """ 695 dictionary like updating of the tag attributes 696 """ 697 698 for (key, value) in kargs.iteritems(): 699 self[key] = value 700 return self
701
702 - def append(self, value):
703 """ 704 list style appending of components 705 706 >>> a=DIV() 707 >>> a.append(SPAN('x')) 708 >>> print a 709 <div><span>x</span></div> 710 """ 711 self._setnode(value) 712 ret = self.components.append(value) 713 self._fixup() 714 return ret
715
716 - def insert(self, i, value):
717 """ 718 list style inserting of components 719 720 >>> a=DIV() 721 >>> a.insert(0,SPAN('x')) 722 >>> print a 723 <div><span>x</span></div> 724 """ 725 self._setnode(value) 726 ret = self.components.insert(i, value) 727 self._fixup() 728 return ret
729
730 - def __getitem__(self, i):
731 """ 732 gets attribute with name 'i' or component #i. 733 If attribute 'i' is not found returns None 734 735 :param i: index 736 if i is a string: the name of the attribute 737 otherwise references to number of the component 738 """ 739 740 if isinstance(i, str): 741 try: 742 return self.attributes[i] 743 except KeyError: 744 return None 745 else: 746 return self.components[i]
747
748 - def __setitem__(self, i, value):
749 """ 750 sets attribute with name 'i' or component #i. 751 752 :param i: index 753 if i is a string: the name of the attribute 754 otherwise references to number of the component 755 :param value: the new value 756 """ 757 self._setnode(value) 758 if isinstance(i, (str, unicode)): 759 self.attributes[i] = value 760 else: 761 self.components[i] = value
762
763 - def __delitem__(self, i):
764 """ 765 deletes attribute with name 'i' or component #i. 766 767 :param i: index 768 if i is a string: the name of the attribute 769 otherwise references to number of the component 770 """ 771 772 if isinstance(i, str): 773 del self.attributes[i] 774 else: 775 del self.components[i]
776
777 - def __len__(self):
778 """ 779 returns the number of included components 780 """ 781 return len(self.components)
782
783 - def __nonzero__(self):
784 """ 785 always return True 786 """ 787 return True
788
789 - def _fixup(self):
790 """ 791 Handling of provided components. 792 793 Nothing to fixup yet. May be overridden by subclasses, 794 eg for wrapping some components in another component or blocking them. 795 """ 796 return
797
798 - def _wrap_components(self, allowed_parents, 799 wrap_parent=None, 800 wrap_lambda=None):
801 """ 802 helper for _fixup. Checks if a component is in allowed_parents, 803 otherwise wraps it in wrap_parent 804 805 :param allowed_parents: (tuple) classes that the component should be an 806 instance of 807 :param wrap_parent: the class to wrap the component in, if needed 808 :param wrap_lambda: lambda to use for wrapping, if needed 809 810 """ 811 components = [] 812 for c in self.components: 813 if isinstance(c, allowed_parents): 814 pass 815 elif wrap_lambda: 816 c = wrap_lambda(c) 817 else: 818 c = wrap_parent(c) 819 if isinstance(c, DIV): 820 c.parent = self 821 components.append(c) 822 self.components = components
823
824 - def _postprocessing(self):
825 """ 826 Handling of attributes (normally the ones not prefixed with '_'). 827 828 Nothing to postprocess yet. May be overridden by subclasses 829 """ 830 return
831
832 - def _traverse(self, status, hideerror=False):
833 # TODO: docstring 834 newstatus = status 835 for c in self.components: 836 if hasattr(c, '_traverse') and callable(c._traverse): 837 c.vars = self.vars 838 c.request_vars = self.request_vars 839 c.errors = self.errors 840 c.latest = self.latest 841 c.session = self.session 842 c.formname = self.formname 843 if hideerror and not \ 844 self.attributes.get('hideerror', False): 845 c['hideerror'] = hideerror 846 newstatus = c._traverse(status, hideerror) and newstatus 847 848 # for input, textarea, select, option 849 # deal with 'value' and 'validation' 850 851 name = self['_name'] 852 if newstatus: 853 newstatus = self._validate() 854 self._postprocessing() 855 elif 'old_value' in self.attributes: 856 self['value'] = self['old_value'] 857 self._postprocessing() 858 elif name and name in self.vars: 859 self['value'] = self.vars[name] 860 self._postprocessing() 861 if name: 862 self.latest[name] = self['value'] 863 return newstatus
864
865 - def _validate(self):
866 """ 867 nothing to validate yet. May be overridden by subclasses 868 """ 869 return True
870
871 - def _setnode(self, value):
872 if isinstance(value, DIV): 873 value.parent = self
874
875 - def _xml(self):
876 """ 877 helper for xml generation. Returns separately: 878 - the component attributes 879 - the generated xml of the inner components 880 881 Component attributes start with an underscore ('_') and 882 do not have a False or None value. The underscore is removed. 883 A value of True is replaced with the attribute name. 884 885 :returns: tuple: (attributes, components) 886 """ 887 888 # get the attributes for this component 889 # (they start with '_', others may have special meanings) 890 fa = '' 891 for key in sorted(self.attributes): 892 value = self[key] 893 if key[:1] != '_': 894 continue 895 name = key[1:] 896 if value is True: 897 value = name 898 elif value is False or value is None: 899 continue 900 fa += ' %s="%s"' % (name, xmlescape(value, True)) 901 902 # get the xml for the inner components 903 co = join([xmlescape(component) for component in 904 self.components]) 905 906 return (fa, co)
907
908 - def xml(self):
909 """ 910 generates the xml for this component. 911 """ 912 913 (fa, co) = self._xml() 914 915 if not self.tag: 916 return co 917 918 if self.tag[-1:] == '/': 919 # <tag [attributes] /> 920 return '<%s%s />' % (self.tag[:-1], fa) 921 922 # else: <tag [attributes]> inner components xml </tag> 923 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
924
925 - def __str__(self):
926 """ 927 str(COMPONENT) returns equals COMPONENT.xml() 928 """ 929 930 return self.xml()
931
932 - def flatten(self, render=None):
933 """ 934 return the text stored by the DIV object rendered by the render function 935 the render function must take text, tagname, and attributes 936 render=None is equivalent to render=lambda text, tag, attr: text 937 938 >>> markdown = lambda text,tag=None,attributes={}: \ 939 {None: re.sub('\s+',' ',text), \ 940 'h1':'#'+text+'\\n\\n', \ 941 'p':text+'\\n'}.get(tag,text) 942 >>> a=TAG('<h1>Header</h1><p>this is a test</p>') 943 >>> a.flatten(markdown) 944 '#Header\\n\\nthis is a test\\n' 945 """ 946 947 text = '' 948 for c in self.components: 949 if isinstance(c, XmlComponent): 950 s = c.flatten(render) 951 elif render: 952 s = render(str(c)) 953 else: 954 s = str(c) 955 text += s 956 if render: 957 text = render(text, self.tag, self.attributes) 958 return text
959 960 regex_tag = re.compile('^[\w\-\:]+') 961 regex_id = re.compile('#([\w\-]+)') 962 regex_class = re.compile('\.([\w\-]+)') 963 regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]') 964
965 - def elements(self, *args, **kargs):
966 """ 967 find all component that match the supplied attribute dictionary, 968 or None if nothing could be found 969 970 All components of the components are searched. 971 972 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y')))) 973 >>> for c in a.elements('span',first_only=True): c[0]='z' 974 >>> print a 975 <div><div><span>z</span>3<div><span>y</span></div></div></div> 976 >>> for c in a.elements('span'): c[0]='z' 977 >>> print a 978 <div><div><span>z</span>3<div><span>z</span></div></div></div> 979 980 It also supports a syntax compatible with jQuery 981 982 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>') 983 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten() 984 hello 985 world 986 >>> for e in a.elements('#1-1'): print e.flatten() 987 hello 988 >>> a.elements('a[u:v=$]')[0].xml() 989 '<a id="1-1" u:v="$">hello</a>' 990 991 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() ) 992 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled' 993 >>> a.xml() 994 '<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>' 995 996 Elements that are matched can also be replaced or removed by specifying 997 a "replace" argument (note, a list of the original matching elements 998 is still returned as usual). 999 1000 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc')))) 1001 >>> b = a.elements('span.abc', replace=P('x', _class='xyz')) 1002 >>> print a 1003 <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div> 1004 1005 "replace" can be a callable, which will be passed the original element and 1006 should return a new element to replace it. 1007 1008 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc')))) 1009 >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz')) 1010 >>> print a 1011 <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div> 1012 1013 If replace=None, matching elements will be removed completely. 1014 1015 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc')))) 1016 >>> b = a.elements('span', find='y', replace=None) 1017 >>> print a 1018 <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div> 1019 1020 If a "find_text" argument is specified, elements will be searched for text 1021 components that match find_text, and any matching text components will be 1022 replaced (find_text is ignored if "replace" is not also specified). 1023 Like the "find" argument, "find_text" can be a string or a compiled regex. 1024 1025 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc')))) 1026 >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello') 1027 >>> print a 1028 <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div> 1029 1030 If other attributes are specified along with find_text, then only components 1031 that match the specified attributes will be searched for find_text. 1032 1033 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc')))) 1034 >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello') 1035 >>> print a 1036 <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div> 1037 """ 1038 if len(args) == 1: 1039 args = [a.strip() for a in args[0].split(',')] 1040 if len(args) > 1: 1041 subset = [self.elements(a, **kargs) for a in args] 1042 return reduce(lambda a, b: a + b, subset, []) 1043 elif len(args) == 1: 1044 items = args[0].split() 1045 if len(items) > 1: 1046 subset = [a.elements(' '.join( 1047 items[1:]), **kargs) for a in self.elements(items[0])] 1048 return reduce(lambda a, b: a + b, subset, []) 1049 else: 1050 item = items[0] 1051 if '#' in item or '.' in item or '[' in item: 1052 match_tag = self.regex_tag.search(item) 1053 match_id = self.regex_id.search(item) 1054 match_class = self.regex_class.search(item) 1055 match_attr = self.regex_attr.finditer(item) 1056 args = [] 1057 if match_tag: 1058 args = [match_tag.group()] 1059 if match_id: 1060 kargs['_id'] = match_id.group(1) 1061 if match_class: 1062 kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % 1063 match_class.group(1).replace('-', '\\-').replace(':', '\\:')) 1064 for item in match_attr: 1065 kargs['_' + item.group(1)] = item.group(2) 1066 return self.elements(*args, **kargs) 1067 # make a copy of the components 1068 matches = [] 1069 # check if the component has an attribute with the same 1070 # value as provided 1071 check = True 1072 tag = getattr(self, 'tag').replace('/', '') 1073 if args and tag not in args: 1074 check = False 1075 for (key, value) in kargs.iteritems(): 1076 if key not in ['first_only', 'replace', 'find_text']: 1077 if isinstance(value, (str, int)): 1078 if self[key] != str(value): 1079 check = False 1080 elif key in self.attributes: 1081 if not value.search(str(self[key])): 1082 check = False 1083 else: 1084 check = False 1085 if 'find' in kargs: 1086 find = kargs['find'] 1087 is_regex = not isinstance(find, (str, int)) 1088 for c in self.components: 1089 if (isinstance(c, str) and ((is_regex and find.search(c)) or 1090 (str(find) in c))): 1091 check = True 1092 # if found, return the component 1093 if check: 1094 matches.append(self) 1095 1096 first_only = kargs.get('first_only', False) 1097 replace = kargs.get('replace', False) 1098 find_text = replace is not False and kargs.get('find_text', False) 1099 is_regex = not isinstance(find_text, (str, int, bool)) 1100 find_components = not (check and first_only) 1101 1102 def replace_component(i): 1103 if replace is None: 1104 del self[i] 1105 elif callable(replace): 1106 self[i] = replace(self[i]) 1107 else: 1108 self[i] = replace
1109 # loop the components 1110 if find_text or find_components: 1111 for i, c in enumerate(self.components): 1112 if check and find_text and isinstance(c, str) and \ 1113 ((is_regex and find_text.search(c)) or (str(find_text) in c)): 1114 replace_component(i) 1115 if find_components and isinstance(c, XmlComponent): 1116 child_matches = c.elements(*args, **kargs) 1117 if len(child_matches): 1118 if not find_text and replace is not False and child_matches[0] is c: 1119 replace_component(i) 1120 if first_only: 1121 return child_matches 1122 matches.extend(child_matches) 1123 return matches
1124
1125 - def element(self, *args, **kargs):
1126 """ 1127 find the first component that matches the supplied attribute dictionary, 1128 or None if nothing could be found 1129 1130 Also the components of the components are searched. 1131 """ 1132 kargs['first_only'] = True 1133 elements = self.elements(*args, **kargs) 1134 if not elements: 1135 # we found nothing 1136 return None 1137 return elements[0]
1138
1139 - def siblings(self, *args, **kargs):
1140 """ 1141 find all sibling components that match the supplied argument list 1142 and attribute dictionary, or None if nothing could be found 1143 """ 1144 sibs = [s for s in self.parent.components if not s == self] 1145 matches = [] 1146 first_only = False 1147 if 'first_only' in kargs: 1148 first_only = kargs.pop('first_only') 1149 for c in sibs: 1150 try: 1151 check = True 1152 tag = getattr(c, 'tag').replace("/", "") 1153 if args and tag not in args: 1154 check = False 1155 for (key, value) in kargs.iteritems(): 1156 if c[key] != value: 1157 check = False 1158 if check: 1159 matches.append(c) 1160 if first_only: 1161 break 1162 except: 1163 pass 1164 return matches
1165
1166 - def sibling(self, *args, **kargs):
1167 """ 1168 find the first sibling component that match the supplied argument list 1169 and attribute dictionary, or None if nothing could be found 1170 """ 1171 kargs['first_only'] = True 1172 sibs = self.siblings(*args, **kargs) 1173 if not sibs: 1174 return None 1175 return sibs[0]
1176
1177 1178 -class CAT(DIV):
1179 1180 tag = ''
1181
1182 1183 -def TAG_unpickler(data):
1184 return cPickle.loads(data)
1185
1186 1187 -def TAG_pickler(data):
1188 d = DIV() 1189 d.__dict__ = data.__dict__ 1190 marshal_dump = cPickle.dumps(d) 1191 return (TAG_unpickler, (marshal_dump,))
1192
1193 1194 -class __TAG__(XmlComponent):
1195 1196 """ 1197 TAG factory example:: 1198 1199 >>> print TAG.first(TAG.second('test'), _key = 3) 1200 <first key=\"3\"><second>test</second></first> 1201 1202 """ 1203
1204 - def __getitem__(self, name):
1205 return self.__getattr__(name)
1206
1207 - def __getattr__(self, name):
1208 if name[-1:] == '_': 1209 name = name[:-1] + '/' 1210 if isinstance(name, unicode): 1211 name = name.encode('utf-8') 1212 1213 class __tag__(DIV): 1214 tag = name
1215 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler) 1216 return lambda *a, **b: __tag__(*a, **b)
1217
1218 - def __call__(self, html):
1219 return web2pyHTMLParser(decoder.decoder(html)).tree
1220 1221 TAG = __TAG__()
1222 1223 1224 -class HTML(DIV):
1225 """ 1226 There are four predefined document type definitions. 1227 They can be specified in the 'doctype' parameter: 1228 1229 -'strict' enables strict doctype 1230 -'transitional' enables transitional doctype (default) 1231 -'frameset' enables frameset doctype 1232 -'html5' enables HTML 5 doctype 1233 -any other string will be treated as user's own doctype 1234 1235 'lang' parameter specifies the language of the document. 1236 Defaults to 'en'. 1237 1238 See also :class:`DIV` 1239 """ 1240 1241 tag = 'html' 1242 1243 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' 1244 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' 1245 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' 1246 html5 = '<!DOCTYPE HTML>\n' 1247
1248 - def xml(self):
1249 lang = self['lang'] 1250 if not lang: 1251 lang = 'en' 1252 self.attributes['_lang'] = lang 1253 doctype = self['doctype'] 1254 if doctype is None: 1255 doctype = self.transitional 1256 elif doctype == 'strict': 1257 doctype = self.strict 1258 elif doctype == 'transitional': 1259 doctype = self.transitional 1260 elif doctype == 'frameset': 1261 doctype = self.frameset 1262 elif doctype == 'html5': 1263 doctype = self.html5 1264 elif doctype == '': 1265 doctype = '' 1266 else: 1267 doctype = '%s\n' % doctype 1268 (fa, co) = self._xml() 1269 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1270
1271 1272 -class XHTML(DIV):
1273 """ 1274 This is XHTML version of the HTML helper. 1275 1276 There are three predefined document type definitions. 1277 They can be specified in the 'doctype' parameter: 1278 1279 -'strict' enables strict doctype 1280 -'transitional' enables transitional doctype (default) 1281 -'frameset' enables frameset doctype 1282 -any other string will be treated as user's own doctype 1283 1284 'lang' parameter specifies the language of the document and the xml document. 1285 Defaults to 'en'. 1286 1287 'xmlns' parameter specifies the xml namespace. 1288 Defaults to 'http://www.w3.org/1999/xhtml'. 1289 1290 See also :class:`DIV` 1291 """ 1292 1293 tag = 'html' 1294 1295 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' 1296 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' 1297 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n' 1298 xmlns = 'http://www.w3.org/1999/xhtml' 1299
1300 - def xml(self):
1301 xmlns = self['xmlns'] 1302 if xmlns: 1303 self.attributes['_xmlns'] = xmlns 1304 else: 1305 self.attributes['_xmlns'] = self.xmlns 1306 lang = self['lang'] 1307 if not lang: 1308 lang = 'en' 1309 self.attributes['_lang'] = lang 1310 self.attributes['_xml:lang'] = lang 1311 doctype = self['doctype'] 1312 if doctype: 1313 if doctype == 'strict': 1314 doctype = self.strict 1315 elif doctype == 'transitional': 1316 doctype = self.transitional 1317 elif doctype == 'frameset': 1318 doctype = self.frameset 1319 else: 1320 doctype = '%s\n' % doctype 1321 else: 1322 doctype = self.transitional 1323 (fa, co) = self._xml() 1324 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1325
1326 1327 -class HEAD(DIV):
1328 1329 tag = 'head'
1330
1331 1332 -class TITLE(DIV):
1333 1334 tag = 'title'
1335
1336 1337 -class META(DIV):
1338 1339 tag = 'meta/'
1340 1345
1346 1347 -class SCRIPT(DIV):
1348 1349 tag = 'script' 1350
1351 - def xml(self):
1352 (fa, co) = self._xml() 1353 # no escaping of subcomponents 1354 co = '\n'.join([str(component) for component in 1355 self.components]) 1356 if co: 1357 # <script [attributes]><!--//--><![CDATA[//><!-- 1358 # script body 1359 # //--><!]]></script> 1360 # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag) 1361 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag) 1362 else: 1363 return DIV.xml(self)
1364
1365 1366 -class STYLE(DIV):
1367 1368 tag = 'style' 1369
1370 - def xml(self):
1371 (fa, co) = self._xml() 1372 # no escaping of subcomponents 1373 co = '\n'.join([str(component) for component in 1374 self.components]) 1375 if co: 1376 # <style [attributes]><!--/*--><![CDATA[/*><!--*/ 1377 # style body 1378 # /*]]>*/--></style> 1379 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag) 1380 else: 1381 return DIV.xml(self)
1382
1383 1384 -class IMG(DIV):
1385 1386 tag = 'img/'
1387
1388 1389 -class SPAN(DIV):
1390 1391 tag = 'span'
1392
1393 1394 -class BODY(DIV):
1395 1396 tag = 'body'
1397
1398 1399 -class H1(DIV):
1400 1401 tag = 'h1'
1402
1403 1404 -class H2(DIV):
1405 1406 tag = 'h2'
1407
1408 1409 -class H3(DIV):
1410 1411 tag = 'h3'
1412
1413 1414 -class H4(DIV):
1415 1416 tag = 'h4'
1417
1418 1419 -class H5(DIV):
1420 1421 tag = 'h5'
1422
1423 1424 -class H6(DIV):
1425 1426 tag = 'h6'
1427
1428 1429 -class P(DIV):
1430 """ 1431 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided. 1432 1433 see also :class:`DIV` 1434 """ 1435 1436 tag = 'p' 1437
1438 - def xml(self):
1439 text = DIV.xml(self) 1440 if self['cr2br']: 1441 text = text.replace('\n', '<br />') 1442 return text
1443
1444 1445 -class STRONG(DIV):
1446 1447 tag = 'strong'
1448
1449 1450 -class B(DIV):
1451 1452 tag = 'b'
1453
1454 1455 -class BR(DIV):
1456 1457 tag = 'br/'
1458
1459 1460 -class HR(DIV):
1461 1462 tag = 'hr/'
1463
1464 1465 -class A(DIV):
1466 1467 tag = 'a' 1468
1469 - def xml(self):
1470 if not self.components and self['_href']: 1471 self.append(self['_href']) 1472 if self['delete']: 1473 d = "jQuery(this).closest('%s').remove();" % self['delete'] 1474 else: 1475 d = '' 1476 if self['component']: 1477 self['_onclick'] = "web2py_component('%s','%s');%sreturn false;" % \ 1478 (self['component'], self['target'] or '', d) 1479 self['_href'] = self['_href'] or '#null' 1480 elif self['callback']: 1481 returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation();" 1482 if d: 1483 self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want o delete this object?')){ajax('%s',[],'%s');%s};%s" % \ 1484 (self['callback'], self['target'] or '', d, returnfalse) 1485 else: 1486 self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \ 1487 (self['callback'], self['target'] or '', d) 1488 self['_href'] = self['_href'] or '#null' 1489 elif self['cid']: 1490 pre = self['pre_call'] + ';' if self['pre_call'] else '' 1491 self['_onclick'] = '%sweb2py_component("%s","%s");%sreturn false;' % \ 1492 (pre,self['_href'], self['cid'], d) 1493 return DIV.xml(self)
1494
1495 1496 -class BUTTON(DIV):
1497 1498 tag = 'button'
1499
1500 1501 -class EM(DIV):
1502 1503 tag = 'em'
1504
1505 1506 -class EMBED(DIV):
1507 1508 tag = 'embed/'
1509
1510 1511 -class TT(DIV):
1512 1513 tag = 'tt'
1514
1515 1516 -class PRE(DIV):
1517 1518 tag = 'pre'
1519
1520 1521 -class CENTER(DIV):
1522 1523 tag = 'center'
1524
1525 1526 -class CODE(DIV):
1527 1528 """ 1529 displays code in HTML with syntax highlighting. 1530 1531 :param attributes: optional attributes: 1532 1533 - language: indicates the language, otherwise PYTHON is assumed 1534 - link: can provide a link 1535 - styles: for styles 1536 1537 Example:: 1538 1539 {{=CODE(\"print 'hello world'\", language='python', link=None, 1540 counter=1, styles={}, highlight_line=None)}} 1541 1542 1543 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\", 1544 \"web2py\", \"html\". 1545 The \"html\" language interprets {{ and }} tags as \"web2py\" code, 1546 \"html_plain\" doesn't. 1547 1548 if a link='/examples/global/vars/' is provided web2py keywords are linked to 1549 the online docs. 1550 1551 the counter is used for line numbering, counter can be None or a prompt 1552 string. 1553 """ 1554
1555 - def xml(self):
1556 language = self['language'] or 'PYTHON' 1557 link = self['link'] 1558 counter = self.attributes.get('counter', 1) 1559 highlight_line = self.attributes.get('highlight_line', None) 1560 context_lines = self.attributes.get('context_lines', None) 1561 styles = self['styles'] or {} 1562 return highlight( 1563 join(self.components), 1564 language=language, 1565 link=link, 1566 counter=counter, 1567 styles=styles, 1568 attributes=self.attributes, 1569 highlight_line=highlight_line, 1570 context_lines=context_lines, 1571 )
1572
1573 1574 -class LABEL(DIV):
1575 1576 tag = 'label'
1577
1578 1579 -class LI(DIV):
1580 1581 tag = 'li'
1582
1583 1584 -class UL(DIV):
1585 """ 1586 UL Component. 1587 1588 If subcomponents are not LI-components they will be wrapped in a LI 1589 1590 see also :class:`DIV` 1591 """ 1592 1593 tag = 'ul' 1594
1595 - def _fixup(self):
1596 self._wrap_components(LI, LI)
1597
1598 1599 -class OL(UL):
1600 1601 tag = 'ol'
1602
1603 1604 -class TD(DIV):
1605 1606 tag = 'td'
1607
1608 1609 -class TH(DIV):
1610 1611 tag = 'th'
1612
1613 1614 -class TR(DIV):
1615 """ 1616 TR Component. 1617 1618 If subcomponents are not TD/TH-components they will be wrapped in a TD 1619 1620 see also :class:`DIV` 1621 """ 1622 1623 tag = 'tr' 1624
1625 - def _fixup(self):
1626 self._wrap_components((TD, TH), TD)
1627
1628 1629 -class THEAD(DIV):
1630 1631 tag = 'thead' 1632
1633 - def _fixup(self):
1634 self._wrap_components(TR, TR)
1635
1636 1637 -class TBODY(DIV):
1638 1639 tag = 'tbody' 1640
1641 - def _fixup(self):
1642 self._wrap_components(TR, TR)
1643
1644 1645 -class TFOOT(DIV):
1646 1647 tag = 'tfoot' 1648
1649 - def _fixup(self):
1650 self._wrap_components(TR, TR)
1651
1652 1653 -class COL(DIV):
1654 1655 tag = 'col'
1656
1657 1658 -class COLGROUP(DIV):
1659 1660 tag = 'colgroup'
1661
1662 1663 -class TABLE(DIV):
1664 """ 1665 TABLE Component. 1666 1667 If subcomponents are not TR/TBODY/THEAD/TFOOT-components 1668 they will be wrapped in a TR 1669 1670 see also :class:`DIV` 1671 """ 1672 1673 tag = 'table' 1674
1675 - def _fixup(self):
1677
1678 1679 -class I(DIV):
1680 1681 tag = 'i'
1682
1683 1684 -class IFRAME(DIV):
1685 1686 tag = 'iframe'
1687
1688 1689 -class INPUT(DIV):
1690 1691 """ 1692 INPUT Component 1693 1694 examples:: 1695 1696 >>> INPUT(_type='text', _name='name', value='Max').xml() 1697 '<input name=\"name\" type=\"text\" value=\"Max\" />' 1698 1699 >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() 1700 '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />' 1701 1702 >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() 1703 '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />' 1704 1705 >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() 1706 '<input name=\"radio\" type=\"radio\" value=\"no\" />' 1707 1708 the input helper takes two special attributes value= and requires=. 1709 1710 :param value: used to pass the initial value for the input field. 1711 value differs from _value because it works for checkboxes, radio, 1712 textarea and select/option too. 1713 1714 - for a checkbox value should be '' or 'on'. 1715 - for a radio or select/option value should be the _value 1716 of the checked/selected item. 1717 1718 :param requires: should be None, or a validator or a list of validators 1719 for the value of the field. 1720 """ 1721 1722 tag = 'input/' 1723
1724 - def _validate(self):
1725 1726 # # this only changes value, not _value 1727 1728 name = self['_name'] 1729 if name is None or name == '': 1730 return True 1731 name = str(name) 1732 request_vars_get = self.request_vars.get 1733 if self['_type'] != 'checkbox': 1734 self['old_value'] = self['value'] or self['_value'] or '' 1735 value = request_vars_get(name, '') 1736 self['value'] = value if not hasattr(value,'file') else None 1737 else: 1738 self['old_value'] = self['value'] or False 1739 value = request_vars_get(name) 1740 if isinstance(value, (tuple, list)): 1741 self['value'] = self['_value'] in value 1742 else: 1743 self['value'] = self['_value'] == value 1744 requires = self['requires'] 1745 if requires: 1746 if not isinstance(requires, (list, tuple)): 1747 requires = [requires] 1748 for validator in requires: 1749 (value, errors) = validator(value) 1750 if not errors is None: 1751 self.vars[name] = value 1752 self.errors[name] = errors 1753 break 1754 if not name in self.errors: 1755 self.vars[name] = value 1756 return True 1757 return False
1758
1759 - def _postprocessing(self):
1760 t = self['_type'] 1761 if not t: 1762 t = self['_type'] = 'text' 1763 t = t.lower() 1764 value = self['value'] 1765 if self['_value'] is None or isinstance(self['_value'],cgi.FieldStorage): 1766 _value = None 1767 else: 1768 _value = str(self['_value']) 1769 if '_checked' in self.attributes and not 'value' in self.attributes: 1770 pass 1771 elif t == 'checkbox': 1772 if not _value: 1773 _value = self['_value'] = 'on' 1774 if not value: 1775 value = [] 1776 elif value is True: 1777 value = [_value] 1778 elif not isinstance(value, (list, tuple)): 1779 value = str(value).split('|') 1780 self['_checked'] = _value in value and 'checked' or None 1781 elif t == 'radio': 1782 if str(value) == str(_value): 1783 self['_checked'] = 'checked' 1784 else: 1785 self['_checked'] = None 1786 elif not t == 'submit': 1787 if value is None: 1788 self['value'] = _value 1789 elif not isinstance(value, list): 1790 self['_value'] = value
1791
1792 - def xml(self):
1793 name = self.attributes.get('_name', None) 1794 if name and hasattr(self, 'errors') \ 1795 and self.errors.get(name, None) \ 1796 and self['hideerror'] != True: 1797 self['_class'] = (self['_class'] and self['_class'] 1798 + ' ' or '') + 'invalidinput' 1799 return DIV.xml(self) + DIV( 1800 DIV( 1801 self.errors[name], _class='error', 1802 errors=None, _id='%s__error' % name), 1803 _class='error_wrapper').xml() 1804 else: 1805 if self['_class'] and self['_class'].endswith('invalidinput'): 1806 self['_class'] = self['_class'][:-12] 1807 if self['_class'] == '': 1808 self['_class'] = None 1809 return DIV.xml(self)
1810
1811 1812 -class TEXTAREA(INPUT):
1813 1814 """ 1815 example:: 1816 1817 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY()) 1818 1819 'blah blah blah ...' will be the content of the textarea field. 1820 """ 1821 1822 tag = 'textarea' 1823
1824 - def _postprocessing(self):
1825 if not '_rows' in self.attributes: 1826 self['_rows'] = 10 1827 if not '_cols' in self.attributes: 1828 self['_cols'] = 40 1829 if not self['value'] is None: 1830 self.components = [self['value']] 1831 elif self.components: 1832 self['value'] = self.components[0]
1833
1834 1835 -class OPTION(DIV):
1836 1837 tag = 'option' 1838
1839 - def _fixup(self):
1840 if not '_value' in self.attributes: 1841 self.attributes['_value'] = str(self.components[0])
1842
1843 1844 -class OBJECT(DIV):
1845 1846 tag = 'object'
1847
1848 1849 -class OPTGROUP(DIV):
1850 1851 tag = 'optgroup' 1852
1853 - def _fixup(self):
1854 components = [] 1855 for c in self.components: 1856 if isinstance(c, OPTION): 1857 components.append(c) 1858 else: 1859 components.append(OPTION(c, _value=str(c))) 1860 self.components = components
1861
1862 1863 -class SELECT(INPUT):
1864 1865 """ 1866 example:: 1867 1868 >>> from validators import IS_IN_SET 1869 >>> SELECT('yes', 'no', _name='selector', value='yes', 1870 ... requires=IS_IN_SET(['yes', 'no'])).xml() 1871 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>' 1872 1873 """ 1874 1875 tag = 'select' 1876
1877 - def _fixup(self):
1878 components = [] 1879 for c in self.components: 1880 if isinstance(c, (OPTION, OPTGROUP)): 1881 components.append(c) 1882 else: 1883 components.append(OPTION(c, _value=str(c))) 1884 self.components = components
1885
1886 - def _postprocessing(self):
1887 component_list = [] 1888 for c in self.components: 1889 if isinstance(c, OPTGROUP): 1890 component_list.append(c.components) 1891 else: 1892 component_list.append([c]) 1893 options = itertools.chain(*component_list) 1894 1895 value = self['value'] 1896 if not value is None: 1897 if not self['_multiple']: 1898 for c in options: # my patch 1899 if ((value is not None) and 1900 (str(c['_value']) == str(value))): 1901 c['_selected'] = 'selected' 1902 else: 1903 c['_selected'] = None 1904 else: 1905 if isinstance(value, (list, tuple)): 1906 values = [str(item) for item in value] 1907 else: 1908 values = [str(value)] 1909 for c in options: # my patch 1910 if ((value is not None) and 1911 (str(c['_value']) in values)): 1912 c['_selected'] = 'selected' 1913 else: 1914 c['_selected'] = None
1915
1916 1917 -class FIELDSET(DIV):
1918 1919 tag = 'fieldset'
1920
1921 1922 -class LEGEND(DIV):
1923 1924 tag = 'legend'
1925
1926 1927 -class FORM(DIV):
1928 1929 """ 1930 example:: 1931 1932 >>> from validators import IS_NOT_EMPTY 1933 >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) 1934 >>> form.xml() 1935 '<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>' 1936 1937 a FORM is container for INPUT, TEXTAREA, SELECT and other helpers 1938 1939 form has one important method:: 1940 1941 form.accepts(request.vars, session) 1942 1943 if form is accepted (and all validators pass) form.vars contains the 1944 accepted vars, otherwise form.errors contains the errors. 1945 in case of errors the form is modified to present the errors to the user. 1946 """ 1947 1948 tag = 'form' 1949
1950 - def __init__(self, *components, **attributes):
1951 DIV.__init__(self, *components, **attributes) 1952 self.vars = Storage() 1953 self.errors = Storage() 1954 self.latest = Storage() 1955 self.accepted = None # none for not submitted
1956
1957 - def assert_status(self, status, request_vars):
1958 return status
1959
1960 - def accepts( 1961 self, 1962 request_vars, 1963 session=None, 1964 formname='default', 1965 keepvalues=False, 1966 onvalidation=None, 1967 hideerror=False, 1968 **kwargs 1969 ):
1970 """ 1971 kwargs is not used but allows to specify the same interface for FORM and SQLFORM 1972 """ 1973 if request_vars.__class__.__name__ == 'Request': 1974 request_vars = request_vars.post_vars 1975 self.errors.clear() 1976 self.request_vars = Storage() 1977 self.request_vars.update(request_vars) 1978 self.session = session 1979 self.formname = formname 1980 self.keepvalues = keepvalues 1981 1982 # if this tag is a form and we are in accepting mode (status=True) 1983 # check formname and formkey 1984 1985 status = True 1986 changed = False 1987 request_vars = self.request_vars 1988 if session: 1989 formkey = session.get('_formkey[%s]' % formname, None) 1990 # check if user tampering with form and void CSRF 1991 if formkey != request_vars._formkey: 1992 status = False 1993 if formname != request_vars._formname: 1994 status = False 1995 if status and session: 1996 # check if editing a record that has been modified by the server 1997 if hasattr(self, 'record_hash') and self.record_hash != formkey: 1998 status = False 1999 self.record_changed = changed = True 2000 status = self._traverse(status, hideerror) 2001 status = self.assert_status(status, request_vars) 2002 if onvalidation: 2003 if isinstance(onvalidation, dict): 2004 onsuccess = onvalidation.get('onsuccess', None) 2005 onfailure = onvalidation.get('onfailure', None) 2006 onchange = onvalidation.get('onchange', None) 2007 if [k for k in onvalidation if not k in ( 2008 'onsuccess','onfailure','onchange')]: 2009 raise RuntimeError('Invalid key in onvalidate dict') 2010 if onsuccess and status: 2011 call_as_list(onsuccess,self) 2012 if onfailure and request_vars and not status: 2013 call_as_list(onfailure,self) 2014 status = len(self.errors) == 0 2015 if changed: 2016 if onchange and self.record_changed and \ 2017 self.detect_record_change: 2018 call_as_list(onchange,self) 2019 elif status: 2020 call_as_list(onvalidation, self) 2021 if self.errors: 2022 status = False 2023 if not session is None: 2024 if hasattr(self, 'record_hash'): 2025 formkey = self.record_hash 2026 else: 2027 formkey = web2py_uuid() 2028 self.formkey = session['_formkey[%s]' % formname] = formkey 2029 if status and not keepvalues: 2030 self._traverse(False, hideerror) 2031 self.accepted = status 2032 return status
2033
2034 - def _postprocessing(self):
2035 if not '_action' in self.attributes: 2036 self['_action'] = '' 2037 if not '_method' in self.attributes: 2038 self['_method'] = 'post' 2039 if not '_enctype' in self.attributes: 2040 self['_enctype'] = 'multipart/form-data'
2041
2042 - def hidden_fields(self):
2043 c = [] 2044 attr = self.attributes.get('hidden', {}) 2045 if 'hidden' in self.attributes: 2046 c = [INPUT(_type='hidden', _name=key, _value=value) 2047 for (key, value) in attr.iteritems()] 2048 if hasattr(self, 'formkey') and self.formkey: 2049 c.append(INPUT(_type='hidden', _name='_formkey', 2050 _value=self.formkey)) 2051 if hasattr(self, 'formname') and self.formname: 2052 c.append(INPUT(_type='hidden', _name='_formname', 2053 _value=self.formname)) 2054 return DIV(c, _class="hidden")
2055
2056 - def xml(self):
2057 newform = FORM(*self.components, **self.attributes) 2058 hidden_fields = self.hidden_fields() 2059 if hidden_fields.components: 2060 newform.append(hidden_fields) 2061 return DIV.xml(newform)
2062
2063 - def validate(self, **kwargs):
2064 """ 2065 This function validates the form, 2066 you can use it instead of directly form.accepts. 2067 2068 Usage: 2069 In controller 2070 2071 def action(): 2072 form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) 2073 form.validate() #you can pass some args here - see below 2074 return dict(form=form) 2075 2076 This can receive a bunch of arguments 2077 2078 onsuccess = 'flash' - will show message_onsuccess in response.flash 2079 None - will do nothing 2080 can be a function (lambda form: pass) 2081 onfailure = 'flash' - will show message_onfailure in response.flash 2082 None - will do nothing 2083 can be a function (lambda form: pass) 2084 onchange = 'flash' - will show message_onchange in response.flash 2085 None - will do nothing 2086 can be a function (lambda form: pass) 2087 2088 message_onsuccess 2089 message_onfailure 2090 message_onchange 2091 next = where to redirect in case of success 2092 any other kwargs will be passed for form.accepts(...) 2093 """ 2094 from gluon import current, redirect 2095 kwargs['request_vars'] = kwargs.get( 2096 'request_vars', current.request.post_vars) 2097 kwargs['session'] = kwargs.get('session', current.session) 2098 kwargs['dbio'] = kwargs.get('dbio', False) 2099 # necessary for SQLHTML forms 2100 2101 onsuccess = kwargs.get('onsuccess', 'flash') 2102 onfailure = kwargs.get('onfailure', 'flash') 2103 onchange = kwargs.get('onchange', 'flash') 2104 message_onsuccess = kwargs.get('message_onsuccess', 2105 current.T("Success!")) 2106 message_onfailure = kwargs.get('message_onfailure', 2107 current.T("Errors in form, please check it out.")) 2108 message_onchange = kwargs.get('message_onchange', 2109 current.T("Form consecutive submissions not allowed. " + 2110 "Try re-submitting or refreshing the form page.")) 2111 next = kwargs.get('next', None) 2112 for key in ('message_onsuccess', 'message_onfailure', 'onsuccess', 2113 'onfailure', 'next', 'message_onchange', 'onchange'): 2114 if key in kwargs: 2115 del kwargs[key] 2116 2117 if self.accepts(**kwargs): 2118 if onsuccess == 'flash': 2119 if next: 2120 current.session.flash = message_onsuccess 2121 else: 2122 current.response.flash = message_onsuccess 2123 elif callable(onsuccess): 2124 onsuccess(self) 2125 if next: 2126 if self.vars: 2127 for key, value in self.vars.iteritems(): 2128 next = next.replace('[%s]' % key, 2129 urllib.quote(str(value))) 2130 if not next.startswith('/'): 2131 next = URL(next) 2132 redirect(next) 2133 return True 2134 elif self.errors: 2135 if onfailure == 'flash': 2136 current.response.flash = message_onfailure 2137 elif callable(onfailure): 2138 onfailure(self) 2139 return False 2140 elif hasattr(self, "record_changed"): 2141 if self.record_changed and self.detect_record_change: 2142 if onchange == 'flash': 2143 current.response.flash = message_onchange 2144 elif callable(onchange): 2145 onchange(self) 2146 return False
2147
2148 - def process(self, **kwargs):
2149 """ 2150 Perform the .validate() method but returns the form 2151 2152 Usage in controllers: 2153 # directly on return 2154 def action(): 2155 #some code here 2156 return dict(form=FORM(...).process(...)) 2157 2158 You can use it with FORM, SQLFORM or FORM based plugins 2159 2160 Examples: 2161 #response.flash messages 2162 def action(): 2163 form = SQLFORM(db.table).process(message_onsuccess='Sucess!') 2164 retutn dict(form=form) 2165 2166 # callback function 2167 # callback receives True or False as first arg, and a list of args. 2168 def my_callback(status, msg): 2169 response.flash = "Success! "+msg if status else "Errors occured" 2170 2171 # after argument can be 'flash' to response.flash messages 2172 # or a function name to use as callback or None to do nothing. 2173 def action(): 2174 return dict(form=SQLFORM(db.table).process(onsuccess=my_callback) 2175 """ 2176 kwargs['dbio'] = kwargs.get('dbio', True) 2177 # necessary for SQLHTML forms 2178 self.validate(**kwargs) 2179 return self
2180 2181 REDIRECT_JS = "window.location='%s';return false" 2182
2183 - def add_button(self, value, url, _class=None):
2184 submit = self.element('input[type=submit]') 2185 submit.parent.append( 2186 INPUT(_type="button", _value=value, _class=_class, 2187 _onclick=self.REDIRECT_JS % url))
2188 2189 @staticmethod
2190 - def confirm(text='OK', buttons=None, hidden=None):
2191 if not buttons: 2192 buttons = {} 2193 if not hidden: 2194 hidden = {} 2195 inputs = [INPUT(_type='button', 2196 _value=name, 2197 _onclick=FORM.REDIRECT_JS % link) 2198 for name, link in buttons.iteritems()] 2199 inputs += [INPUT(_type='hidden', 2200 _name=name, 2201 _value=value) 2202 for name, value in hidden.iteritems()] 2203 form = FORM(INPUT(_type='submit', _value=text), *inputs) 2204 form.process() 2205 return form
2206
2207 2208 -class BEAUTIFY(DIV):
2209 2210 """ 2211 example:: 2212 2213 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() 2214 '<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>' 2215 2216 turns any list, dictionary, etc into decent looking html. 2217 Two special attributes are 2218 :sorted: a function that takes the dict and returned sorted keys 2219 :keyfilter: a funciton that takes a key and returns its representation 2220 or None if the key is to be skipped. By default key[:1]=='_' is skipped. 2221 """ 2222 2223 tag = 'div' 2224 2225 @staticmethod
2226 - def no_underscore(key):
2227 if key[:1] == '_': 2228 return None 2229 return key
2230
2231 - def __init__(self, component, **attributes):
2232 self.components = [component] 2233 self.attributes = attributes 2234 sorter = attributes.get('sorted', sorted) 2235 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore) 2236 components = [] 2237 attributes = copy.copy(self.attributes) 2238 level = attributes['level'] = attributes.get('level', 6) - 1 2239 if '_class' in attributes: 2240 attributes['_class'] += 'i' 2241 if level == 0: 2242 return 2243 for c in self.components: 2244 if hasattr(c, 'value') and not callable(c.value): 2245 if c.value: 2246 components.append(c.value) 2247 if hasattr(c, 'xml') and callable(c.xml): 2248 components.append(c) 2249 continue 2250 elif hasattr(c, 'keys') and callable(c.keys): 2251 rows = [] 2252 try: 2253 keys = (sorter and sorter(c)) or c 2254 for key in keys: 2255 if isinstance(key, (str, unicode)) and keyfilter: 2256 filtered_key = keyfilter(key) 2257 else: 2258 filtered_key = str(key) 2259 if filtered_key is None: 2260 continue 2261 value = c[key] 2262 if isinstance(value, types.LambdaType): 2263 continue 2264 rows.append( 2265 TR( 2266 TD(filtered_key, _style='font-weight:bold;vertical-align:top'), 2267 TD(':', _valign='top'), 2268 TD(BEAUTIFY(value, **attributes)))) 2269 components.append(TABLE(*rows, **attributes)) 2270 continue 2271 except: 2272 pass 2273 if isinstance(c, str): 2274 components.append(str(c)) 2275 elif isinstance(c, unicode): 2276 components.append(c.encode('utf8')) 2277 elif isinstance(c, (list, tuple)): 2278 items = [TR(TD(BEAUTIFY(item, **attributes))) 2279 for item in c] 2280 components.append(TABLE(*items, **attributes)) 2281 elif isinstance(c, cgi.FieldStorage): 2282 components.append('FieldStorage object') 2283 else: 2284 components.append(repr(c)) 2285 self.components = components
2286 2379
2380 2381 -def embed64( 2382 filename=None, 2383 file=None, 2384 data=None, 2385 extension='image/gif', 2386 ):
2387 """ 2388 helper to encode the provided (binary) data into base64. 2389 2390 :param filename: if provided, opens and reads this file in 'rb' mode 2391 :param file: if provided, reads this file 2392 :param data: if provided, uses the provided data 2393 """ 2394 2395 if filename and os.path.exists(file): 2396 fp = open(filename, 'rb') 2397 data = fp.read() 2398 fp.close() 2399 data = base64.b64encode(data) 2400 return 'data:%s;base64,%s' % (extension, data)
2401
2402 2403 -def test():
2404 """ 2405 Example: 2406 2407 >>> from validators import * 2408 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() 2409 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div> 2410 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() 2411 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div> 2412 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() 2413 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div> 2414 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml() 2415 <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> 2416 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10'))) 2417 >>> print form.xml() 2418 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form> 2419 >>> print form.accepts({'myvar':'34'}, formname=None) 2420 False 2421 >>> print form.xml() 2422 <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> 2423 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) 2424 True 2425 >>> print form.xml() 2426 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form> 2427 >>> form=FORM(SELECT('cat', 'dog', _name='myvar')) 2428 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True) 2429 True 2430 >>> print form.xml() 2431 <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> 2432 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!'))) 2433 >>> print form.accepts({'myvar':'as df'}, formname=None) 2434 False 2435 >>> print form.xml() 2436 <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> 2437 >>> session={} 2438 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) 2439 >>> if form.accepts({}, session,formname=None): print 'passed' 2440 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed' 2441 """ 2442 pass
2443
2444 2445 -class web2pyHTMLParser(HTMLParser):
2446 """ 2447 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers. 2448 obj.tree contains the root of the tree, and tree can be manipulated 2449 2450 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree) 2451 'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz' 2452 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree) 2453 '<div>a<span>b</span></div>c' 2454 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree 2455 >>> tree.element(_a='b')['_c']=5 2456 >>> str(tree) 2457 'hello<div a="b" c="5">world</div>' 2458 """
2459 - def __init__(self, text, closed=('input', 'link')):
2460 HTMLParser.__init__(self) 2461 self.tree = self.parent = TAG['']() 2462 self.closed = closed 2463 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)] 2464 self.last = None 2465 self.feed(text)
2466
2467 - def handle_starttag(self, tagname, attrs):
2468 if tagname.upper() in self.tags: 2469 tag = eval(tagname.upper()) 2470 else: 2471 if tagname in self.closed: 2472 tagname += '/' 2473 tag = TAG[tagname]() 2474 for key, value in attrs: 2475 tag['_' + key] = value 2476 tag.parent = self.parent 2477 self.parent.append(tag) 2478 if not tag.tag.endswith('/'): 2479 self.parent = tag 2480 else: 2481 self.last = tag.tag[:-1]
2482
2483 - def handle_data(self, data):
2484 if not isinstance(data, unicode): 2485 try: 2486 data = data.decode('utf8') 2487 except: 2488 data = data.decode('latin1') 2489 self.parent.append(data.encode('utf8', 'xmlcharref'))
2490
2491 - def handle_charref(self, name):
2492 if name.startswith('x'): 2493 self.parent.append(unichr(int(name[1:], 16)).encode('utf8')) 2494 else: 2495 self.parent.append(unichr(int(name)).encode('utf8'))
2496
2497 - def handle_entityref(self, name):
2498 self.parent.append(entitydefs[name])
2499
2500 - def handle_endtag(self, tagname):
2501 # this deals with unbalanced tags 2502 if tagname == self.last: 2503 return 2504 while True: 2505 try: 2506 parent_tagname = self.parent.tag 2507 self.parent = self.parent.parent 2508 except: 2509 raise RuntimeError("unable to balance tag %s" % tagname) 2510 if parent_tagname[:len(tagname)] == tagname: break
2511
2512 2513 -def markdown_serializer(text, tag=None, attr=None):
2514 attr = attr or {} 2515 if tag is None: 2516 return re.sub('\s+', ' ', text) 2517 if tag == 'br': 2518 return '\n\n' 2519 if tag == 'h1': 2520 return '#' + text + '\n\n' 2521 if tag == 'h2': 2522 return '#' * 2 + text + '\n\n' 2523 if tag == 'h3': 2524 return '#' * 3 + text + '\n\n' 2525 if tag == 'h4': 2526 return '#' * 4 + text + '\n\n' 2527 if tag == 'p': 2528 return text + '\n\n' 2529 if tag == 'b' or tag == 'strong': 2530 return '**%s**' % text 2531 if tag == 'em' or tag == 'i': 2532 return '*%s*' % text 2533 if tag == 'tt' or tag == 'code': 2534 return '`%s`' % text 2535 if tag == 'a': 2536 return '[%s](%s)' % (text, attr.get('_href', '')) 2537 if tag == 'img': 2538 return '![%s](%s)' % (attr.get('_alt', ''), attr.get('_src', '')) 2539 return text
2540
2541 2542 -def markmin_serializer(text, tag=None, attr=None):
2543 attr = attr or {} 2544 # if tag is None: return re.sub('\s+',' ',text) 2545 if tag == 'br': 2546 return '\n\n' 2547 if tag == 'h1': 2548 return '# ' + text + '\n\n' 2549 if tag == 'h2': 2550 return '#' * 2 + ' ' + text + '\n\n' 2551 if tag == 'h3': 2552 return '#' * 3 + ' ' + text + '\n\n' 2553 if tag == 'h4': 2554 return '#' * 4 + ' ' + text + '\n\n' 2555 if tag == 'p': 2556 return text + '\n\n' 2557 if tag == 'li': 2558 return '\n- ' + text.replace('\n', ' ') 2559 if tag == 'tr': 2560 return text[3:].replace('\n', ' ') + '\n' 2561 if tag in ['table', 'blockquote']: 2562 return '\n-----\n' + text + '\n------\n' 2563 if tag in ['td', 'th']: 2564 return ' | ' + text 2565 if tag in ['b', 'strong', 'label']: 2566 return '**%s**' % text 2567 if tag in ['em', 'i']: 2568 return "''%s''" % text 2569 if tag in ['tt']: 2570 return '``%s``' % text.strip() 2571 if tag in ['code']: 2572 return '``\n%s``' % text 2573 if tag == 'a': 2574 return '[[%s %s]]' % (text, attr.get('_href', '')) 2575 if tag == 'img': 2576 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', '')) 2577 return text
2578
2579 2580 -class MARKMIN(XmlComponent):
2581 """ 2582 For documentation: http://web2py.com/examples/static/markmin.html 2583 """
2584 - def __init__(self, text, extra=None, allowed=None, sep='p', 2585 url=None, environment=None, latex='google', 2586 autolinks='default', 2587 protolinks='default', 2588 class_prefix='', 2589 id_prefix='markmin_'):
2590 self.text = text 2591 self.extra = extra or {} 2592 self.allowed = allowed or {} 2593 self.sep = sep 2594 self.url = URL if url == True else url 2595 self.environment = environment 2596 self.latex = latex 2597 self.autolinks = autolinks 2598 self.protolinks = protolinks 2599 self.class_prefix = class_prefix 2600 self.id_prefix = id_prefix
2601
2602 - def xml(self):
2603 """ 2604 calls the gluon.contrib.markmin render function to convert the wiki syntax 2605 """ 2606 from contrib.markmin.markmin2html import render 2607 return render(self.text, extra=self.extra, 2608 allowed=self.allowed, sep=self.sep, latex=self.latex, 2609 URL=self.url, environment=self.environment, 2610 autolinks=self.autolinks, protolinks=self.protolinks, 2611 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2612
2613 - def __str__(self):
2614 return self.xml()
2615
2616 - def flatten(self, render=None):
2617 """ 2618 return the text stored by the MARKMIN object rendered by the render function 2619 """ 2620 return self.text
2621
2622 - def elements(self, *args, **kargs):
2623 """ 2624 to be considered experimental since the behavior of this method is questionable 2625 another options could be TAG(self.text).elements(*args,**kargs) 2626 """ 2627 return [self.text]
2628 2629 2630 if __name__ == '__main__': 2631 import doctest 2632 doctest.testmod() 2633