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

Source Code for Module gluon.html

  1  """ 
  2  This file is part of web2py Web Framework (Copyrighted, 2007) 
  3  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  4  License: GPL v2 
  5  """ 
  6   
  7  import cgi, re, random, copy, sys, types, urllib, tokenize, keyword, base64 
  8  from storage import Storage 
  9  from validators import * 
 10  from highlight import highlight 
 11  import sanitizer 
 12   
 13  __all__=['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CENTER', 'CODE', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'IFRAME', 'IMG', 'INPUT', 'LABEL', 'LI', 'LINK', 'OL', 'UL', 'META', 'OBJECT', 'ON', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'STYLE', 'TABLE', 'TD', 'TEXTAREA', 'TH', 'TITLE', 'TR', 'TT', 'URL', 'XML', 'xmlescape', 'embed64'] 
 14   
15 -def xmlescape(data,quote=False):
16 try: 17 data=data.xml() 18 except AttributeError: 19 if not isinstance(data,(str,unicode)): data=str(data) 20 if isinstance(data,unicode): data=data.encode("utf8","xmlcharrefreplace") 21 data=cgi.escape(data,quote) 22 return data
23
24 -def URL(a=None,c=None,f=None,r=None,args=[],vars={}):
25 """ 26 example: 27 28 >>> URL(a='a',c='c',f='f',args=['x','y','z'],vars={'p':1, 'q':2}) 29 '/a/c/f/x/y/z?q=2&p=1' 30 31 generates a url "/a/c/f" corresponding to application a, controller c 32 and function f. If r=request is passed, a,c,f are set, respectively, 33 to r.applicaiton, r.controller, r.function. 34 35 The more typical usage is: 36 37 URL(r=request,f='index') that generates a url for the index function 38 within the present application and controller. 39 """ 40 application=controller=function=None 41 if r: 42 application=r.application 43 controller=r.controller 44 function=r.function 45 if a: application=a 46 if c: controller=c 47 if f: 48 if isinstance(f,str): function=f 49 else: function=f.__name__ 50 if not (application and controller and function): 51 raise SyntaxError, 'not enough information to build the url' 52 other='' 53 if args: other='/'+'/'.join([str(x) for x in args]) 54 if vars: other=other+'?'+urllib.urlencode(vars) 55 url='/%s/%s/%s%s' % (application, controller, function, other) 56 return url
57 58 ON=None 59
60 -class XML(object):
61 """ 62 example: 63 64 >>> XML('<h1>Hello</h1>').xml() 65 '<h1>Hello</h1>' 66 67 use it to wrap a string that contains XML/HTML so that it will not be 68 escaped by the template 69 """
70 - def __init__(self,text,sanitize=False,permitted_tags=['a','b','blockquote','br/','i', 'li', 'ol','ul', 'p', 'cite','code','pre','img/'],allowed_attributes={'a':['href','title'],'img':['src','alt'],'blockquote':['type']}):
71 if sanitize: text=sanitizer.sanitize(text,permitted_tags,allowed_attributes) 72 self.text=text
73 - def xml(self):
74 return self.text
75 - def __str__(self):
76 return self.xml()
77
78 -class DIV(object):
79 """ 80 example: 81 82 >>> DIV('hello','world',_style='color:red;').xml() 83 '<div style="color:red;">helloworld</div>' 84 85 all other HTML helpers are derived from DIV. 86 _something="value" attributes are transparently translated into 87 something="value" HTML attributes 88 """ 89 tag='div'
90 - def __init__(self,*components,**attributes):
91 if self.tag[-1]=='/' and components: 92 raise SyntaxError, '<%s> tags cannot have components' % self.tag 93 self.components=list(components) 94 self.attributes=attributes 95 self.postprocessing() 96 self.errors=Storage() 97 self.vars=Storage() 98 self.session=None 99 self.formname=None
100 - def postprocessing(self):
101 return
102 - def rec_clear(self,clear_attributes_value=False):
103 if hasattr(self,'attributes'): 104 if clear_attributes_value: 105 if self.attributes.has_key('default'): 106 self.attributes['value']=self.attributes['default'] 107 else: 108 self.attributes['value']='' 109 self.postprocessing() 110 if self.attributes.has_key('value'): 111 self.attributes['default']=self.attributes['value'] 112 for c in self.components: 113 if hasattr(c,'rec_clear'): 114 c.errors=self.errors 115 c.vars=self.vars 116 c.session=self.session 117 c.formname=self.formname 118 c.rec_clear(clear_attributes_value)
119 - def accepts(self,vars,session=None,formname='default',keepvalues=False):
120 self.errors=Storage() 121 self.session=session 122 self.formname=formname 123 self.rec_clear() 124 form_key='_form_key[%s]' % formname 125 if session!=None and session.has_key(form_key): 126 form_key_value=session[form_key] 127 del session[form_key] 128 if not vars.has_key('_form_key') or \ 129 vars['_form_key']!=form_key_value: 130 return False 131 if formname and formname!=vars._formname: return False 132 self.rec_accepts(vars) 133 if not len(self.errors) and not keepvalues: self.rec_clear(True) 134 return len(self.errors)==0
135 - def rec_accepts(self,vars):
136 for c in self.components: 137 if hasattr(c,'rec_accepts'): c.rec_accepts(vars)
138 - def _xml(self):
139 items=self.attributes.items() 140 fa=' '.join([key[1:].lower() for key,value in items if key[:1]=='_' and value==None]+['%s="%s"' % (key[1:].lower(),xmlescape(value,True)) for key,value in self.attributes.items() if key[:1]=='_' and value]) 141 if fa: fa=' '+fa 142 co=''.join([xmlescape(component) for component in self.components]) 143 return fa,co
144 - def xml(self):
145 fa,co=self._xml() 146 if self.tag[-1]=='/': return '<%s%s/>' % (self.tag[:-1],fa) 147 return '<%s%s>%s</%s>' % (self.tag,fa,co,self.tag)
148 - def __str__(self):
149 return self.xml()
150
151 -class HTML(DIV):
152 tag='html'
153 - def xml(self):
154 fa,co=self._xml() 155 return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<%s%s>%s</%s>' % (self.tag,fa,co,self.tag)
156
157 -class HEAD(DIV): tag='head'
158
159 -class TITLE(DIV): tag='title'
160
161 -class META(DIV): tag='meta'
162 164
165 -class SCRIPT(DIV):
166 """ 167 """ 168 tag='script'
169 - def xml(self):
170 fa,co=self._xml() 171 if co: return '<%s%s><!--\n%s\n//--></%s>' % (self.tag,fa,co,self.tag) 172 else: return DIV.xml(self)
173
174 -class STYLE(SCRIPT):
175 """ 176 """ 177 tag='style'
178
179 -class IMG(DIV): tag='img/'
180
181 -class SPAN(DIV): tag='span'
182
183 -class BODY(DIV): tag='body'
184
185 -class H1(DIV): tag='h1'
186
187 -class H2(DIV): tag='h2'
188
189 -class H3(DIV): tag='h3'
190
191 -class H4(DIV): tag='h4'
192
193 -class H5(DIV): tag='h5'
194
195 -class H6(DIV): tag='h6'
196
197 -class P(DIV):
198 tag='p'
199 - def xml(self):
200 text=DIV.xml(self) 201 if self.attributes.has_key('cr2br') and self.attributes['cr2br']: 202 text=text.replace('\n','<br/>') 203 return text
204
205 -class B(DIV): tag='B'
206
207 -class BR(DIV): tag='br/'
208
209 -class HR(DIV): tag='hr/'
210
211 -class A(DIV): tag='a'
212
213 -class EM(DIV): tag='em'
214
215 -class EMBED(DIV): tag='embed/'
216
217 -class TT(DIV): tag='tt'
218
219 -class PRE(DIV): tag='pre'
220
221 -class CENTER(DIV): tag='center'
222
223 -class CODE(DIV):
224 """ 225 displays code in HTML with syntax highlighting. Exmaple: 226 227 {{=CODE("print 'hello world'",language='python',link=None,counter=1,styles={})}} 228 229 supported languages are "python", "html_plain", "c", "cpp", "web2py", "html". 230 The "html" language interprets {{ and }} tags as "web2py" code, "html_plain" doesn't. 231 232 if a link='/exmaples/global/vars/' is provided web2py keywords are linked to the online docs. 233 the counter is used for line numbering, counter can be None or a prompt string. 234 """
235 - def xml(self):
236 if not self.attributes.has_key('language'): language='PYTHON' 237 else: language=self.attributes['language'] 238 if not self.attributes.has_key('link'): link=None 239 else: link=self.attributes['link'] 240 241 if not self.attributes.has_key('counter'): counter=1 242 else: counter=self.attributes['counter'] 243 if not self.attributes.has_key('styles'): styles={} 244 else: styles=self.attributes['styles'] 245 return highlight(''.join(self.components),language=language,link=link,counter=counter,styles=styles,attributes=self.attributes)
246
247 -class LABEL(DIV): tag='label'
248
249 -class LI(DIV): tag='li'
250
251 -class UL(DIV):
252 tag='ul'
253 - def postprocessing(self):
254 components=[] 255 for c in self.components: 256 if isinstance(c,LI): 257 components.append(c) 258 else: 259 components.append(LI(c)) 260 self.components=components 261
262 -class OL(UL): tag='ol'
263
264 -class TD(DIV): tag='td'
265
266 -class TH(DIV): tag='th'
267
268 -class TR(DIV):
269 tag='tr'
270 - def postprocessing(self):
271 components=[] 272 for c in self.components: 273 if isinstance(c, (TD, TH)): 274 components.append(c) 275 else: 276 components.append(TD(c)) 277 self.components=components
278
279 -class TABLE(DIV):
280 tag='table'
281 - def postprocessing(self):
282 components=[] 283 for c in self.components: 284 if isinstance(c,TR): 285 components.append(c) 286 else: 287 components.append(TR(*c)) 288 self.components=components
289
290 -class IFRAME(DIV): tag='iframe'
291
292 -class INPUT(DIV):
293 """ 294 examples: 295 296 >>> INPUT(_type='text',_name='name',value='Max').xml() 297 '<input value="Max" type="text" name="name"/>' 298 >>> INPUT(_type='checkbox',_name='checkbox',value='on').xml() 299 '<input checked type="checkbox" name="checkbox"/>' 300 >>> INPUT(_type='radio',_name='radio',_value='yes',value='yes').xml() 301 '<input checked value="yes" type="radio" name="radio"/>' 302 >>> INPUT(_type='radio',_name='radio',_value='no',value='yes').xml() 303 '<input value="no" type="radio" name="radio"/>' 304 305 the input helper takes two special attributes value= and requires=. 306 307 value is used to pass the initial value for the input field. 308 value differs from _value because it works for checkboxes, radio, 309 textarea and select/option too. 310 for a checkbox value should be '' or 'on'. 311 for a radio or select/option value should be the _value 312 of the checked/selected item. 313 314 requres should be None, or a validator or a list of validators for the 315 value of the field. 316 """ 317 tag='input/'
318 - def postprocessing(self):
319 if self.attributes.has_key('_type') and \ 320 self.attributes.has_key('value') and self.attributes['value']!=None: 321 if self.attributes['_type'].lower()=='checkbox': 322 if self.attributes['value']: self.attributes['_checked']=ON 323 elif self.attributes.has_key('_checked'): 324 del self.attributes['_checked'] 325 elif self.attributes['_type'].lower()=='radio' and \ 326 self.attributes.has_key('_value'): 327 if self.attributes['value']==self.attributes['_value']: 328 self.attributes['_checked']=ON 329 elif self.attributes.has_key('_checked'): 330 del self.attributes['_checked'] 331 elif self.attributes['_type']=='text': 332 self.attributes['_value']=self.attributes['value'] 333 elif not self.attributes.has_key('_type') and \ 334 self.attributes.has_key('value') and self.attributes['value']!=None: 335 self.attributes['_value']=self.attributes['value']
336 - def rec_accepts(self,vars):
337 if not self.attributes.has_key('_name'): return True 338 name=self.attributes['_name'] 339 if vars.has_key(name): value=vars[name] 340 elif self.attributes.has_key('value') and \ 341 self.attributes['value']!=None: value=self.attributes['value'] 342 else: value='' 343 if isinstance(value,(str,unicode)): self.attributes['value']=value 344 self.postprocessing() 345 if isinstance(value,cgi.FieldStorage): self.attributes['value']=value 346 else: self.attributes['value']=str(value) 347 if self.attributes.has_key('requires'): 348 requires=self.attributes['requires'] 349 if not isinstance(requires,(list,tuple)): requires=[requires] 350 for validator in requires: 351 value,errors=validator(value) 352 self.vars[name]=value 353 if errors!=None: 354 self.errors[name]=errors 355 return False 356 self.vars[name]=value 357 self.postprocessing() 358 return True
359 - def xml(self):
360 try: 361 name=self.attributes['_name'] 362 return DIV.xml(self)+DIV(self.errors[name],\ 363 _class='error',errors=None).xml() 364 except: return DIV.xml(self)
365
366 -class TEXTAREA(INPUT):
367 """ 368 TEXTAREA(_name='sometext',value='bla '*100,requires=IS_NOT_EMPTY()) 369 'bla bla bla ...' will be the content of the textarea field. 370 """ 371 tag='textarea'
372 - def postprocessing(self):
373 if not self.attributes.has_key('_rows'): 374 self.attributes['_rows']=10 375 if not self.attributes.has_key('_cols'): 376 self.attributes['_cols']=40 377 if self.attributes.has_key('value'): 378 if self.attributes['value']!=None: 379 self.components=[self.attributes['value']] 380 else: 381 self.components=[]
382
383 -class OPTION(DIV): tag='option'
384
385 -class OBJECT(DIV): tag='object'
386
387 -class SELECT(INPUT):
388 """ 389 example: 390 391 >>> SELECT('yes','no',_name='selector',value='yes',requires=IS_IN_SET(['yes','no'])).xml() 392 '<select name="selector"><option selected value="yes">yes</option><option value="no">no</option></select>' 393 """ 394 tag='select'
395 - def postprocessing(self):
396 components=[] 397 for c in self.components: 398 if isinstance(c,OPTION): 399 components.append(c) 400 else: 401 components.append(OPTION(c,_value=str(c))) 402 if self.attributes.has_key('value') and \ 403 self.attributes['value']!=None and \ 404 self.attributes['value']==components[-1].attributes['_value']: 405 components[-1].attributes['_selected']=ON 406 self.components=components
407
408 -class FIELDSET(DIV): tag='fieldset'
409
410 -class FORM(DIV):
411 """ 412 example: 413 414 >>> form=FORM(INPUT(_name="test",requires=IS_NOT_EMPTY())) 415 >>> form.xml() 416 '<form enctype="multipart/form-data" method="post"><input name="test"/></form>' 417 418 a FORM is container for INPUT, TEXTAREA, SELECT and other helpers 419 420 form has one important method: 421 422 form.accepts(request.vars, session) 423 424 if form is accepted (and all validators pass) form.vars containes the 425 accepted vars, otherwise form.errors contains the errors. 426 in case of errors the form is modified to present the errors to the user. 427 """ 428 tag='form'
429 - def postprocessing(self):
430 if not self.attributes.has_key('_action'): self.attributes['_action']="" 431 if not self.attributes.has_key('_method'): self.attributes['_method']="post" 432 if not self.attributes.has_key('_enctype'): self.attributes['_enctype']="multipart/form-data"
433 - def xml(self):
434 if self.session!=None: 435 try: 436 if self.components[-1].attributes['_name']=='_form_key': 437 self.components=self.components[:-1] 438 except: pass 439 form_key='_form_key[%s]' % self.formname 440 key=self.session[form_key]=str(random.random())[2:] 441 self.components.append(INPUT(_type='hidden', 442 _name='_form_key',_value=key)) 443 if self.formname!=None: 444 self.components.append(INPUT(_type='hidden', 445 _name='_formname',_value=self.formname)) 446 if self.attributes.has_key('hidden'): 447 hidden=self.attributes['hidden'] 448 for key,value in hidden.items(): 449 self.components.append(INPUT(_type='hidden', 450 _name=key,_value=value)) 451 return DIV.xml(self) 452
453 -class BEAUTIFY(DIV):
454 """ 455 example: 456 457 >>> BEAUTIFY(['a','b',{'hello':'world'}]).xml() 458 '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td><B><div>hello</div></B></td><td align="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' 459 460 turns any list, dictionarie, etc into decent looking html. 461 """ 462 tag='div'
463 - def postprocessing(self):
464 components=[] 465 attributes=copy.copy(self.attributes) 466 if attributes.has_key('_class'): attributes['_class']+='i' 467 for c in self.components: 468 t=type(c) 469 s=dir(c) # this really has to be fixed!!!! 470 if 'xml' in s: # assume c has a .xml() 471 components.append(c) 472 continue 473 elif 'keys' in s: 474 rows=[] 475 try: 476 keys=c.keys() 477 keys.sort() 478 for key in keys: 479 if str(key)[:1]=='_': continue 480 value=c[key] 481 if type(value)==types.LambdaType: continue 482 rows.append(TR(TD(B(BEAUTIFY(key,**attributes))), 483 TD(':',_align="top"), 484 TD(BEAUTIFY(value,**attributes)))) 485 components.append(TABLE(*rows,**attributes)) 486 continue 487 except: pass 488 if isinstance(c,(list,tuple)): 489 items=[TR(TD(BEAUTIFY(item,**attributes))) for item in c] 490 components.append(TABLE(*items,**attributes)) 491 continue 492 elif isinstance(c,str): components.append(str(c)) 493 elif isinstance(c,unicode): components.append(c.encode('utf8')) 494 else: components.append(repr(c)) 495 self.components=components
496
497 -def embed64(filename=None,file=None,data=None,extension='image/gif'):
498 if filename: file=open(filename,'rb') 499 if file: data=file.read() 500 data=base64.b64encode(data) 501 return 'data:%s;base64,%s'%(extension,data)
502
503 -def test():
504 """ 505 Example: 506 507 >>> from validators import * 508 >>> print DIV(A('click me',_href=URL(a='a',c='b',f='c')),BR(),HR(),DIV(SPAN("World"),_class='unkown')).xml() 509 <div><a href="/a/b/c">click me</a><br/><hr/><div class="unkown"><span>World</span></div></div> 510 >>> print DIV(UL("doc","cat","mouse")).xml() 511 <div><lu><li>doc</li><li>cat</li><li>mouse</li></lu></div> 512 >>> print DIV(UL("doc",LI("cat", _class='felin'),18)).xml() 513 <div><lu><li>doc</li><li class="felin">cat</li><li>18</li></lu></div> 514 >>> print TABLE(['a','b','c'],TR('d','e','f'),TR(TD(1),TD(2),TD(3))).xml() 515 <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> 516 >>> form=FORM(INPUT(_type='text',_name='myvar',requires=IS_EXPR('int(value)<10'))) 517 >>> print form.xml() 518 <form enctype="multipart/form-data" method="post"><input type="text" name="myvar"/></form> 519 >>> print form.accepts({'myvar':'34'},formname=None) 520 False 521 >>> print form.xml() 522 <form enctype="multipart/form-data" method="post"><input value="34" type="text" name="myvar"/><div class="error">invalid expression!</div></form> 523 >>> print form.accepts({'myvar':'4'},formname=None,keepvalues=True) 524 True 525 >>> print form.xml() 526 <form enctype="multipart/form-data" method="post"><input value="4" type="text" name="myvar"/></form> 527 >>> form=FORM(SELECT('cat','dog',_name='myvar')) 528 >>> print form.accepts({'myvar':'dog'},formname=None) 529 True 530 >>> print form.xml() 531 <form enctype="multipart/form-data" method="post"><select name="myvar"><option value="cat">cat</option><option selected value="dog">dog</option></select></form> 532 >>> form=FORM(INPUT(_type='text',_name='myvar',requires=IS_MATCH('^\w+$','only alphanumeric!'))) 533 >>> print form.accepts({'myvar':'as df'},formname=None) 534 False 535 >>> print form.xml() 536 <form enctype="multipart/form-data" method="post"><input value="as df" type="text" name="myvar"/><div class="error">only alphanumeric!</div></form> 537 538 >>> session={} 539 >>> form=FORM(INPUT(value="Hello World",_name="var",requires=IS_MATCH('^\w+$'))) 540 >>> if form.accepts({},session,formname=None): print 'passed' 541 >>> tmp=form.xml() # form has to be generated or _form_key is not stored 542 >>> if form.accepts({'var':'test ','_form_key':session['_form_key[None]']},session,formname=None): print 'passed' 543 """ 544 pass
545 546 547 if __name__=='__main__': 548 import doctest 549 doctest.testmod() 550 551 ''' 552 form=FORM(TABLE(TR('Name:',INPUT(_type='text',_name='name'),BR()), 553 TR('Password:',INPUT(_type='password',_name='password')))) 554 vars={} 555 session={} 556 if form.accepts(vars,session): 557 print 'accepted' 558 print 'not accepted' 559 print form.xml() 560 print form.session 561 vars['_form_key']=form.session['_form_key'] 562 vars['name']='Massimo' 563 vars['password']='Dip' 564 if form.accepts(vars,session): 565 print 'accepted' 566 ''' 567