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