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