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 web2py Web Framework (Copyrighted, 2007-2010). 
   6  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>. 
   7  License: GPL v2 
   8  """ 
   9   
  10  import uuid 
  11  import cgi 
  12  import os 
  13  import re 
  14  import copy 
  15  import types 
  16  import urllib 
  17  import base64 
  18  import sanitizer 
  19  import rewrite 
  20   
  21  from storage import Storage 
  22  from validators import * 
  23  from highlight import highlight 
  24   
  25  regex_crlf = re.compile('\r|\n') 
  26   
  27  __all__ = [ 
  28      'A', 
  29      'B', 
  30      'BEAUTIFY', 
  31      'BODY', 
  32      'BR', 
  33      'CENTER', 
  34      'CODE', 
  35      'DIV', 
  36      'EM', 
  37      'EMBED', 
  38      'FIELDSET', 
  39      'FORM', 
  40      'H1', 
  41      'H2', 
  42      'H3', 
  43      'H4', 
  44      'H5', 
  45      'H6', 
  46      'HEAD', 
  47      'HR', 
  48      'HTML', 
  49      'I', 
  50      'IFRAME', 
  51      'IMG', 
  52      'INPUT', 
  53      'LABEL', 
  54      'LEGEND', 
  55      'LI', 
  56      'LINK', 
  57      'OL', 
  58      'UL', 
  59      'MENU', 
  60      'META', 
  61      'OBJECT', 
  62      'ON', 
  63      'OPTION', 
  64      'P', 
  65      'PRE', 
  66      'SCRIPT', 
  67      'SELECT', 
  68      'SPAN', 
  69      'STYLE', 
  70      'TABLE', 
  71      'TAG', 
  72      'TD', 
  73      'TEXTAREA', 
  74      'TH', 
  75      'THEAD', 
  76      'TBODY', 
  77      'TFOOT', 
  78      'TITLE', 
  79      'TR', 
  80      'TT', 
  81      'URL', 
  82      'XHTML', 
  83      'XML', 
  84      'xmlescape', 
  85      'embed64', 
  86      ] 
  87   
  88   
89 -def xmlescape(data, quote = False):
90 """ 91 returns an escaped string of the provided data 92 93 :param data: the data to be escaped 94 :param quote: optional (default False) 95 """ 96 97 # first try the xml function 98 try: 99 return data.xml() 100 except AttributeError: 101 pass 102 except TypeError: 103 pass 104 105 # otherwise, make it a string 106 if not isinstance(data, (str, unicode)): 107 data = str(data) 108 elif isinstance(data, unicode): 109 data = data.encode('utf8', 'xmlcharrefreplace') 110 111 # ... and do the escaping 112 data = cgi.escape(data, quote) 113 return data
114 115
116 -def URL( 117 a=None, 118 c=None, 119 f=None, 120 r=None, 121 args=[], 122 vars={}, 123 anchor='', 124 extension=None, 125 ):
126 """ 127 generate a relative URL 128 129 example:: 130 131 >>> URL(a='a', c='c', f='f', args=['x', 'y', 'z'], 132 ... vars={'p':1, 'q':2}, anchor='1') 133 '/a/c/f/x/y/z#1?q=2&p=1' 134 135 generates a url \"/a/c/f\" corresponding to application a, controller c 136 and function f. If r=request is passed, a, c, f are set, respectively, 137 to r.application, r.controller, r.function. 138 139 The more typical usage is: 140 141 URL(r=request, f='index') that generates a url for the index function 142 within the present application and controller. 143 144 :param a: application (default to current if r is given) 145 :param c: controller (default to current if r is given) 146 :param f: function (default to current if r is given) 147 :param r: request (optional) 148 :param args: any arguments (optional) 149 :param vars: any variables (optional) 150 :param anchor: anchorname, without # (optional) 151 152 :raises SyntaxError: when no application, controller or function is 153 available 154 :raises SyntaxError: when a CRLF is found in the generated url 155 """ 156 157 application = controller = function = None 158 if r: 159 application = r.application 160 controller = r.controller 161 function = r.function 162 if a: 163 application = a 164 if c: 165 controller = c 166 if f: 167 if isinstance(f, str): 168 function = f 169 else: 170 function = f.__name__ 171 172 if not (application and controller and function): 173 raise SyntaxError, 'not enough information to build the url' 174 175 other = '' 176 if args != [] and not isinstance(args, (list, tuple)): 177 args = [args] 178 if args: 179 other = urllib.quote('/' + '/'.join([str(x) for x in args])) 180 if extension: 181 function += '.'+extension 182 if anchor: 183 other += '#' + urllib.quote(str(anchor)) 184 if vars: 185 other += '?%s' % urllib.urlencode(vars) 186 187 url = '/%s/%s/%s%s' % (application, controller, function, other) 188 189 if regex_crlf.search(url): 190 raise SyntaxError, 'CRLF Injection Detected' 191 return rewrite.filter_out(url)
192 193 194 ON = True 195 196
197 -class XmlComponent(object):
198 """ 199 Abstract root for all Html components 200 """ 201 202 # TODO: move some DIV methods to here 203
204 - def xml(self):
205 raise NotImplementedError
206 207
208 -class XML(XmlComponent):
209 """ 210 use it to wrap a string that contains XML/HTML so that it will not be 211 escaped by the template 212 213 example: 214 215 >>> XML('<h1>Hello</h1>').xml() 216 '<h1>Hello</h1>' 217 """ 218
219 - def __init__( 220 self, 221 text, 222 sanitize = False, 223 permitted_tags = [ 224 'a', 225 'b', 226 'blockquote', 227 'br/', 228 'i', 229 'li', 230 'ol', 231 'ul', 232 'p', 233 'cite', 234 'code', 235 'pre', 236 'img/', 237 ], 238 allowed_attributes = { 239 'a': ['href', 'title'], 240 'img': ['src', 'alt'], 241 'blockquote': ['type'] 242 }, 243 ):
244 """ 245 :param text: the XML text 246 :param sanitize: sanitize text using the permitted tags and allowed 247 attributes (default False) 248 :param permitted_tags: list of permitted tags (default: simple list of 249 tags) 250 :param allowed_attributes: dictionary of allowed attributed (default 251 for A, IMG and BlockQuote). 252 The key is the tag; the value is a list of allowed attributes. 253 """ 254 255 if sanitize: 256 text = sanitizer.sanitize(text, permitted_tags, 257 allowed_attributes) 258 if isinstance(text, unicode): 259 text = text.encode('utf8', 'xmlcharrefreplace') 260 elif not isinstance(text, str): 261 text = str(text) 262 self.text = text
263
264 - def xml(self):
265 return self.text
266
267 - def __str__(self):
268 return self.xml()
269 270
271 -class DIV(XmlComponent):
272 """ 273 HTML helper, for easy generating and manipulating a DOM structure. 274 Little or no validation is done. 275 276 Behaves like a dictionary regarding updating of attributes. 277 Behaves like a list regarding inserting/appending components. 278 279 example:: 280 281 >>> DIV('hello', 'world', _style='color:red;').xml() 282 '<div style=\"color:red;\">helloworld</div>' 283 284 all other HTML helpers are derived from DIV. 285 286 _something=\"value\" attributes are transparently translated into 287 something=\"value\" HTML attributes 288 """ 289 290 # name of the tag, subclasses should update this 291 # tags ending with a '/' denote classes that cannot 292 # contain components 293 tag = 'div' 294
295 - def __init__(self, *components, **attributes):
296 """ 297 :param *components: any components that should be nested in this element 298 :param **attributes: any attributes you want to give to this element 299 300 :raises SyntaxError: when a stand alone tag receives components 301 """ 302 303 if self.tag[-1:] == '/' and components: 304 raise SyntaxError, '<%s> tags cannot have components'\ 305 % self.tag 306 if len(components) == 1 and isinstance(components[0], (list, 307 tuple)): 308 self.components = list(components[0]) 309 else: 310 self.components = list(components) 311 self.attributes = attributes 312 self._fixup() 313 # converts special attributes in components attributes 314 self._postprocessing()
315
316 - def update(self, **kargs):
317 """ 318 dictionary like updating of the tag attributes 319 """ 320 321 for (key, value) in kargs.items(): 322 self[key] = value 323 return self
324
325 - def append(self, value):
326 """ 327 list style appending of components 328 """ 329 330 return self.components.append(value)
331
332 - def insert(self, i, value):
333 """ 334 list style inserting of components 335 """ 336 337 return self.components.insert(i, value)
338
339 - def __getitem__(self, i):
340 """ 341 gets attribute with name 'i' or component #i. 342 If attribute 'i' is not found returns None 343 344 :param i: index 345 if i is a string: the name of the attribute 346 otherwise references to number of the component 347 """ 348 349 if isinstance(i, str): 350 try: 351 return self.attributes[i] 352 except KeyError: 353 return None 354 else: 355 return self.components[i]
356
357 - def __setitem__(self, i, value):
358 """ 359 sets attribute with name 'i' or component #i. 360 361 :param i: index 362 if i is a string: the name of the attribute 363 otherwise references to number of the component 364 :param value: the new value 365 """ 366 367 if isinstance(i, str): 368 self.attributes[i] = value 369 else: 370 self.components[i] = value
371
372 - def __delitem__(self, i):
373 """ 374 deletes attribute with name 'i' or component #i. 375 376 :param i: index 377 if i is a string: the name of the attribute 378 otherwise references to number of the component 379 """ 380 381 if isinstance(i, str): 382 del self.attributes[i] 383 else: 384 del self.components[i]
385
386 - def __len__(self):
387 """ 388 returns the number of included components 389 """ 390 return len(self.components)
391
392 - def __nonzero__(self):
393 """ 394 always return True 395 """ 396 return True
397
398 - def _fixup(self):
399 """ 400 Handling of provided components. 401 402 Nothing to fixup yet. May be overridden by subclasses, 403 eg for wrapping some components in another component or blocking them. 404 """ 405 return
406
407 - def _wrap_components(self, allowed_parents, wrap_parent = None, 408 wrap_lambda = None):
409 """ 410 helper for _fixup. Checks if a component is in allowed_parents, 411 otherwise wraps it in wrap_parent 412 413 :param allowed_parents: (tuple) classes that the component should be an 414 instance of 415 :param wrap_parent: the class to wrap the component in, if needed 416 :param wrap_lambda: lambda to use for wrapping, if needed 417 418 """ 419 components = [] 420 for c in self.components: 421 if isinstance(c, allowed_parents): 422 components.append(c) 423 else: 424 if wrap_lambda: 425 components.append(wrap_lambda(c)) 426 else: 427 components.append(wrap_parent(c)) 428 self.components = components
429
430 - def _postprocessing(self):
431 """ 432 Handling of attributes (normally the ones not prefixed with '_'). 433 434 Nothing to postprocess yet. May be overridden by subclasses 435 """ 436 return
437
438 - def _traverse(self, status):
439 # TODO: docstring 440 newstatus = status 441 for c in self.components: 442 if hasattr(c, '_traverse'): 443 c.vars = self.vars 444 c.request_vars = self.request_vars 445 c.errors = self.errors 446 c.latest = self.latest 447 c.session = self.session 448 c.formname = self.formname 449 newstatus = c._traverse(status) and newstatus 450 451 # for input, textarea, select, option 452 # deal with 'value' and 'validation' 453 454 name = self['_name'] 455 if newstatus: 456 newstatus = self._validate() 457 self._postprocessing() 458 elif 'old_value' in self.attributes: 459 self['value'] = self['old_value'] 460 self._postprocessing() 461 elif name and name in self.vars: 462 self['value'] = self.vars[name] 463 self._postprocessing() 464 if name: 465 self.latest[name] = self['value'] 466 return newstatus
467
468 - def _validate(self):
469 """ 470 nothing to validate yet. May be overridden by subclasses 471 """ 472 return True
473
474 - def _xml(self):
475 """ 476 helper for xml generation. Returns separately: 477 - the component attributes 478 - the generated xml of the inner components 479 480 Component attributes start with an underscore ('_') and 481 do not have a False or None value. The underscore is removed 482 and the name will be in lower case. 483 A value of True is replaced with the attribute name. 484 485 :returns: tuple: (attributes, components) 486 """ 487 488 # get the attributes for this component 489 # (they start with '_', others may have special meanings) 490 fa = '' 491 for key in sorted(self.attributes): 492 value = self[key] 493 if key[:1] != '_': 494 continue 495 name = key[1:].lower() 496 if value is True: 497 value = name 498 elif value is False or value is None: 499 continue 500 fa += ' %s="%s"' % (name, xmlescape(value, True)) 501 502 # get the xml for the inner components 503 co = ''.join([xmlescape(component) for component in 504 self.components]) 505 506 return (fa, co)
507
508 - def xml(self):
509 """ 510 generates the xml for this component. 511 """ 512 513 (fa, co) = self._xml() 514 515 if not self.tag: 516 return co 517 518 if self.tag[-1:] == '/': 519 # <tag [attributes] /> 520 return '<%s%s />' % (self.tag[:-1], fa) 521 522 # else: <tag [attributes]> inner components xml </tag> 523 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
524
525 - def __str__(self):
526 """ 527 str(COMPONENT) returns equals COMPONENT.xml() 528 """ 529 530 return self.xml()
531
532 - def elements(self, *args, **kargs):
533 """ 534 find all component that match the supplied attribute dictionary, 535 or None if nothing could be found 536 537 All components of the components are searched. 538 """ 539 # make a copy of the components 540 components = [self] 541 matches = [] 542 first_only = False 543 if kargs.has_key("first_only"): 544 first_only = kargs["first_only"] 545 del kargs["first_only"] 546 # loop the copy 547 for c in components: 548 try: 549 # if the component has components, add it to the list 550 # so it can be part of the processing 551 components += copy.copy(c.components) 552 # check if the component has an attribute with the same 553 # value as provided 554 check = True 555 tag = getattr(c,'tag').replace("/","") 556 if args and tag not in args: 557 check = False 558 for (key, value) in kargs.items(): 559 if c[key] != value: 560 check = False 561 # if found, return the component 562 if check: 563 matches.append(c) 564 if first_only: break 565 except: 566 pass 567 return matches
568
569 - def element(self, *args, **kargs):
570 """ 571 find the first component that matches the supplied attribute dictionary, 572 or None if nothing could be found 573 574 Also the components of the components are searched. 575 """ 576 kargs['first_only'] = True 577 elements = self.elements(*args, **kargs) 578 if not elements: 579 # we found nothing 580 return None 581 return elements[0]
582 583
584 -class __TAG__(XmlComponent):
585 586 """ 587 TAG factory example:: 588 589 >>> print TAG.first(TAG.second('test'), _key = 3) 590 <first key=\"3\"><second>test</second></first> 591 592 """ 593
594 - def __getitem__(self, name):
595 return self.__getattr__(name)
596
597 - def __getattr__(self, name):
598 if name[-1:] == '_': 599 name = name[:-1] + '/' 600 601 class __tag__(DIV): 602 603 tag = name
604 605 606 return lambda *a, **b: __tag__(*a, **b)
607 608 609 TAG = __TAG__() 610 611
612 -class HTML(DIV):
613 """ 614 There are four predefined document type definitions. 615 They can be specified in the 'doctype' parameter: 616 617 -'strict' enables strict doctype 618 -'transitional' enables transitional doctype (default) 619 -'frameset' enables frameset doctype 620 -'html5' enables HTML 5 doctype 621 -any other string will be treated as user's own doctype 622 623 'lang' parameter specifies the language of the document. 624 Defaults to 'en'. 625 626 See also :class:`DIV` 627 """ 628 629 tag = 'html' 630 631 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' 632 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' 633 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' 634 html5 = '<!DOCTYPE HTML>\n' 635
636 - def xml(self):
637 lang = self['lang'] 638 if not lang: 639 lang = 'en' 640 self.attributes['_lang'] = lang 641 doctype = self['doctype'] 642 if doctype: 643 if doctype == 'strict': 644 doctype = self.strict 645 elif doctype == 'transitional': 646 doctype = self.transitional 647 elif doctype == 'frameset': 648 doctype = self.frameset 649 elif doctype == 'html5': 650 doctype = self.html5 651 else: 652 doctype = '%s\n' % doctype 653 else: 654 doctype = self.transitional 655 (fa, co) = self._xml() 656 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
657 658
659 -class XHTML(DIV):
660 """ 661 This is XHTML version of the HTML helper. 662 663 There are three predefined document type definitions. 664 They can be specified in the 'doctype' parameter: 665 666 -'strict' enables strict doctype 667 -'transitional' enables transitional doctype (default) 668 -'frameset' enables frameset doctype 669 -any other string will be treated as user's own doctype 670 671 'lang' parameter specifies the language of the document and the xml document. 672 Defaults to 'en'. 673 674 'xmlns' parameter specifies the xml namespace. 675 Defaults to 'http://www.w3.org/1999/xhtml'. 676 677 See also :class:`DIV` 678 """ 679 680 tag = 'html' 681 682 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' 683 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' 684 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n' 685 xmlns = 'http://www.w3.org/1999/xhtml' 686
687 - def xml(self):
688 xmlns = self['xmlns'] 689 if xmlns: 690 self.attributes['_xmlns'] = xmlns 691 else: 692 self.attributes['_xmlns'] = self.xmlns 693 lang = self['lang'] 694 if not lang: 695 lang = 'en' 696 self.attributes['_lang'] = lang 697 self.attributes['_xml:lang'] = lang 698 doctype = self['doctype'] 699 if doctype: 700 if doctype == 'strict': 701 doctype = self.strict 702 elif doctype == 'transitional': 703 doctype = self.transitional 704 elif doctype == 'frameset': 705 doctype = self.frameset 706 else: 707 doctype = '%s\n' % doctype 708 else: 709 doctype = self.transitional 710 (fa, co) = self._xml() 711 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
712 713
714 -class HEAD(DIV):
715 716 tag = 'head'
717 718
719 -class TITLE(DIV):
720 721 tag = 'title'
722 723
724 -class META(DIV):
725 726 tag = 'meta/'
727 728 732 733
734 -class SCRIPT(DIV):
735 736 tag = 'script' 737
738 - def xml(self):
739 (fa, co) = self._xml() 740 # no escaping of subcomponents 741 co = '\n'.join([str(component) for component in 742 self.components]) 743 if co: 744 #<script [attributes]><!--//--><![CDATA[//><!-- 745 #script body 746 #//--><!]]></script> 747 #return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag) 748 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag) 749 else: 750 return DIV.xml(self)
751 752
753 -class STYLE(DIV):
754 755 tag = 'style' 756
757 - def xml(self):
758 (fa, co) = self._xml() 759 # no escaping of subcomponents 760 co = '\n'.join([str(component) for component in 761 self.components]) 762 if co: 763 #<style [attributes]><!--/*--><![CDATA[/*><!--*/ 764 #style body 765 #/*]]>*/--></style> 766 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag) 767 else: 768 return DIV.xml(self)
769 770
771 -class IMG(DIV):
772 773 tag = 'img/'
774 775
776 -class SPAN(DIV):
777 778 tag = 'span'
779 780
781 -class BODY(DIV):
782 783 tag = 'body'
784 785
786 -class H1(DIV):
787 788 tag = 'h1'
789 790
791 -class H2(DIV):
792 793 tag = 'h2'
794 795
796 -class H3(DIV):
797 798 tag = 'h3'
799 800
801 -class H4(DIV):
802 803 tag = 'h4'
804 805
806 -class H5(DIV):
807 808 tag = 'h5'
809 810
811 -class H6(DIV):
812 813 tag = 'h6'
814 815
816 -class P(DIV):
817 """ 818 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided. 819 820 see also :class:`DIV` 821 """ 822 823 tag = 'p' 824
825 - def xml(self):
826 text = DIV.xml(self) 827 if self['cr2br']: 828 text = text.replace('\n', '<br />') 829 return text
830 831
832 -class B(DIV):
833 834 tag = 'b'
835 836
837 -class BR(DIV):
838 839 tag = 'br/'
840 841
842 -class HR(DIV):
843 844 tag = 'hr/'
845 846
847 -class A(DIV):
848 849 tag = 'a'
850 851
852 -class EM(DIV):
853 854 tag = 'em'
855 856
857 -class EMBED(DIV):
858 859 tag = 'embed/'
860 861
862 -class TT(DIV):
863 864 tag = 'tt'
865 866
867 -class PRE(DIV):
868 869 tag = 'pre'
870 871
872 -class CENTER(DIV):
873 874 tag = 'center'
875 876
877 -class CODE(DIV):
878 879 """ 880 displays code in HTML with syntax highlighting. 881 882 :param attributes: optional attributes: 883 884 - language: indicates the language, otherwise PYTHON is assumed 885 - link: can provide a link 886 - styles: for styles 887 888 Example:: 889 890 {{=CODE(\"print 'hello world'\", language='python', link=None, 891 counter=1, styles={})}} 892 893 894 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\", 895 \"web2py\", \"html\". 896 The \"html\" language interprets {{ and }} tags as \"web2py\" code, 897 \"html_plain\" doesn't. 898 899 if a link='/examples/global/vars/' is provided web2py keywords are linked to 900 the online docs. 901 902 the counter is used for line numbering, counter can be None or a prompt 903 string. 904 """ 905
906 - def xml(self):
907 language = self['language'] or 'PYTHON' 908 link = self['link'] 909 counter = self.attributes.get('counter', 1) 910 styles = self['styles'] or {} 911 return highlight( 912 ''.join(self.components), 913 language=language, 914 link=link, 915 counter=counter, 916 styles=styles, 917 attributes=self.attributes, 918 )
919 920
921 -class LABEL(DIV):
922 923 tag = 'label'
924 925
926 -class LI(DIV):
927 928 tag = 'li'
929 930
931 -class UL(DIV):
932 """ 933 UL Component. 934 935 If subcomponents are not LI-components they will be wrapped in a LI 936 937 see also :class:`DIV` 938 """ 939 940 tag = 'ul' 941
942 - def _fixup(self):
943 self._wrap_components(LI, LI)
944 945
946 -class OL(UL):
947 948 tag = 'ol'
949 950
951 -class TD(DIV):
952 953 tag = 'td'
954 955
956 -class TH(DIV):
957 958 tag = 'th'
959 960
961 -class TR(DIV):
962 """ 963 TR Component. 964 965 If subcomponents are not TD/TH-components they will be wrapped in a TD 966 967 see also :class:`DIV` 968 """ 969 970 tag = 'tr' 971
972 - def _fixup(self):
973 self._wrap_components((TD, TH), TD)
974 975
976 -class THEAD(DIV):
977 978 tag = 'thead'
979 980
981 -class TBODY(DIV):
982 983 tag = 'tbody'
984 985
986 -class TFOOT(DIV):
987 988 tag = 'tfoot'
989 990
991 -class TABLE(DIV):
992 """ 993 TABLE Component. 994 995 If subcomponents are not TR/TBODY/THEAD/TFOOT-components 996 they will be wrapped in a TR 997 998 see also :class:`DIV` 999 """ 1000 1001 tag = 'table' 1002
1003 - def _fixup(self):
1005
1006 -class I(DIV):
1007 1008 tag = 'i'
1009
1010 -class IFRAME(DIV):
1011 1012 tag = 'iframe'
1013 1014
1015 -class INPUT(DIV):
1016 1017 """ 1018 INPUT Component 1019 1020 examples:: 1021 1022 >>> INPUT(_type='text', _name='name', value='Max').xml() 1023 '<input name=\"name\" type=\"text\" value=\"Max\" />' 1024 1025 >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() 1026 '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />' 1027 1028 >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() 1029 '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />' 1030 1031 >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() 1032 '<input name=\"radio\" type=\"radio\" value=\"no\" />' 1033 1034 the input helper takes two special attributes value= and requires=. 1035 1036 :param value: used to pass the initial value for the input field. 1037 value differs from _value because it works for checkboxes, radio, 1038 textarea and select/option too. 1039 1040 - for a checkbox value should be '' or 'on'. 1041 - for a radio or select/option value should be the _value 1042 of the checked/selected item. 1043 1044 :param requires: should be None, or a validator or a list of validators 1045 for the value of the field. 1046 """ 1047 1048 tag = 'input/' 1049
1050 - def _validate(self):
1051 1052 # # this only changes value, not _value 1053 1054 name = self['_name'] 1055 if name == None or name == '': 1056 return True 1057 name = str(name) 1058 if self['_type'] != 'checkbox': 1059 self['old_value'] = self['value'] or self['_value'] or '' 1060 value = self.request_vars.get(name, '') 1061 self['value'] = value 1062 else: 1063 self['old_value'] = self['value'] or False 1064 value = self.request_vars.get(name) 1065 if isinstance(value, (tuple, list)): 1066 self['value'] = self['_value'] in value 1067 else: 1068 self['value'] = self['_value'] == value 1069 requires = self['requires'] 1070 if requires: 1071 if not isinstance(requires, (list, tuple)): 1072 requires = [requires] 1073 for validator in requires: 1074 (value, errors) = validator(value) 1075 if errors != None: 1076 self.vars[name] = value 1077 self.errors[name] = errors 1078 break 1079 if not name in self.errors: 1080 self.vars[name] = value 1081 return True 1082 return False
1083
1084 - def _postprocessing(self):
1085 t = self['_type'] 1086 if not t: 1087 t = self['_type'] = 'text' 1088 t = t.lower() 1089 if t == 'checkbox': 1090 if not self['_value']: 1091 self['_value'] = 'on' 1092 if self['value']: 1093 self['_checked'] = 'checked' 1094 else: 1095 self['_checked'] = None 1096 elif t == 'radio': 1097 if str(self['value']) == str(self['_value']): 1098 self['_checked'] = 'checked' 1099 else: 1100 self['_checked'] = None 1101 elif t == 'text': 1102 if self['value'] != None: 1103 self['_value'] = self['value'] 1104 else: 1105 self['value'] = self['_value']
1106
1107 - def xml(self):
1108 name = self.attributes.get('_name', None) 1109 if name and hasattr(self, 'errors') \ 1110 and self.errors.get(name, None) \ 1111 and self['hideerror'] != True: 1112 return DIV.xml(self) + DIV(self.errors[name], _class='error', 1113 errors=None, _id='%s__error' % name).xml() 1114 else: 1115 return DIV.xml(self)
1116 1117
1118 -class TEXTAREA(INPUT):
1119 1120 """ 1121 example:: 1122 1123 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY()) 1124 1125 'blah blah blah ...' will be the content of the textarea field. 1126 """ 1127 1128 tag = 'textarea' 1129
1130 - def _postprocessing(self):
1131 if not '_rows' in self.attributes: 1132 self['_rows'] = 10 1133 if not '_cols' in self.attributes: 1134 self['_cols'] = 40 1135 if self['value'] != None: 1136 self.components = [self['value']] 1137 elif self.components: 1138 self['value'] = self.components[0]
1139 1140
1141 -class OPTION(DIV):
1142 1143 tag = 'option' 1144
1145 - def _fixup(self):
1146 if not '_value' in self.attributes: 1147 self.attributes['_value'] = str(self.components[0])
1148 1149
1150 -class OBJECT(DIV):
1151 1152 tag = 'object'
1153 1154
1155 -class SELECT(INPUT):
1156 1157 """ 1158 example:: 1159 1160 >>> SELECT('yes', 'no', _name='selector', value='yes', 1161 ... requires=IS_IN_SET(['yes', 'no'])).xml() 1162 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>' 1163 1164 """ 1165 1166 tag = 'select' 1167
1168 - def _fixup(self):
1169 components = [] 1170 for c in self.components: 1171 if isinstance(c, OPTION): 1172 components.append(c) 1173 else: 1174 components.append(OPTION(c, _value=str(c))) 1175 self.components = components
1176
1177 - def _postprocessing(self):
1178 if self['value'] != None: 1179 if not self['_multiple']: 1180 for c in self.components: 1181 if self['value'] and str(c['_value'])\ 1182 == str(self['value']): 1183 c['_selected'] = 'selected' 1184 else: 1185 c['_selected'] = None 1186 else: 1187 values = re.compile('[\w\-:]+').findall(str(self['value'])) 1188 for c in self.components: 1189 if self['value'] and str(c['_value']) in values: 1190 c['_selected'] = 'selected' 1191 else: 1192 c['_selected'] = None
1193 1194
1195 -class FIELDSET(DIV):
1196 1197 tag = 'fieldset'
1198 1199
1200 -class LEGEND(DIV):
1201 1202 tag = 'legend'
1203 1204
1205 -class FORM(DIV):
1206 1207 """ 1208 example:: 1209 1210 >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) 1211 >>> form.xml() 1212 '<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>' 1213 1214 a FORM is container for INPUT, TEXTAREA, SELECT and other helpers 1215 1216 form has one important method:: 1217 1218 form.accepts(request.vars, session) 1219 1220 if form is accepted (and all validators pass) form.vars contains the 1221 accepted vars, otherwise form.errors contains the errors. 1222 in case of errors the form is modified to present the errors to the user. 1223 """ 1224 1225 tag = 'form' 1226
1227 - def __init__(self, *components, **attributes):
1228 if self.tag[-1:] == '/' and components: 1229 raise SyntaxError, '<%s> tags cannot have components' % self.tag 1230 if len(components) == 1 and isinstance(components[0], (list, 1231 tuple)): 1232 self.components = list(components[0]) 1233 else: 1234 self.components = list(components) 1235 self.attributes = attributes 1236 self._fixup() 1237 # converts special attributes in components attributes 1238 self._postprocessing() 1239 self.vars = Storage() 1240 self.errors = Storage() 1241 self.latest = Storage()
1242
1243 - def accepts( 1244 self, 1245 vars, 1246 session=None, 1247 formname='default', 1248 keepvalues=False, 1249 onvalidation=None, 1250 ):
1251 self.errors.clear() 1252 self.request_vars = Storage() 1253 self.request_vars.update(vars) 1254 self.session = session 1255 self.formname = formname 1256 self.keepvalues = keepvalues 1257 1258 # if this tag is a form and we are in accepting mode (status=True) 1259 # check formname and formkey 1260 1261 status = True 1262 if self.session and self.session.get('_formkey[%s]' 1263 % self.formname, None) != self.request_vars._formkey: 1264 status = False 1265 if self.formname != self.request_vars._formname: 1266 status = False 1267 status = self._traverse(status) 1268 if status and onvalidation: 1269 onvalidation(self) 1270 if self.errors: 1271 status = False 1272 if session != None: 1273 self.formkey = session['_formkey[%s]' % formname] = \ 1274 str(uuid.uuid4()) 1275 if status and not keepvalues: 1276 self._traverse(False) 1277 return status
1278
1279 - def _postprocessing(self):
1280 if not '_action' in self.attributes: 1281 self['_action'] = '' 1282 if not '_method' in self.attributes: 1283 self['_method'] = 'post' 1284 if not '_enctype' in self.attributes: 1285 self['_enctype'] = 'multipart/form-data'
1286
1287 - def hidden_fields(self):
1288 c = [] 1289 if 'hidden' in self.attributes: 1290 for (key, value) in self.attributes.get('hidden', 1291 {}).items(): 1292 c.append(INPUT(_type='hidden', _name=key, _value=value)) 1293 if hasattr(self, 'formkey') and self.formkey: 1294 c.append(INPUT(_type='hidden', _name='_formkey', 1295 _value=self.formkey)) 1296 if hasattr(self, 'formname') and self.formname: 1297 c.append(INPUT(_type='hidden', _name='_formname', 1298 _value=self.formname)) 1299 return DIV(c, _class="hidden")
1300
1301 - def xml(self):
1302 newform = FORM(*self.components, **self.attributes) 1303 hidden_fields = self.hidden_fields() 1304 if hidden_fields.components: 1305 newform.append(hidden_fields) 1306 return DIV.xml(newform)
1307 1308
1309 -class BEAUTIFY(DIV):
1310 1311 """ 1312 example:: 1313 1314 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() 1315 '<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;"><div>hello</div></td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' 1316 1317 turns any list, dictionary, etc into decent looking html. 1318 """ 1319 1320 tag = 'div' 1321
1322 - def __init__(self, component, **attributes):
1323 self.components = [component] 1324 self.attributes = attributes 1325 components = [] 1326 attributes = copy.copy(self.attributes) 1327 if '_class' in attributes: 1328 attributes['_class'] += 'i' 1329 for c in self.components: 1330 s = dir(c) # this really has to be fixed!!!! 1331 if 'xml' in s: # assume c has a .xml() 1332 components.append(c) 1333 continue 1334 elif 'keys' in s: 1335 rows = [] 1336 try: 1337 for key in sorted(c): 1338 if str(key)[:1] == '_': 1339 continue 1340 value = c[key] 1341 if type(value) == types.LambdaType: 1342 continue 1343 rows.append(TR(TD(BEAUTIFY(key, 1344 **attributes), _style='font-weight:bold;'), TD(':', 1345 _valign='top'), TD(BEAUTIFY(value, 1346 **attributes)))) 1347 components.append(TABLE(*rows, **attributes)) 1348 continue 1349 except: 1350 pass 1351 if isinstance(c, str): 1352 components.append(str(c)) 1353 elif isinstance(c, unicode): 1354 components.append(c.encode('utf8')) 1355 elif isinstance(c, (list, tuple)): 1356 items = [TR(TD(BEAUTIFY(item, **attributes))) 1357 for item in c] 1358 components.append(TABLE(*items, **attributes)) 1359 elif isinstance(c, cgi.FieldStorage): 1360 components.append('FieldStorage object') 1361 else: 1362 components.append(repr(c)) 1363 self.components = components
1364 1365 1418 1419
1420 -def embed64( 1421 filename = None, 1422 file = None, 1423 data = None, 1424 extension = 'image/gif', 1425 ):
1426 """ 1427 helper to encode the provided (binary) data into base64. 1428 1429 :param filename: if provided, opens and reads this file in 'rb' mode 1430 :param file: if provided, reads this file 1431 :param data: if provided, uses the provided data 1432 """ 1433 1434 if filename and os.path.exists(file): 1435 fp = open(filename, 'rb') 1436 data = fp.read() 1437 fp.close() 1438 data = base64.b64encode(data) 1439 return 'data:%s;base64,%s' % (extension, data)
1440 1441
1442 -def test():
1443 """ 1444 Example: 1445 1446 >>> from validators import * 1447 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() 1448 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div> 1449 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() 1450 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div> 1451 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() 1452 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div> 1453 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml() 1454 <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> 1455 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10'))) 1456 >>> print form.xml() 1457 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form> 1458 >>> print form.accepts({'myvar':'34'}, formname=None) 1459 False 1460 >>> print form.xml() 1461 <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> 1462 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) 1463 True 1464 >>> print form.xml() 1465 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form> 1466 >>> form=FORM(SELECT('cat', 'dog', _name='myvar')) 1467 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True) 1468 True 1469 >>> print form.xml() 1470 <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> 1471 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!'))) 1472 >>> print form.accepts({'myvar':'as df'}, formname=None) 1473 False 1474 >>> print form.xml() 1475 <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> 1476 >>> session={} 1477 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) 1478 >>> if form.accepts({}, session,formname=None): print 'passed' 1479 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed' 1480 """ 1481 1482 pass
1483 1484 1485 if __name__ == '__main__': 1486 import doctest 1487 doctest.testmod() 1488