1
2
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 cPickle
23 import marshal
24 from HTMLParser import HTMLParser
25 from htmlentitydefs import name2codepoint
26 from contrib.markmin.markmin2html import render
27
28 from storage import Storage
29 from highlight import highlight
30 from utils import web2py_uuid, hmac_hash
31
32 regex_crlf = re.compile('\r|\n')
33
34 join = ''.join
35
36 __all__ = [
37 'A',
38 'B',
39 'BEAUTIFY',
40 'BODY',
41 'BR',
42 'BUTTON',
43 'CENTER',
44 'CAT',
45 'CODE',
46 'COL',
47 'COLGROUP',
48 'DIV',
49 'EM',
50 'EMBED',
51 'FIELDSET',
52 'FORM',
53 'H1',
54 'H2',
55 'H3',
56 'H4',
57 'H5',
58 'H6',
59 'HEAD',
60 'HR',
61 'HTML',
62 'I',
63 'IFRAME',
64 'IMG',
65 'INPUT',
66 'LABEL',
67 'LEGEND',
68 'LI',
69 'LINK',
70 'OL',
71 'UL',
72 'MARKMIN',
73 'MENU',
74 'META',
75 'OBJECT',
76 'ON',
77 'OPTION',
78 'P',
79 'PRE',
80 'SCRIPT',
81 'OPTGROUP',
82 'SELECT',
83 'SPAN',
84 'STYLE',
85 'TABLE',
86 'TAG',
87 'TD',
88 'TEXTAREA',
89 'TH',
90 'THEAD',
91 'TBODY',
92 'TFOOT',
93 'TITLE',
94 'TR',
95 'TT',
96 'URL',
97 'XHTML',
98 'XML',
99 'xmlescape',
100 'embed64',
101 ]
102
103
105 """
106 returns an escaped string of the provided data
107
108 :param data: the data to be escaped
109 :param quote: optional (default False)
110 """
111
112
113 if hasattr(data,'xml') and callable(data.xml):
114 return data.xml()
115
116
117 if not isinstance(data, (str, unicode)):
118 data = str(data)
119 elif isinstance(data, unicode):
120 data = data.encode('utf8', 'xmlcharrefreplace')
121
122
123 data = cgi.escape(data, quote).replace("'","'")
124 return data
125
126
127 -def URL(
128 a=None,
129 c=None,
130 f=None,
131 r=None,
132 args=None,
133 vars=None,
134 anchor='',
135 extension=None,
136 env=None,
137 hmac_key=None,
138 hash_vars=True,
139 salt=None,
140 user_signature=None,
141 scheme=None,
142 host=None,
143 port=None,
144 encode_embedded_slash=False,
145 ):
146 """
147 generate a URL
148
149 example::
150
151 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
152 ... vars={'p':1, 'q':2}, anchor='1'))
153 '/a/c/f/x/y/z?p=1&q=2#1'
154
155 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
156 ... vars={'p':(1,3), 'q':2}, anchor='1'))
157 '/a/c/f/x/y/z?p=1&p=3&q=2#1'
158
159 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
160 ... vars={'p':(3,1), 'q':2}, anchor='1'))
161 '/a/c/f/x/y/z?p=3&p=1&q=2#1'
162
163 >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
164 '/a/c/f#1%2B2'
165
166 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
167 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
168 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1'
169
170 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z']))
171 '/a/c/f/w/x/y/z'
172
173 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z'], encode_embedded_slash=True))
174 '/a/c/f/w%2Fx/y%2Fz'
175
176 generates a url '/a/c/f' corresponding to application a, controller c
177 and function f. If r=request is passed, a, c, f are set, respectively,
178 to r.application, r.controller, r.function.
179
180 The more typical usage is:
181
182 URL(r=request, f='index') that generates a url for the index function
183 within the present application and controller.
184
185 :param a: application (default to current if r is given)
186 :param c: controller (default to current if r is given)
187 :param f: function (default to current if r is given)
188 :param r: request (optional)
189 :param args: any arguments (optional)
190 :param vars: any variables (optional)
191 :param anchor: anchorname, without # (optional)
192 :param hmac_key: key to use when generating hmac signature (optional)
193 :param hash_vars: which of the vars to include in our hmac signature
194 True (default) - hash all vars, False - hash none of the vars,
195 iterable - hash only the included vars ['key1','key2']
196 :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
197 :param host: string to force absolute URL with host (True means http_host)
198 :param port: optional port number (forces absolute URL)
199
200 :raises SyntaxError: when no application, controller or function is
201 available
202 :raises SyntaxError: when a CRLF is found in the generated url
203 """
204
205 if args in (None,[]): args = []
206 vars = vars or {}
207 application = None
208 controller = None
209 function = None
210
211 if not r:
212 if a and not c and not f: (f,a,c)=(a,c,f)
213 elif a and c and not f: (c,f,a)=(a,c,f)
214 from globals import current
215 if hasattr(current,'request'):
216 r = current.request
217 if r:
218 application = r.application
219 controller = r.controller
220 function = r.function
221 env = r.env
222 if extension is None and r.extension != 'html':
223 extension = r.extension
224 if a:
225 application = a
226 if c:
227 controller = c
228 if f:
229 if not isinstance(f, str):
230 if hasattr(f,'__name__'):
231 function = f.__name__
232 else:
233 raise SyntaxError, 'when calling URL, function or function name required'
234 elif '.' in f:
235 function, extension = f.split('.', 1)
236 else:
237 function = f
238
239 function2 = '%s.%s' % (function,extension or 'html')
240
241 if not (application and controller and function):
242 raise SyntaxError, 'not enough information to build the url'
243
244 if not isinstance(args, (list, tuple)):
245 args = [args]
246
247 if args:
248 if encode_embedded_slash:
249 other = '/' + '/'.join([urllib.quote(str(x), '') for x in args])
250 else:
251 other = args and urllib.quote('/' + '/'.join([str(x) for x in args]))
252 else:
253 other = ''
254
255 if other.endswith('/'):
256 other += '/'
257
258 if vars.has_key('_signature'): vars.pop('_signature')
259 list_vars = []
260 for (key, vals) in sorted(vars.items()):
261 if not isinstance(vals, (list, tuple)):
262 vals = [vals]
263 for val in vals:
264 list_vars.append((key, val))
265
266 if user_signature:
267 from globals import current
268 if current.session.auth:
269 hmac_key = current.session.auth.hmac_key
270
271 if hmac_key:
272
273
274
275 h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
276
277
278 if hash_vars is True:
279 h_vars = list_vars
280 elif hash_vars is False:
281 h_vars = ''
282 else:
283 if hash_vars and not isinstance(hash_vars, (list, tuple)):
284 hash_vars = [hash_vars]
285 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
286
287
288 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
289
290 sig = hmac_hash(message, hmac_key, digest_alg='sha1', salt=salt)
291
292 list_vars.append(('_signature', sig))
293
294 if list_vars:
295 other += '?%s' % urllib.urlencode(list_vars)
296 if anchor:
297 other += '#' + urllib.quote(str(anchor))
298 if extension:
299 function += '.' + extension
300
301 if regex_crlf.search(join([application, controller, function, other])):
302 raise SyntaxError, 'CRLF Injection Detected'
303 url = rewrite.url_out(r, env, application, controller, function,
304 args, other, scheme, host, port)
305 return url
306
307
308 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
309 """
310 Verifies that a request's args & vars have not been tampered with by the user
311
312 :param request: web2py's request object
313 :param hmac_key: the key to authenticate with, must be the same one previously
314 used when calling URL()
315 :param hash_vars: which vars to include in our hashing. (Optional)
316 Only uses the 1st value currently
317 True (or undefined) means all, False none,
318 an iterable just the specified keys
319
320 do not call directly. Use instead:
321
322 URL.verify(hmac_key='...')
323
324 the key has to match the one used to generate the URL.
325
326 >>> r = Storage()
327 >>> gv = Storage(p=(1,3),q=2,_signature='a32530f0d0caa80964bb92aad2bedf8a4486a31f')
328 >>> r.update(dict(application='a', controller='c', function='f', extension='html'))
329 >>> r['args'] = ['x', 'y', 'z']
330 >>> r['get_vars'] = gv
331 >>> verifyURL(r, 'key')
332 True
333 >>> verifyURL(r, 'kay')
334 False
335 >>> r.get_vars.p = (3, 1)
336 >>> verifyURL(r, 'key')
337 True
338 >>> r.get_vars.p = (3, 2)
339 >>> verifyURL(r, 'key')
340 False
341
342 """
343
344 if not request.get_vars.has_key('_signature'):
345 return False
346
347
348 if user_signature:
349 from globals import current
350 if not current.session or not current.session.auth:
351 return False
352 hmac_key = current.session.auth.hmac_key
353 if not hmac_key:
354 return False
355
356
357 original_sig = request.get_vars._signature
358
359
360 vars, args = request.get_vars, request.args
361
362
363 request.get_vars.pop('_signature')
364
365
366
367
368 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
369 h_args = '/%s/%s/%s.%s%s' % (request.application,
370 request.controller,
371 request.function,
372 request.extension,
373 other)
374
375
376
377
378 list_vars = []
379 for (key, vals) in sorted(vars.items()):
380 if not isinstance(vals, (list, tuple)):
381 vals = [vals]
382 for val in vals:
383 list_vars.append((key, val))
384
385
386 if hash_vars is True:
387 h_vars = list_vars
388 elif hash_vars is False:
389 h_vars = ''
390 else:
391
392 try:
393 if hash_vars and not isinstance(hash_vars, (list, tuple)):
394 hash_vars = [hash_vars]
395 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
396 except:
397
398 return False
399
400 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
401
402
403 sig = hmac_hash(message, str(hmac_key), digest_alg='sha1', salt=salt)
404
405
406
407 request.get_vars['_signature'] = original_sig
408
409
410
411 return original_sig == sig
412
413 URL.verify = verifyURL
414
415 ON = True
416
417
419 """
420 Abstract root for all Html components
421 """
422
423
424
426 raise NotImplementedError
427
428
429 -class XML(XmlComponent):
430 """
431 use it to wrap a string that contains XML/HTML so that it will not be
432 escaped by the template
433
434 example:
435
436 >>> XML('<h1>Hello</h1>').xml()
437 '<h1>Hello</h1>'
438 """
439
440 - def __init__(
441 self,
442 text,
443 sanitize = False,
444 permitted_tags = [
445 'a',
446 'b',
447 'blockquote',
448 'br/',
449 'i',
450 'li',
451 'ol',
452 'ul',
453 'p',
454 'cite',
455 'code',
456 'pre',
457 'img/',
458 'h1','h2','h3','h4','h5','h6',
459 'table','tr','td','div',
460 ],
461 allowed_attributes = {
462 'a': ['href', 'title'],
463 'img': ['src', 'alt'],
464 'blockquote': ['type'],
465 'td': ['colspan'],
466 },
467 ):
468 """
469 :param text: the XML text
470 :param sanitize: sanitize text using the permitted tags and allowed
471 attributes (default False)
472 :param permitted_tags: list of permitted tags (default: simple list of
473 tags)
474 :param allowed_attributes: dictionary of allowed attributed (default
475 for A, IMG and BlockQuote).
476 The key is the tag; the value is a list of allowed attributes.
477 """
478
479 if sanitize:
480 text = sanitizer.sanitize(text, permitted_tags,
481 allowed_attributes)
482 if isinstance(text, unicode):
483 text = text.encode('utf8', 'xmlcharrefreplace')
484 elif not isinstance(text, str):
485 text = str(text)
486 self.text = text
487
490
493
495 return '%s%s' % (self,other)
496
498 return '%s%s' % (other,self)
499
501 return cmp(str(self),str(other))
502
504 return hash(str(self))
505
507 return getattr(str(self),name)
508
511
513 return str(self)[i:j]
514
516 for c in str(self): yield c
517
519 return len(str(self))
520
522 """
523 return the text stored by the XML object rendered by the render function
524 """
525 if render:
526 return render(self.text,None,{})
527 return self.text
528
530 """
531 to be considered experimental since the behavior of this method is questionable
532 another options could be TAG(self.text).elements(*args,**kargs)
533 """
534 return []
535
536
538 return marshal.loads(data)
541 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
542
543
544
545 -class DIV(XmlComponent):
546 """
547 HTML helper, for easy generating and manipulating a DOM structure.
548 Little or no validation is done.
549
550 Behaves like a dictionary regarding updating of attributes.
551 Behaves like a list regarding inserting/appending components.
552
553 example::
554
555 >>> DIV('hello', 'world', _style='color:red;').xml()
556 '<div style=\"color:red;\">helloworld</div>'
557
558 all other HTML helpers are derived from DIV.
559
560 _something=\"value\" attributes are transparently translated into
561 something=\"value\" HTML attributes
562 """
563
564
565
566
567 tag = 'div'
568
569 - def __init__(self, *components, **attributes):
570 """
571 :param *components: any components that should be nested in this element
572 :param **attributes: any attributes you want to give to this element
573
574 :raises SyntaxError: when a stand alone tag receives components
575 """
576
577 if self.tag[-1:] == '/' and components:
578 raise SyntaxError, '<%s> tags cannot have components'\
579 % self.tag
580 if len(components) == 1 and isinstance(components[0], (list,tuple)):
581 self.components = list(components[0])
582 else:
583 self.components = list(components)
584 self.attributes = attributes
585 self._fixup()
586
587 self._postprocessing()
588 self.parent = None
589 for c in self.components:
590 self._setnode(c)
591
593 """
594 dictionary like updating of the tag attributes
595 """
596
597 for (key, value) in kargs.items():
598 self[key] = value
599 return self
600
602 """
603 list style appending of components
604
605 >>> a=DIV()
606 >>> a.append(SPAN('x'))
607 >>> print a
608 <div><span>x</span></div>
609 """
610 self._setnode(value)
611 ret = self.components.append(value)
612 self._fixup()
613 return ret
614
616 """
617 list style inserting of components
618
619 >>> a=DIV()
620 >>> a.insert(0,SPAN('x'))
621 >>> print a
622 <div><span>x</span></div>
623 """
624 self._setnode(value)
625 ret = self.components.insert(i, value)
626 self._fixup()
627 return ret
628
630 """
631 gets attribute with name 'i' or component #i.
632 If attribute 'i' is not found returns None
633
634 :param i: index
635 if i is a string: the name of the attribute
636 otherwise references to number of the component
637 """
638
639 if isinstance(i, str):
640 try:
641 return self.attributes[i]
642 except KeyError:
643 return None
644 else:
645 return self.components[i]
646
648 """
649 sets attribute with name 'i' or component #i.
650
651 :param i: index
652 if i is a string: the name of the attribute
653 otherwise references to number of the component
654 :param value: the new value
655 """
656 self._setnode(value)
657 if isinstance(i, (str, unicode)):
658 self.attributes[i] = value
659 else:
660 self.components[i] = value
661
663 """
664 deletes attribute with name 'i' or component #i.
665
666 :param i: index
667 if i is a string: the name of the attribute
668 otherwise references to number of the component
669 """
670
671 if isinstance(i, str):
672 del self.attributes[i]
673 else:
674 del self.components[i]
675
677 """
678 returns the number of included components
679 """
680 return len(self.components)
681
683 """
684 always return True
685 """
686 return True
687
689 """
690 Handling of provided components.
691
692 Nothing to fixup yet. May be overridden by subclasses,
693 eg for wrapping some components in another component or blocking them.
694 """
695 return
696
697 - def _wrap_components(self, allowed_parents,
698 wrap_parent = None,
699 wrap_lambda = None):
700 """
701 helper for _fixup. Checks if a component is in allowed_parents,
702 otherwise wraps it in wrap_parent
703
704 :param allowed_parents: (tuple) classes that the component should be an
705 instance of
706 :param wrap_parent: the class to wrap the component in, if needed
707 :param wrap_lambda: lambda to use for wrapping, if needed
708
709 """
710 components = []
711 for c in self.components:
712 if isinstance(c, allowed_parents):
713 pass
714 elif wrap_lambda:
715 c = wrap_lambda(c)
716 else:
717 c = wrap_parent(c)
718 if isinstance(c,DIV):
719 c.parent = self
720 components.append(c)
721 self.components = components
722
723 - def _postprocessing(self):
724 """
725 Handling of attributes (normally the ones not prefixed with '_').
726
727 Nothing to postprocess yet. May be overridden by subclasses
728 """
729 return
730
731 - def _traverse(self, status, hideerror=False):
732
733 newstatus = status
734 for c in self.components:
735 if hasattr(c, '_traverse') and callable(c._traverse):
736 c.vars = self.vars
737 c.request_vars = self.request_vars
738 c.errors = self.errors
739 c.latest = self.latest
740 c.session = self.session
741 c.formname = self.formname
742 c['hideerror']=hideerror
743 newstatus = c._traverse(status,hideerror) and newstatus
744
745
746
747
748 name = self['_name']
749 if newstatus:
750 newstatus = self._validate()
751 self._postprocessing()
752 elif 'old_value' in self.attributes:
753 self['value'] = self['old_value']
754 self._postprocessing()
755 elif name and name in self.vars:
756 self['value'] = self.vars[name]
757 self._postprocessing()
758 if name:
759 self.latest[name] = self['value']
760 return newstatus
761
763 """
764 nothing to validate yet. May be overridden by subclasses
765 """
766 return True
767
769 if isinstance(value,DIV):
770 value.parent = self
771
773 """
774 helper for xml generation. Returns separately:
775 - the component attributes
776 - the generated xml of the inner components
777
778 Component attributes start with an underscore ('_') and
779 do not have a False or None value. The underscore is removed.
780 A value of True is replaced with the attribute name.
781
782 :returns: tuple: (attributes, components)
783 """
784
785
786
787 fa = ''
788 for key in sorted(self.attributes):
789 value = self[key]
790 if key[:1] != '_':
791 continue
792 name = key[1:]
793 if value is True:
794 value = name
795 elif value is False or value is None:
796 continue
797 fa += ' %s="%s"' % (name, xmlescape(value, True))
798
799
800 co = join([xmlescape(component) for component in
801 self.components])
802
803 return (fa, co)
804
806 """
807 generates the xml for this component.
808 """
809
810 (fa, co) = self._xml()
811
812 if not self.tag:
813 return co
814
815 if self.tag[-1:] == '/':
816
817 return '<%s%s />' % (self.tag[:-1], fa)
818
819
820 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
821
823 """
824 str(COMPONENT) returns equals COMPONENT.xml()
825 """
826
827 return self.xml()
828
830 """
831 return the text stored by the DIV object rendered by the render function
832 the render function must take text, tagname, and attributes
833 render=None is equivalent to render=lambda text, tag, attr: text
834
835 >>> markdown = lambda text,tag=None,attributes={}: \
836 {None: re.sub('\s+',' ',text), \
837 'h1':'#'+text+'\\n\\n', \
838 'p':text+'\\n'}.get(tag,text)
839 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
840 >>> a.flatten(markdown)
841 '#Header\\n\\nthis is a test\\n'
842 """
843
844 text = ''
845 for c in self.components:
846 if isinstance(c,XmlComponent):
847 s=c.flatten(render)
848 elif render:
849 s=render(str(c))
850 else:
851 s=str(c)
852 text+=s
853 if render:
854 text = render(text,self.tag,self.attributes)
855 return text
856
857 regex_tag=re.compile('^[\w\-\:]+')
858 regex_id=re.compile('#([\w\-]+)')
859 regex_class=re.compile('\.([\w\-]+)')
860 regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]')
861
862
864 """
865 find all component that match the supplied attribute dictionary,
866 or None if nothing could be found
867
868 All components of the components are searched.
869
870 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
871 >>> for c in a.elements('span',first_only=True): c[0]='z'
872 >>> print a
873 <div><div><span>z</span>3<div><span>y</span></div></div></div>
874 >>> for c in a.elements('span'): c[0]='z'
875 >>> print a
876 <div><div><span>z</span>3<div><span>z</span></div></div></div>
877
878 It also supports a syntax compatible with jQuery
879
880 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
881 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
882 hello
883 world
884 >>> for e in a.elements('#1-1'): print e.flatten()
885 hello
886 >>> a.elements('a[u:v=$]')[0].xml()
887 '<a id="1-1" u:v="$">hello</a>'
888
889 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
890 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
891 >>> a.xml()
892 '<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>'
893 """
894 if len(args)==1:
895 args = [a.strip() for a in args[0].split(',')]
896 if len(args)>1:
897 subset = [self.elements(a,**kargs) for a in args]
898 return reduce(lambda a,b:a+b,subset,[])
899 elif len(args)==1:
900 items = args[0].split()
901 if len(items)>1:
902 subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])]
903 return reduce(lambda a,b:a+b,subset,[])
904 else:
905 item=items[0]
906 if '#' in item or '.' in item or '[' in item:
907 match_tag = self.regex_tag.search(item)
908 match_id = self.regex_id.search(item)
909 match_class = self.regex_class.search(item)
910 match_attr = self.regex_attr.finditer(item)
911 args = []
912 if match_tag: args = [match_tag.group()]
913 if match_id: kargs['_id'] = match_id.group(1)
914 if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \
915 match_class.group(1).replace('-','\\-').replace(':','\\:'))
916 for item in match_attr:
917 kargs['_'+item.group(1)]=item.group(2)
918 return self.elements(*args,**kargs)
919
920 matches = []
921 first_only = False
922 if kargs.has_key("first_only"):
923 first_only = kargs["first_only"]
924 del kargs["first_only"]
925
926
927 check = True
928 tag = getattr(self,'tag').replace("/","")
929 if args and tag not in args:
930 check = False
931 for (key, value) in kargs.items():
932 if isinstance(value,(str,int)):
933 if self[key] != str(value):
934 check = False
935 elif key in self.attributes:
936 if not value.search(str(self[key])):
937 check = False
938 else:
939 check = False
940 if 'find' in kargs:
941 find = kargs['find']
942 for c in self.components:
943 if isinstance(find,(str,int)):
944 if isinstance(c,str) and str(find) in c:
945 check = True
946 else:
947 if isinstance(c,str) and find.search(c):
948 check = True
949
950 if check:
951 matches.append(self)
952 if first_only:
953 return matches
954
955 for c in self.components:
956 if isinstance(c, XmlComponent):
957 kargs['first_only'] = first_only
958 child_matches = c.elements( *args, **kargs )
959 if first_only and len(child_matches) != 0:
960 return child_matches
961 matches.extend( child_matches )
962 return matches
963
964
965 - def element(self, *args, **kargs):
966 """
967 find the first component that matches the supplied attribute dictionary,
968 or None if nothing could be found
969
970 Also the components of the components are searched.
971 """
972 kargs['first_only'] = True
973 elements = self.elements(*args, **kargs)
974 if not elements:
975
976 return None
977 return elements[0]
978
980 """
981 find all sibling components that match the supplied argument list
982 and attribute dictionary, or None if nothing could be found
983 """
984 sibs = [s for s in self.parent.components if not s == self]
985 matches = []
986 first_only = False
987 if kargs.has_key("first_only"):
988 first_only = kargs["first_only"]
989 del kargs["first_only"]
990 for c in sibs:
991 try:
992 check = True
993 tag = getattr(c,'tag').replace("/","")
994 if args and tag not in args:
995 check = False
996 for (key, value) in kargs.items():
997 if c[key] != value:
998 check = False
999 if check:
1000 matches.append(c)
1001 if first_only: break
1002 except:
1003 pass
1004 return matches
1005
1007 """
1008 find the first sibling component that match the supplied argument list
1009 and attribute dictionary, or None if nothing could be found
1010 """
1011 kargs['first_only'] = True
1012 sibs = self.siblings(*args, **kargs)
1013 if not sibs:
1014 return None
1015 return sibs[0]
1016
1020
1022 return cPickle.loads(data)
1023
1025 d = DIV()
1026 d.__dict__ = data.__dict__
1027 marshal_dump = cPickle.dumps(d)
1028 return (TAG_unpickler, (marshal_dump,))
1029
1031
1032 """
1033 TAG factory example::
1034
1035 >>> print TAG.first(TAG.second('test'), _key = 3)
1036 <first key=\"3\"><second>test</second></first>
1037
1038 """
1039
1042
1050 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
1051 return lambda *a, **b: __tag__(*a, **b)
1052
1055
1056 TAG = __TAG__()
1057
1058
1060 """
1061 There are four predefined document type definitions.
1062 They can be specified in the 'doctype' parameter:
1063
1064 -'strict' enables strict doctype
1065 -'transitional' enables transitional doctype (default)
1066 -'frameset' enables frameset doctype
1067 -'html5' enables HTML 5 doctype
1068 -any other string will be treated as user's own doctype
1069
1070 'lang' parameter specifies the language of the document.
1071 Defaults to 'en'.
1072
1073 See also :class:`DIV`
1074 """
1075
1076 tag = 'html'
1077
1078 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1079 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1080 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1081 html5 = '<!DOCTYPE HTML>\n'
1082
1084 lang = self['lang']
1085 if not lang:
1086 lang = 'en'
1087 self.attributes['_lang'] = lang
1088 doctype = self['doctype']
1089 if doctype:
1090 if doctype == 'strict':
1091 doctype = self.strict
1092 elif doctype == 'transitional':
1093 doctype = self.transitional
1094 elif doctype == 'frameset':
1095 doctype = self.frameset
1096 elif doctype == 'html5':
1097 doctype = self.html5
1098 else:
1099 doctype = '%s\n' % doctype
1100 else:
1101 doctype = self.transitional
1102 (fa, co) = self._xml()
1103 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1104
1106 """
1107 This is XHTML version of the HTML helper.
1108
1109 There are three predefined document type definitions.
1110 They can be specified in the 'doctype' parameter:
1111
1112 -'strict' enables strict doctype
1113 -'transitional' enables transitional doctype (default)
1114 -'frameset' enables frameset doctype
1115 -any other string will be treated as user's own doctype
1116
1117 'lang' parameter specifies the language of the document and the xml document.
1118 Defaults to 'en'.
1119
1120 'xmlns' parameter specifies the xml namespace.
1121 Defaults to 'http://www.w3.org/1999/xhtml'.
1122
1123 See also :class:`DIV`
1124 """
1125
1126 tag = 'html'
1127
1128 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1129 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1130 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1131 xmlns = 'http://www.w3.org/1999/xhtml'
1132
1134 xmlns = self['xmlns']
1135 if xmlns:
1136 self.attributes['_xmlns'] = xmlns
1137 else:
1138 self.attributes['_xmlns'] = self.xmlns
1139 lang = self['lang']
1140 if not lang:
1141 lang = 'en'
1142 self.attributes['_lang'] = lang
1143 self.attributes['_xml:lang'] = lang
1144 doctype = self['doctype']
1145 if doctype:
1146 if doctype == 'strict':
1147 doctype = self.strict
1148 elif doctype == 'transitional':
1149 doctype = self.transitional
1150 elif doctype == 'frameset':
1151 doctype = self.frameset
1152 else:
1153 doctype = '%s\n' % doctype
1154 else:
1155 doctype = self.transitional
1156 (fa, co) = self._xml()
1157 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1158
1159
1163
1167
1168
1172
1173
1177
1178
1180
1181 tag = 'script'
1182
1184 (fa, co) = self._xml()
1185
1186 co = '\n'.join([str(component) for component in
1187 self.components])
1188 if co:
1189
1190
1191
1192
1193 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1194 else:
1195 return DIV.xml(self)
1196
1197
1199
1200 tag = 'style'
1201
1203 (fa, co) = self._xml()
1204
1205 co = '\n'.join([str(component) for component in
1206 self.components])
1207 if co:
1208
1209
1210
1211 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1212 else:
1213 return DIV.xml(self)
1214
1215
1219
1220
1224
1225
1229
1230
1234
1235
1239
1240
1244
1245
1249
1250
1254
1255
1259
1260
1262 """
1263 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1264
1265 see also :class:`DIV`
1266 """
1267
1268 tag = 'p'
1269
1271 text = DIV.xml(self)
1272 if self['cr2br']:
1273 text = text.replace('\n', '<br />')
1274 return text
1275
1276
1280
1281
1285
1286
1290
1291
1293
1294 tag = 'a'
1295
1297 if self['delete']:
1298 d = "jQuery(this).closest('%s').remove();" % self['delete']
1299 else:
1300 d = ''
1301 if self['component']:
1302 self['_onclick']="web2py_component('%s','%s');%sreturn false;" % \
1303 (self['component'],self['target'] or '',d)
1304 self['_href'] = self['_href'] or '#null'
1305 elif self['callback']:
1306 if d:
1307 self['_onclick']="if(confirm(w2p_ajax_confirm_message||'Are you sure you want o delete this object?')){ajax('%s',[],'%s');%s};return false;" % (self['callback'],self['target'] or '',d)
1308 else:
1309 self['_onclick']="ajax('%s',[],'%s');%sreturn false;" % \
1310 (self['callback'],self['target'] or '',d)
1311 self['_href'] = self['_href'] or '#null'
1312 elif self['cid']:
1313 self['_onclick']='web2py_component("%s","%s");%sreturn false;' % \
1314 (self['_href'],self['cid'],d)
1315 return DIV.xml(self)
1316
1317
1321
1322
1326
1327
1331
1332
1336
1337
1341
1342
1346
1347
1349
1350 """
1351 displays code in HTML with syntax highlighting.
1352
1353 :param attributes: optional attributes:
1354
1355 - language: indicates the language, otherwise PYTHON is assumed
1356 - link: can provide a link
1357 - styles: for styles
1358
1359 Example::
1360
1361 {{=CODE(\"print 'hello world'\", language='python', link=None,
1362 counter=1, styles={}, highlight_line=None)}}
1363
1364
1365 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1366 \"web2py\", \"html\".
1367 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1368 \"html_plain\" doesn't.
1369
1370 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1371 the online docs.
1372
1373 the counter is used for line numbering, counter can be None or a prompt
1374 string.
1375 """
1376
1378 language = self['language'] or 'PYTHON'
1379 link = self['link']
1380 counter = self.attributes.get('counter', 1)
1381 highlight_line = self.attributes.get('highlight_line', None)
1382 styles = self['styles'] or {}
1383 return highlight(
1384 join(self.components),
1385 language=language,
1386 link=link,
1387 counter=counter,
1388 styles=styles,
1389 attributes=self.attributes,
1390 highlight_line=highlight_line,
1391 )
1392
1393
1397
1398
1402
1403
1405 """
1406 UL Component.
1407
1408 If subcomponents are not LI-components they will be wrapped in a LI
1409
1410 see also :class:`DIV`
1411 """
1412
1413 tag = 'ul'
1414
1417
1418
1422
1423
1427
1428
1432
1433
1435 """
1436 TR Component.
1437
1438 If subcomponents are not TD/TH-components they will be wrapped in a TD
1439
1440 see also :class:`DIV`
1441 """
1442
1443 tag = 'tr'
1444
1447
1449
1450 tag = 'thead'
1451
1454
1455
1457
1458 tag = 'tbody'
1459
1462
1463
1470
1471
1475
1476
1478
1479 tag = 'colgroup'
1480
1481
1483 """
1484 TABLE Component.
1485
1486 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1487 they will be wrapped in a TR
1488
1489 see also :class:`DIV`
1490 """
1491
1492 tag = 'table'
1493
1496
1500
1504
1505
1616
1617
1618 -class TEXTAREA(INPUT):
1619
1620 """
1621 example::
1622
1623 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1624
1625 'blah blah blah ...' will be the content of the textarea field.
1626 """
1627
1628 tag = 'textarea'
1629
1630 - def _postprocessing(self):
1631 if not '_rows' in self.attributes:
1632 self['_rows'] = 10
1633 if not '_cols' in self.attributes:
1634 self['_cols'] = 40
1635 if not self['value'] is None:
1636 self.components = [self['value']]
1637 elif self.components:
1638 self['value'] = self.components[0]
1639
1640
1642
1643 tag = 'option'
1644
1646 if not '_value' in self.attributes:
1647 self.attributes['_value'] = str(self.components[0])
1648
1649
1653
1655
1656 tag = 'optgroup'
1657
1659 components = []
1660 for c in self.components:
1661 if isinstance(c, OPTION):
1662 components.append(c)
1663 else:
1664 components.append(OPTION(c, _value=str(c)))
1665 self.components = components
1666
1667
1669
1670 """
1671 example::
1672
1673 >>> from validators import IS_IN_SET
1674 >>> SELECT('yes', 'no', _name='selector', value='yes',
1675 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1676 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1677
1678 """
1679
1680 tag = 'select'
1681
1683 components = []
1684 for c in self.components:
1685 if isinstance(c, (OPTION, OPTGROUP)):
1686 components.append(c)
1687 else:
1688 components.append(OPTION(c, _value=str(c)))
1689 self.components = components
1690
1691 - def _postprocessing(self):
1692 component_list = []
1693 for c in self.components:
1694 if isinstance(c, OPTGROUP):
1695 component_list.append(c.components)
1696 else:
1697 component_list.append([c])
1698 options = itertools.chain(*component_list)
1699
1700 value = self['value']
1701 if not value is None:
1702 if not self['_multiple']:
1703 for c in options:
1704 if value and str(c['_value'])==str(value):
1705 c['_selected'] = 'selected'
1706 else:
1707 c['_selected'] = None
1708 else:
1709 if isinstance(value,(list,tuple)):
1710 values = [str(item) for item in value]
1711 else:
1712 values = [str(value)]
1713 for c in options:
1714 if value and str(c['_value']) in values:
1715 c['_selected'] = 'selected'
1716 else:
1717 c['_selected'] = None
1718
1719
1721
1722 tag = 'fieldset'
1723
1724
1728
1729
1953
1954
1956
1957 """
1958 example::
1959
1960 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
1961 '<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>'
1962
1963 turns any list, dictionary, etc into decent looking html.
1964 Two special attributes are
1965 :sorted: a function that takes the dict and returned sorted keys
1966 :keyfilter: a funciton that takes a key and returns its representation
1967 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
1968 """
1969
1970 tag = 'div'
1971
1972 @staticmethod
1974 if key[:1]=='_':
1975 return None
1976 return key
1977
1978 - def __init__(self, component, **attributes):
1979 self.components = [component]
1980 self.attributes = attributes
1981 sorter = attributes.get('sorted',sorted)
1982 keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore)
1983 components = []
1984 attributes = copy.copy(self.attributes)
1985 level = attributes['level'] = attributes.get('level',6) - 1
1986 if '_class' in attributes:
1987 attributes['_class'] += 'i'
1988 if level == 0:
1989 return
1990 for c in self.components:
1991 if hasattr(c,'xml') and callable(c.xml):
1992 components.append(c)
1993 continue
1994 elif hasattr(c,'keys') and callable(c.keys):
1995 rows = []
1996 try:
1997 keys = (sorter and sorter(c)) or c
1998 for key in keys:
1999 if isinstance(key,(str,unicode)) and keyfilter:
2000 filtered_key = keyfilter(key)
2001 else:
2002 filtered_key = str(key)
2003 if filtered_key is None:
2004 continue
2005 value = c[key]
2006 if type(value) == types.LambdaType:
2007 continue
2008 rows.append(TR(TD(filtered_key, _style='font-weight:bold;vertical-align:top'),
2009 TD(':',_valign='top'),
2010 TD(BEAUTIFY(value, **attributes))))
2011 components.append(TABLE(*rows, **attributes))
2012 continue
2013 except:
2014 pass
2015 if isinstance(c, str):
2016 components.append(str(c))
2017 elif isinstance(c, unicode):
2018 components.append(c.encode('utf8'))
2019 elif isinstance(c, (list, tuple)):
2020 items = [TR(TD(BEAUTIFY(item, **attributes)))
2021 for item in c]
2022 components.append(TABLE(*items, **attributes))
2023 elif isinstance(c, cgi.FieldStorage):
2024 components.append('FieldStorage object')
2025 else:
2026 components.append(repr(c))
2027 self.components = components
2028
2029
2031 """
2032 Used to build menus
2033
2034 Optional arguments
2035 _class: defaults to 'web2py-menu web2py-menu-vertical'
2036 ul_class: defaults to 'web2py-menu-vertical'
2037 li_class: defaults to 'web2py-menu-expand'
2038
2039 Example:
2040 menu = MENU([['name', False, URL(...), [submenu]], ...])
2041 {{=menu}}
2042 """
2043
2044 tag = 'ul'
2045
2047 self.data = data
2048 self.attributes = args
2049 if not '_class' in self.attributes:
2050 self['_class'] = 'web2py-menu web2py-menu-vertical'
2051 if not 'ul_class' in self.attributes:
2052 self['ul_class'] = 'web2py-menu-vertical'
2053 if not 'li_class' in self.attributes:
2054 self['li_class'] = 'web2py-menu-expand'
2055 if not 'li_active' in self.attributes:
2056 self['li_active'] = 'web2py-menu-active'
2057 if not 'mobile' in self.attributes:
2058 self['mobile'] = False
2059
2061 if level == 0:
2062 ul = UL(**self.attributes)
2063 else:
2064 ul = UL(_class=self['ul_class'])
2065 for item in data:
2066 (name, active, link) = item[:3]
2067 if isinstance(link,DIV):
2068 li = LI(link)
2069 elif 'no_link_url' in self.attributes and self['no_link_url']==link:
2070 li = LI(DIV(name))
2071 elif link:
2072 li = LI(A(name, _href=link))
2073 else:
2074 li = LI(A(name, _href='#',
2075 _onclick='javascript:void(0);return false;'))
2076 if len(item) > 3 and item[3]:
2077 li['_class'] = self['li_class']
2078 li.append(self.serialize(item[3], level+1))
2079 if active or ('active_url' in self.attributes and self['active_url']==link):
2080 if li['_class']:
2081 li['_class'] = li['_class']+' '+self['li_active']
2082 else:
2083 li['_class'] = self['li_active']
2084 ul.append(li)
2085 return ul
2086
2097
2103
2104
2105 -def embed64(
2106 filename = None,
2107 file = None,
2108 data = None,
2109 extension = 'image/gif',
2110 ):
2111 """
2112 helper to encode the provided (binary) data into base64.
2113
2114 :param filename: if provided, opens and reads this file in 'rb' mode
2115 :param file: if provided, reads this file
2116 :param data: if provided, uses the provided data
2117 """
2118
2119 if filename and os.path.exists(file):
2120 fp = open(filename, 'rb')
2121 data = fp.read()
2122 fp.close()
2123 data = base64.b64encode(data)
2124 return 'data:%s;base64,%s' % (extension, data)
2125
2126
2128 """
2129 Example:
2130
2131 >>> from validators import *
2132 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
2133 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2134 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
2135 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2136 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
2137 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2138 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2139 <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>
2140 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2141 >>> print form.xml()
2142 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2143 >>> print form.accepts({'myvar':'34'}, formname=None)
2144 False
2145 >>> print form.xml()
2146 <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>
2147 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2148 True
2149 >>> print form.xml()
2150 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2151 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2152 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2153 True
2154 >>> print form.xml()
2155 <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>
2156 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2157 >>> print form.accepts({'myvar':'as df'}, formname=None)
2158 False
2159 >>> print form.xml()
2160 <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>
2161 >>> session={}
2162 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
2163 >>> if form.accepts({}, session,formname=None): print 'passed'
2164 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2165 """
2166 pass
2167
2168
2170 """
2171 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2172 obj.tree contains the root of the tree, and tree can be manipulated
2173
2174 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2175 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2176 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2177 '<div>a<span>b</span></div>c'
2178 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2179 >>> tree.element(_a='b')['_c']=5
2180 >>> str(tree)
2181 'hello<div a="b" c="5">world</div>'
2182 """
2183 - def __init__(self,text,closed=('input','link')):
2184 HTMLParser.__init__(self)
2185 self.tree = self.parent = TAG['']()
2186 self.closed = closed
2187 self.tags = [x for x in __all__ if isinstance(eval(x),DIV)]
2188 self.last = None
2189 self.feed(text)
2191 if tagname.upper() in self.tags:
2192 tag=eval(tagname.upper())
2193 else:
2194 if tagname in self.closed: tagname+='/'
2195 tag = TAG[tagname]()
2196 for key,value in attrs: tag['_'+key]=value
2197 tag.parent = self.parent
2198 self.parent.append(tag)
2199 if not tag.tag.endswith('/'):
2200 self.parent=tag
2201 else:
2202 self.last = tag.tag[:-1]
2204 if not isinstance(data,unicode):
2205 try:
2206 data = data.decode('utf8')
2207 except:
2208 data = data.decode('latin1')
2209 self.parent.append(data.encode('utf8','xmlcharref'))
2218
2219 if tagname==self.last:
2220 return
2221 while True:
2222 try:
2223 parent_tagname=self.parent.tag
2224 self.parent = self.parent.parent
2225 except:
2226 raise RuntimeError, "unable to balance tag %s" % tagname
2227 if parent_tagname[:len(tagname)]==tagname: break
2228
2230 attr = attr or {}
2231 if tag is None: return re.sub('\s+',' ',text)
2232 if tag=='br': return '\n\n'
2233 if tag=='h1': return '#'+text+'\n\n'
2234 if tag=='h2': return '#'*2+text+'\n\n'
2235 if tag=='h3': return '#'*3+text+'\n\n'
2236 if tag=='h4': return '#'*4+text+'\n\n'
2237 if tag=='p': return text+'\n\n'
2238 if tag=='b' or tag=='strong': return '**%s**' % text
2239 if tag=='em' or tag=='i': return '*%s*' % text
2240 if tag=='tt' or tag=='code': return '`%s`' % text
2241 if tag=='a': return '[%s](%s)' % (text,attr.get('_href',''))
2242 if tag=='img': return '' % (attr.get('_alt',''),attr.get('_src',''))
2243 return text
2244
2246 attr = attr or {}
2247
2248 if tag=='br': return '\n\n'
2249 if tag=='h1': return '# '+text+'\n\n'
2250 if tag=='h2': return '#'*2+' '+text+'\n\n'
2251 if tag=='h3': return '#'*3+' '+text+'\n\n'
2252 if tag=='h4': return '#'*4+' '+text+'\n\n'
2253 if tag=='p': return text+'\n\n'
2254 if tag=='li': return '\n- '+text.replace('\n',' ')
2255 if tag=='tr': return text[3:].replace('\n',' ')+'\n'
2256 if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n'
2257 if tag in ['td','th']: return ' | '+text
2258 if tag in ['b','strong','label']: return '**%s**' % text
2259 if tag in ['em','i']: return "''%s''" % text
2260 if tag in ['tt']: return '``%s``' % text.strip()
2261 if tag in ['code']: return '``\n%s``' % text
2262 if tag=='a': return '[[%s %s]]' % (text,attr.get('_href',''))
2263 if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src',''))
2264 return text
2265
2266
2268 """
2269 For documentation: http://web2py.com/examples/static/markmin.html
2270 """
2271 - def __init__(self, text, extra=None, allowed=None, sep='p'):
2272 self.text = text
2273 self.extra = extra or {}
2274 self.allowed = allowed or {}
2275 self.sep = sep
2276
2278 """
2279 calls the gluon.contrib.markmin render function to convert the wiki syntax
2280 """
2281 return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep)
2282
2285
2287 """
2288 return the text stored by the MARKMIN object rendered by the render function
2289 """
2290 return self.text
2291
2293 """
2294 to be considered experimental since the behavior of this method is questionable
2295 another options could be TAG(self.text).elements(*args,**kargs)
2296 """
2297 return [self.text]
2298
2299
2300 if __name__ == '__main__':
2301 import doctest
2302 doctest.testmod()
2303