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

Source Code for Module web2py.gluon.html

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