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 marshal
23 from HTMLParser import HTMLParser
24 from htmlentitydefs import name2codepoint
25 from contrib.markmin.markmin2html import render
26
27 from storage import Storage
28 from highlight import highlight
29 from utils import web2py_uuid, hmac_hash
30
31 import hmac
32 import hashlib
33
34 regex_crlf = re.compile('\r|\n')
35
36 __all__ = [
37 'A',
38 'B',
39 'BEAUTIFY',
40 'BODY',
41 'BR',
42 'CENTER',
43 'CODE',
44 'DIV',
45 'EM',
46 'EMBED',
47 'FIELDSET',
48 'FORM',
49 'H1',
50 'H2',
51 'H3',
52 'H4',
53 'H5',
54 'H6',
55 'HEAD',
56 'HR',
57 'HTML',
58 'I',
59 'IFRAME',
60 'IMG',
61 'INPUT',
62 'LABEL',
63 'LEGEND',
64 'LI',
65 'LINK',
66 'OL',
67 'UL',
68 'MARKMIN',
69 'MENU',
70 'META',
71 'OBJECT',
72 'ON',
73 'OPTION',
74 'P',
75 'PRE',
76 'SCRIPT',
77 'OPTGROUP',
78 'SELECT',
79 'SPAN',
80 'STYLE',
81 'TABLE',
82 'TAG',
83 'TD',
84 'TEXTAREA',
85 'TH',
86 'THEAD',
87 'TBODY',
88 'TFOOT',
89 'TITLE',
90 'TR',
91 'TT',
92 'URL',
93 'XHTML',
94 'XML',
95 'xmlescape',
96 'embed64',
97 ]
98
99
101 """
102 returns an escaped string of the provided data
103
104 :param data: the data to be escaped
105 :param quote: optional (default False)
106 """
107
108
109 if hasattr(data,'xml') and callable(data.xml):
110 return data.xml()
111
112
113 if not isinstance(data, (str, unicode)):
114 data = str(data)
115 elif isinstance(data, unicode):
116 data = data.encode('utf8', 'xmlcharrefreplace')
117
118
119 data = cgi.escape(data, quote).replace("'","'")
120 return data
121
122
123 -def URL(
124 a=None,
125 c=None,
126 f=None,
127 r=None,
128 args=[],
129 vars={},
130 anchor='',
131 extension=None,
132 env=None,
133 hmac_key=None,
134 hash_vars=True,
135 salt=None,
136 user_signature=None,
137 scheme=None,
138 host=None,
139 port=None,
140 ):
141 """
142 generate a URL
143
144 example::
145
146 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
147 ... vars={'p':1, 'q':2}, anchor='1'))
148 '/a/c/f/x/y/z?p=1&q=2#1'
149
150 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
151 ... vars={'p':(1,3), 'q':2}, anchor='1'))
152 '/a/c/f/x/y/z?p=1&p=3&q=2#1'
153
154 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
155 ... vars={'p':(3,1), 'q':2}, anchor='1'))
156 '/a/c/f/x/y/z?p=3&p=1&q=2#1'
157
158 >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
159 '/a/c/f#1%2B2'
160
161 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
162 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
163 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1'
164
165 generates a url '/a/c/f' corresponding to application a, controller c
166 and function f. If r=request is passed, a, c, f are set, respectively,
167 to r.application, r.controller, r.function.
168
169 The more typical usage is:
170
171 URL(r=request, f='index') that generates a url for the index function
172 within the present application and controller.
173
174 :param a: application (default to current if r is given)
175 :param c: controller (default to current if r is given)
176 :param f: function (default to current if r is given)
177 :param r: request (optional)
178 :param args: any arguments (optional)
179 :param vars: any variables (optional)
180 :param anchor: anchorname, without # (optional)
181 :param hmac_key: key to use when generating hmac signature (optional)
182 :param hash_vars: which of the vars to include in our hmac signature
183 True (default) - hash all vars, False - hash none of the vars,
184 iterable - hash only the included vars ['key1','key2']
185 :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
186 :param host: string to force absolute URL with host (True means http_host)
187 :param port: optional port number (forces absolute URL)
188
189 :raises SyntaxError: when no application, controller or function is
190 available
191 :raises SyntaxError: when a CRLF is found in the generated url
192 """
193
194 args = args or []
195 vars = vars or {}
196 application = None
197 controller = None
198 function = None
199
200 if not r:
201 if a and not c and not f: (f,a,c)=(a,c,f)
202 elif a and c and not f: (c,f,a)=(a,c,f)
203 from globals import current
204 if hasattr(current,'request'):
205 r = current.request
206 if r:
207 application = r.application
208 controller = r.controller
209 function = r.function
210 env = r.env
211 if extension is None and r.extension != 'html':
212 extension = r.extension
213 if a:
214 application = a
215 if c:
216 controller = c
217 if f:
218 if not isinstance(f, str):
219 function = f.__name__
220 elif '.' in f:
221 function, extension = f.split('.', 1)
222 else:
223 function = f
224
225 function2 = '%s.%s' % (function,extension or 'html')
226
227 if not (application and controller and function):
228 raise SyntaxError, 'not enough information to build the url'
229
230 if not isinstance(args, (list, tuple)):
231 args = [args]
232 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
233 if other.endswith('/'):
234 other += '/'
235
236 if vars.has_key('_signature'): vars.pop('_signature')
237 list_vars = []
238 for (key, vals) in sorted(vars.items()):
239 if not isinstance(vals, (list, tuple)):
240 vals = [vals]
241 for val in vals:
242 list_vars.append((key, val))
243
244 if user_signature:
245 from globals import current
246 if current.session.auth:
247 hmac_key = current.session.auth.hmac_key
248
249 if hmac_key:
250
251
252
253 h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
254
255
256 if hash_vars is True:
257 h_vars = list_vars
258 elif hash_vars is False:
259 h_vars = ''
260 else:
261 if hash_vars and not isinstance(hash_vars, (list, tuple)):
262 hash_vars = [hash_vars]
263 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
264
265
266 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
267
268 sig = hmac_hash(message,hmac_key,salt=salt)
269
270 vars['_signature'] = sig
271 list_vars.append(('_signature', sig))
272
273 if vars:
274 other += '?%s' % urllib.urlencode(list_vars)
275 if anchor:
276 other += '#' + urllib.quote(str(anchor))
277 if extension:
278 function += '.' + extension
279
280 if regex_crlf.search(''.join([application, controller, function, other])):
281 raise SyntaxError, 'CRLF Injection Detected'
282 url = rewrite.url_out(r, env, application, controller, function,
283 args, other, scheme, host, port)
284 return url
285
286
287 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
288 """
289 Verifies that a request's args & vars have not been tampered with by the user
290
291 :param request: web2py's request object
292 :param hmac_key: the key to authenticate with, must be the same one previously
293 used when calling URL()
294 :param hash_vars: which vars to include in our hashing. (Optional)
295 Only uses the 1st value currently
296 True (or undefined) means all, False none,
297 an iterable just the specified keys
298
299 do not call directly. Use instead:
300
301 URL.verify(hmac_key='...')
302
303 the key has to match the one used to generate the URL.
304
305 >>> r = Storage()
306 >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6')
307 >>> r.update(dict(application='a', controller='c', function='f'))
308 >>> r['args'] = ['x', 'y', 'z']
309 >>> r['get_vars'] = gv
310 >>> verifyURL(r, 'key')
311 True
312 >>> verifyURL(r, 'kay')
313 False
314 >>> r.get_vars.p = (3, 1)
315 >>> verifyURL(r, 'key')
316 True
317 >>> r.get_vars.p = (3, 2)
318 >>> verifyURL(r, 'key')
319 False
320
321 """
322
323 if not request.get_vars.has_key('_signature'):
324 return False
325
326
327 if user_signature:
328 from globals import current
329 if not current.session:
330 return False
331 hmac_key = current.session.auth.hmac_key
332 if not hmac_key:
333 return False
334
335
336 original_sig = request.get_vars._signature
337
338
339 vars, args = request.get_vars, request.args
340
341
342 request.get_vars.pop('_signature')
343
344
345
346
347 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
348 h_args = '/%s/%s/%s.%s%s' % (request.application,
349 request.controller,
350 request.function,
351 request.extension,
352 other)
353
354
355
356
357 list_vars = []
358 for (key, vals) in sorted(vars.items()):
359 if not isinstance(vals, (list, tuple)):
360 vals = [vals]
361 for val in vals:
362 list_vars.append((key, val))
363
364
365 if hash_vars is True:
366 h_vars = list_vars
367 elif hash_vars is False:
368 h_vars = ''
369 else:
370
371 try:
372 if hash_vars and not isinstance(hash_vars, (list, tuple)):
373 hash_vars = [hash_vars]
374 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
375 except:
376
377 return False
378
379 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
380
381
382 sig = hmac_hash(message,str(hmac_key),salt=salt)
383
384
385
386 request.get_vars['_signature'] = original_sig
387
388
389
390 return original_sig == sig
391
392 URL.verify = verifyURL
393
394 ON = True
395
396
398 """
399 Abstract root for all Html components
400 """
401
402
403
405 raise NotImplementedError
406
407
408 -class XML(XmlComponent):
409 """
410 use it to wrap a string that contains XML/HTML so that it will not be
411 escaped by the template
412
413 example:
414
415 >>> XML('<h1>Hello</h1>').xml()
416 '<h1>Hello</h1>'
417 """
418
419 - def __init__(
420 self,
421 text,
422 sanitize = False,
423 permitted_tags = [
424 'a',
425 'b',
426 'blockquote',
427 'br/',
428 'i',
429 'li',
430 'ol',
431 'ul',
432 'p',
433 'cite',
434 'code',
435 'pre',
436 'img/',
437 'h1','h2','h3','h4','h5','h6',
438 'table','tr','td','div',
439 ],
440 allowed_attributes = {
441 'a': ['href', 'title'],
442 'img': ['src', 'alt'],
443 'blockquote': ['type'],
444 'td': ['colspan'],
445 },
446 ):
447 """
448 :param text: the XML text
449 :param sanitize: sanitize text using the permitted tags and allowed
450 attributes (default False)
451 :param permitted_tags: list of permitted tags (default: simple list of
452 tags)
453 :param allowed_attributes: dictionary of allowed attributed (default
454 for A, IMG and BlockQuote).
455 The key is the tag; the value is a list of allowed attributes.
456 """
457
458 if sanitize:
459 text = sanitizer.sanitize(text, permitted_tags,
460 allowed_attributes)
461 if isinstance(text, unicode):
462 text = text.encode('utf8', 'xmlcharrefreplace')
463 elif not isinstance(text, str):
464 text = str(text)
465 self.text = text
466
469
472
474 return '%s%s' % (self,other)
475
477 return '%s%s' % (other,self)
478
480 return cmp(str(self),str(other))
481
483 return hash(str(self))
484
486 return getattr(str(self),name)
487
490
492 return str(self)[i:j]
493
495 for c in str(self): yield c
496
498 return len(str(self))
499
501 """
502 return the text stored by the XML object rendered by the render function
503 """
504 if render:
505 return render(self.text,None,{})
506 return self.text
507
509 """
510 to be considered experimental since the behavior of this method is questionable
511 another options could be TAG(self.text).elements(*args,**kargs)
512 """
513 return []
514
515
517 return marshal.loads(data)
520 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
521
522
523
524 -class DIV(XmlComponent):
525 """
526 HTML helper, for easy generating and manipulating a DOM structure.
527 Little or no validation is done.
528
529 Behaves like a dictionary regarding updating of attributes.
530 Behaves like a list regarding inserting/appending components.
531
532 example::
533
534 >>> DIV('hello', 'world', _style='color:red;').xml()
535 '<div style=\"color:red;\">helloworld</div>'
536
537 all other HTML helpers are derived from DIV.
538
539 _something=\"value\" attributes are transparently translated into
540 something=\"value\" HTML attributes
541 """
542
543
544
545
546 tag = 'div'
547
548 - def __init__(self, *components, **attributes):
549 """
550 :param *components: any components that should be nested in this element
551 :param **attributes: any attributes you want to give to this element
552
553 :raises SyntaxError: when a stand alone tag receives components
554 """
555
556 if self.tag[-1:] == '/' and components:
557 raise SyntaxError, '<%s> tags cannot have components'\
558 % self.tag
559 if len(components) == 1 and isinstance(components[0], (list,tuple)):
560 self.components = list(components[0])
561 else:
562 self.components = list(components)
563 self.attributes = attributes
564 self._fixup()
565
566 self._postprocessing()
567 self.parent = None
568 for c in self.components:
569 self._setnode(c)
570
572 """
573 dictionary like updating of the tag attributes
574 """
575
576 for (key, value) in kargs.items():
577 self[key] = value
578 return self
579
581 """
582 list style appending of components
583
584 >>> a=DIV()
585 >>> a.append(SPAN('x'))
586 >>> print a
587 <div><span>x</span></div>
588 """
589 self._setnode(value)
590 ret = self.components.append(value)
591 self._fixup()
592 return ret
593
595 """
596 list style inserting of components
597
598 >>> a=DIV()
599 >>> a.insert(0,SPAN('x'))
600 >>> print a
601 <div><span>x</span></div>
602 """
603 self._setnode(value)
604 ret = self.components.insert(i, value)
605 self._fixup()
606 return ret
607
609 """
610 gets attribute with name 'i' or component #i.
611 If attribute 'i' is not found returns None
612
613 :param i: index
614 if i is a string: the name of the attribute
615 otherwise references to number of the component
616 """
617
618 if isinstance(i, str):
619 try:
620 return self.attributes[i]
621 except KeyError:
622 return None
623 else:
624 return self.components[i]
625
627 """
628 sets attribute with name 'i' or component #i.
629
630 :param i: index
631 if i is a string: the name of the attribute
632 otherwise references to number of the component
633 :param value: the new value
634 """
635 self._setnode(value)
636 if isinstance(i, (str, unicode)):
637 self.attributes[i] = value
638 else:
639 self.components[i] = value
640
642 """
643 deletes attribute with name 'i' or component #i.
644
645 :param i: index
646 if i is a string: the name of the attribute
647 otherwise references to number of the component
648 """
649
650 if isinstance(i, str):
651 del self.attributes[i]
652 else:
653 del self.components[i]
654
656 """
657 returns the number of included components
658 """
659 return len(self.components)
660
662 """
663 always return True
664 """
665 return True
666
668 """
669 Handling of provided components.
670
671 Nothing to fixup yet. May be overridden by subclasses,
672 eg for wrapping some components in another component or blocking them.
673 """
674 return
675
676 - def _wrap_components(self, allowed_parents,
677 wrap_parent = None,
678 wrap_lambda = None):
679 """
680 helper for _fixup. Checks if a component is in allowed_parents,
681 otherwise wraps it in wrap_parent
682
683 :param allowed_parents: (tuple) classes that the component should be an
684 instance of
685 :param wrap_parent: the class to wrap the component in, if needed
686 :param wrap_lambda: lambda to use for wrapping, if needed
687
688 """
689 components = []
690 for c in self.components:
691 if isinstance(c, allowed_parents):
692 pass
693 elif wrap_lambda:
694 c = wrap_lambda(c)
695 else:
696 c = wrap_parent(c)
697 if isinstance(c,DIV):
698 c.parent = self
699 components.append(c)
700 self.components = components
701
702 - def _postprocessing(self):
703 """
704 Handling of attributes (normally the ones not prefixed with '_').
705
706 Nothing to postprocess yet. May be overridden by subclasses
707 """
708 return
709
710 - def _traverse(self, status, hideerror=False):
711
712 newstatus = status
713 for c in self.components:
714 if hasattr(c, '_traverse') and callable(c._traverse):
715 c.vars = self.vars
716 c.request_vars = self.request_vars
717 c.errors = self.errors
718 c.latest = self.latest
719 c.session = self.session
720 c.formname = self.formname
721 c['hideerror']=hideerror
722 newstatus = c._traverse(status,hideerror) and newstatus
723
724
725
726
727 name = self['_name']
728 if newstatus:
729 newstatus = self._validate()
730 self._postprocessing()
731 elif 'old_value' in self.attributes:
732 self['value'] = self['old_value']
733 self._postprocessing()
734 elif name and name in self.vars:
735 self['value'] = self.vars[name]
736 self._postprocessing()
737 if name:
738 self.latest[name] = self['value']
739 return newstatus
740
742 """
743 nothing to validate yet. May be overridden by subclasses
744 """
745 return True
746
748 if isinstance(value,DIV):
749 value.parent = self
750
752 """
753 helper for xml generation. Returns separately:
754 - the component attributes
755 - the generated xml of the inner components
756
757 Component attributes start with an underscore ('_') and
758 do not have a False or None value. The underscore is removed.
759 A value of True is replaced with the attribute name.
760
761 :returns: tuple: (attributes, components)
762 """
763
764
765
766 fa = ''
767 for key in sorted(self.attributes):
768 value = self[key]
769 if key[:1] != '_':
770 continue
771 name = key[1:]
772 if value is True:
773 value = name
774 elif value is False or value is None:
775 continue
776 fa += ' %s="%s"' % (name, xmlescape(value, True))
777
778
779 co = ''.join([xmlescape(component) for component in
780 self.components])
781
782 return (fa, co)
783
785 """
786 generates the xml for this component.
787 """
788
789 (fa, co) = self._xml()
790
791 if not self.tag:
792 return co
793
794 if self.tag[-1:] == '/':
795
796 return '<%s%s />' % (self.tag[:-1], fa)
797
798
799 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
800
802 """
803 str(COMPONENT) returns equals COMPONENT.xml()
804 """
805
806 return self.xml()
807
809 """
810 return the text stored by the DIV object rendered by the render function
811 the render function must take text, tagname, and attributes
812 render=None is equivalent to render=lambda text, tag, attr: text
813
814 >>> markdown = lambda text,tag=None,attributes={}: \
815 {None: re.sub('\s+',' ',text), \
816 'h1':'#'+text+'\\n\\n', \
817 'p':text+'\\n'}.get(tag,text)
818 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
819 >>> a.flatten(markdown)
820 '#Header\\n\\nthis is a test\\n'
821 """
822
823 text = ''
824 for c in self.components:
825 if isinstance(c,XmlComponent):
826 s=c.flatten(render)
827 elif render:
828 s=render(str(c))
829 else:
830 s=str(c)
831 text+=s
832 if render:
833 text = render(text,self.tag,self.attributes)
834 return text
835
836 regex_tag=re.compile('^[\w\-\:]+')
837 regex_id=re.compile('#([\w\-]+)')
838 regex_class=re.compile('\.([\w\-]+)')
839 regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]')
840
841
843 """
844 find all component that match the supplied attribute dictionary,
845 or None if nothing could be found
846
847 All components of the components are searched.
848
849 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
850 >>> for c in a.elements('span',first_only=True): c[0]='z'
851 >>> print a
852 <div><div><span>z</span>3<div><span>y</span></div></div></div>
853 >>> for c in a.elements('span'): c[0]='z'
854 >>> print a
855 <div><div><span>z</span>3<div><span>z</span></div></div></div>
856
857 It also supports a syntax compatible with jQuery
858
859 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
860 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
861 hello
862 world
863 >>> for e in a.elements('#1-1'): print e.flatten()
864 hello
865 >>> a.elements('a[u:v=$]')[0].xml()
866 '<a id="1-1" u:v="$">hello</a>'
867
868 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
869 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
870 >>> a.xml()
871 '<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
872 """
873 if len(args)==1:
874 args = [a.strip() for a in args[0].split(',')]
875 if len(args)>1:
876 subset = [self.elements(a,**kargs) for a in args]
877 return reduce(lambda a,b:a+b,subset,[])
878 elif len(args)==1:
879 items = args[0].split()
880 if len(items)>1:
881 subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])]
882 return reduce(lambda a,b:a+b,subset,[])
883 else:
884 item=items[0]
885 if '#' in item or '.' in item or '[' in item:
886 match_tag = self.regex_tag.search(item)
887 match_id = self.regex_id.search(item)
888 match_class = self.regex_class.search(item)
889 match_attr = self.regex_attr.finditer(item)
890 args = []
891 if match_tag: args = [match_tag.group()]
892 if match_id: kargs['_id'] = match_id.group(1)
893 if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \
894 match_class.group(1).replace('-','\\-').replace(':','\\:'))
895 for item in match_attr:
896 kargs['_'+item.group(1)]=item.group(2)
897 return self.elements(*args,**kargs)
898
899 matches = []
900 first_only = False
901 if kargs.has_key("first_only"):
902 first_only = kargs["first_only"]
903 del kargs["first_only"]
904
905
906 check = True
907 tag = getattr(self,'tag').replace("/","")
908 if args and tag not in args:
909 check = False
910 for (key, value) in kargs.items():
911 if isinstance(value,(str,int)):
912 if self[key] != str(value):
913 check = False
914 elif key in self.attributes:
915 if not value.search(str(self[key])):
916 check = False
917 else:
918 check = False
919 if 'find' in kargs:
920 find = kargs['find']
921 for c in self.components:
922 if isinstance(find,(str,int)):
923 if isinstance(c,str) and str(find) in c:
924 check = True
925 else:
926 if isinstance(c,str) and find.search(c):
927 check = True
928
929 if check:
930 matches.append(self)
931 if first_only:
932 return matches
933
934 for c in self.components:
935 if isinstance(c, XmlComponent):
936 kargs['first_only'] = first_only
937 child_matches = c.elements( *args, **kargs )
938 if first_only and len(child_matches) != 0:
939 return child_matches
940 matches.extend( child_matches )
941 return matches
942
943
944 - def element(self, *args, **kargs):
945 """
946 find the first component that matches the supplied attribute dictionary,
947 or None if nothing could be found
948
949 Also the components of the components are searched.
950 """
951 kargs['first_only'] = True
952 elements = self.elements(*args, **kargs)
953 if not elements:
954
955 return None
956 return elements[0]
957
959 """
960 find all sibling components that match the supplied argument list
961 and attribute dictionary, or None if nothing could be found
962 """
963 sibs = [s for s in self.parent.components if not s == self]
964 matches = []
965 first_only = False
966 if kargs.has_key("first_only"):
967 first_only = kargs["first_only"]
968 del kargs["first_only"]
969 for c in sibs:
970 try:
971 check = True
972 tag = getattr(c,'tag').replace("/","")
973 if args and tag not in args:
974 check = False
975 for (key, value) in kargs.items():
976 if c[key] != value:
977 check = False
978 if check:
979 matches.append(c)
980 if first_only: break
981 except:
982 pass
983 return matches
984
986 """
987 find the first sibling component that match the supplied argument list
988 and attribute dictionary, or None if nothing could be found
989 """
990 kargs['first_only'] = True
991 sibs = self.siblings(*args, **kargs)
992 if not sibs:
993 return None
994 return sibs[0]
995
997
998 """
999 TAG factory example::
1000
1001 >>> print TAG.first(TAG.second('test'), _key = 3)
1002 <first key=\"3\"><second>test</second></first>
1003
1004 """
1005
1008
1016
1017 return lambda *a, **b: __tag__(*a, **b)
1018
1021
1022 TAG = __TAG__()
1023
1024
1026 """
1027 There are four predefined document type definitions.
1028 They can be specified in the 'doctype' parameter:
1029
1030 -'strict' enables strict doctype
1031 -'transitional' enables transitional doctype (default)
1032 -'frameset' enables frameset doctype
1033 -'html5' enables HTML 5 doctype
1034 -any other string will be treated as user's own doctype
1035
1036 'lang' parameter specifies the language of the document.
1037 Defaults to 'en'.
1038
1039 See also :class:`DIV`
1040 """
1041
1042 tag = 'html'
1043
1044 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1045 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1046 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1047 html5 = '<!DOCTYPE HTML>\n'
1048
1050 lang = self['lang']
1051 if not lang:
1052 lang = 'en'
1053 self.attributes['_lang'] = lang
1054 doctype = self['doctype']
1055 if doctype:
1056 if doctype == 'strict':
1057 doctype = self.strict
1058 elif doctype == 'transitional':
1059 doctype = self.transitional
1060 elif doctype == 'frameset':
1061 doctype = self.frameset
1062 elif doctype == 'html5':
1063 doctype = self.html5
1064 else:
1065 doctype = '%s\n' % doctype
1066 else:
1067 doctype = self.transitional
1068 (fa, co) = self._xml()
1069 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1070
1072 """
1073 This is XHTML version of the HTML helper.
1074
1075 There are three predefined document type definitions.
1076 They can be specified in the 'doctype' parameter:
1077
1078 -'strict' enables strict doctype
1079 -'transitional' enables transitional doctype (default)
1080 -'frameset' enables frameset doctype
1081 -any other string will be treated as user's own doctype
1082
1083 'lang' parameter specifies the language of the document and the xml document.
1084 Defaults to 'en'.
1085
1086 'xmlns' parameter specifies the xml namespace.
1087 Defaults to 'http://www.w3.org/1999/xhtml'.
1088
1089 See also :class:`DIV`
1090 """
1091
1092 tag = 'html'
1093
1094 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1095 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1096 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1097 xmlns = 'http://www.w3.org/1999/xhtml'
1098
1100 xmlns = self['xmlns']
1101 if xmlns:
1102 self.attributes['_xmlns'] = xmlns
1103 else:
1104 self.attributes['_xmlns'] = self.xmlns
1105 lang = self['lang']
1106 if not lang:
1107 lang = 'en'
1108 self.attributes['_lang'] = lang
1109 self.attributes['_xml:lang'] = lang
1110 doctype = self['doctype']
1111 if doctype:
1112 if doctype == 'strict':
1113 doctype = self.strict
1114 elif doctype == 'transitional':
1115 doctype = self.transitional
1116 elif doctype == 'frameset':
1117 doctype = self.frameset
1118 else:
1119 doctype = '%s\n' % doctype
1120 else:
1121 doctype = self.transitional
1122 (fa, co) = self._xml()
1123 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1124
1125
1129
1133
1134
1138
1139
1143
1144
1146
1147 tag = 'script'
1148
1150 (fa, co) = self._xml()
1151
1152 co = '\n'.join([str(component) for component in
1153 self.components])
1154 if co:
1155
1156
1157
1158
1159 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1160 else:
1161 return DIV.xml(self)
1162
1163
1165
1166 tag = 'style'
1167
1169 (fa, co) = self._xml()
1170
1171 co = '\n'.join([str(component) for component in
1172 self.components])
1173 if co:
1174
1175
1176
1177 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1178 else:
1179 return DIV.xml(self)
1180
1181
1185
1186
1190
1191
1195
1196
1200
1201
1205
1206
1210
1211
1215
1216
1220
1221
1225
1226
1228 """
1229 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1230
1231 see also :class:`DIV`
1232 """
1233
1234 tag = 'p'
1235
1237 text = DIV.xml(self)
1238 if self['cr2br']:
1239 text = text.replace('\n', '<br />')
1240 return text
1241
1242
1246
1247
1251
1252
1256
1257
1259
1260 tag = 'a'
1261
1263 if self['callback']:
1264 self['_onclick']="ajax('%s',[],'%s');return false;" % \
1265 (self['callback'],self['target'] or '')
1266 self['_href'] = self['_href'] or '#null'
1267 elif self['cid']:
1268 self['_onclick']='web2py_component("%s","%s");return false;' % \
1269 (self['_href'],self['cid'])
1270 return DIV.xml(self)
1271
1272
1276
1277
1281
1282
1286
1287
1291
1292
1296
1297
1299
1300 """
1301 displays code in HTML with syntax highlighting.
1302
1303 :param attributes: optional attributes:
1304
1305 - language: indicates the language, otherwise PYTHON is assumed
1306 - link: can provide a link
1307 - styles: for styles
1308
1309 Example::
1310
1311 {{=CODE(\"print 'hello world'\", language='python', link=None,
1312 counter=1, styles={}, highlight_line=None)}}
1313
1314
1315 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1316 \"web2py\", \"html\".
1317 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1318 \"html_plain\" doesn't.
1319
1320 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1321 the online docs.
1322
1323 the counter is used for line numbering, counter can be None or a prompt
1324 string.
1325 """
1326
1328 language = self['language'] or 'PYTHON'
1329 link = self['link']
1330 counter = self.attributes.get('counter', 1)
1331 highlight_line = self.attributes.get('highlight_line', None)
1332 styles = self['styles'] or {}
1333 return highlight(
1334 ''.join(self.components),
1335 language=language,
1336 link=link,
1337 counter=counter,
1338 styles=styles,
1339 attributes=self.attributes,
1340 highlight_line=highlight_line,
1341 )
1342
1343
1347
1348
1352
1353
1355 """
1356 UL Component.
1357
1358 If subcomponents are not LI-components they will be wrapped in a LI
1359
1360 see also :class:`DIV`
1361 """
1362
1363 tag = 'ul'
1364
1367
1368
1372
1373
1377
1378
1382
1383
1385 """
1386 TR Component.
1387
1388 If subcomponents are not TD/TH-components they will be wrapped in a TD
1389
1390 see also :class:`DIV`
1391 """
1392
1393 tag = 'tr'
1394
1397
1399
1400 tag = 'thead'
1401
1404
1405
1407
1408 tag = 'tbody'
1409
1412
1413
1420
1421
1423 """
1424 TABLE Component.
1425
1426 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1427 they will be wrapped in a TR
1428
1429 see also :class:`DIV`
1430 """
1431
1432 tag = 'table'
1433
1436
1440
1444
1445
1556
1557
1558 -class TEXTAREA(INPUT):
1559
1560 """
1561 example::
1562
1563 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1564
1565 'blah blah blah ...' will be the content of the textarea field.
1566 """
1567
1568 tag = 'textarea'
1569
1570 - def _postprocessing(self):
1571 if not '_rows' in self.attributes:
1572 self['_rows'] = 10
1573 if not '_cols' in self.attributes:
1574 self['_cols'] = 40
1575 if self['value'] != None:
1576 self.components = [self['value']]
1577 elif self.components:
1578 self['value'] = self.components[0]
1579
1580
1582
1583 tag = 'option'
1584
1586 if not '_value' in self.attributes:
1587 self.attributes['_value'] = str(self.components[0])
1588
1589
1593
1595
1596 tag = 'optgroup'
1597
1599 components = []
1600 for c in self.components:
1601 if isinstance(c, OPTION):
1602 components.append(c)
1603 else:
1604 components.append(OPTION(c, _value=str(c)))
1605 self.components = components
1606
1607
1609
1610 """
1611 example::
1612
1613 >>> from validators import IS_IN_SET
1614 >>> SELECT('yes', 'no', _name='selector', value='yes',
1615 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1616 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1617
1618 """
1619
1620 tag = 'select'
1621
1623 components = []
1624 for c in self.components:
1625 if isinstance(c, (OPTION, OPTGROUP)):
1626 components.append(c)
1627 else:
1628 components.append(OPTION(c, _value=str(c)))
1629 self.components = components
1630
1631 - def _postprocessing(self):
1632 component_list = []
1633 for c in self.components:
1634 if isinstance(c, OPTGROUP):
1635 component_list.append(c.components)
1636 else:
1637 component_list.append([c])
1638 options = itertools.chain(*component_list)
1639
1640 value = self['value']
1641 if value != None:
1642 if not self['_multiple']:
1643 for c in options:
1644 if value and str(c['_value'])==str(value):
1645 c['_selected'] = 'selected'
1646 else:
1647 c['_selected'] = None
1648 else:
1649 if isinstance(value,(list,tuple)):
1650 values = [str(item) for item in value]
1651 else:
1652 values = [str(value)]
1653 for c in options:
1654 if value and str(c['_value']) in values:
1655 c['_selected'] = 'selected'
1656 else:
1657 c['_selected'] = None
1658
1659
1661
1662 tag = 'fieldset'
1663
1664
1668
1669
1788
1789
1791
1792 """
1793 example::
1794
1795 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
1796 '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>'
1797
1798 turns any list, dictionary, etc into decent looking html.
1799 Two special attributes are
1800 :sorted: a function that takes the dict and returned sorted keys
1801 :keyfilter: a funciton that takes a key and returns its representation
1802 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
1803 """
1804
1805 tag = 'div'
1806
1807 @staticmethod
1809 if key[:1]=='_':
1810 return None
1811 return key
1812
1813 - def __init__(self, component, **attributes):
1814 self.components = [component]
1815 self.attributes = attributes
1816 sorter = attributes.get('sorted',sorted)
1817 keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore)
1818 components = []
1819 attributes = copy.copy(self.attributes)
1820 level = attributes['level'] = attributes.get('level',6) - 1
1821 if '_class' in attributes:
1822 attributes['_class'] += 'i'
1823 if level == 0:
1824 return
1825 for c in self.components:
1826 if hasattr(c,'xml') and callable(c.xml):
1827 components.append(c)
1828 continue
1829 elif hasattr(c,'keys') and callable(c.keys):
1830 rows = []
1831 try:
1832 keys = (sorter and sorter(c)) or c
1833 for key in keys:
1834 if isinstance(key,(str,unicode)) and keyfilter:
1835 filtered_key = keyfilter(key)
1836 else:
1837 filtered_key = str(key)
1838 if filtered_key is None:
1839 continue
1840 value = c[key]
1841 if type(value) == types.LambdaType:
1842 continue
1843 rows.append(TR(TD(filtered_key, _style='font-weight:bold;'),
1844 TD(':',_valign='top'),
1845 TD(BEAUTIFY(value, **attributes))))
1846 components.append(TABLE(*rows, **attributes))
1847 continue
1848 except:
1849 pass
1850 if isinstance(c, str):
1851 components.append(str(c))
1852 elif isinstance(c, unicode):
1853 components.append(c.encode('utf8'))
1854 elif isinstance(c, (list, tuple)):
1855 items = [TR(TD(BEAUTIFY(item, **attributes)))
1856 for item in c]
1857 components.append(TABLE(*items, **attributes))
1858 elif isinstance(c, cgi.FieldStorage):
1859 components.append('FieldStorage object')
1860 else:
1861 components.append(repr(c))
1862 self.components = components
1863
1864
1866 """
1867 Used to build menus
1868
1869 Optional arguments
1870 _class: defaults to 'web2py-menu web2py-menu-vertical'
1871 ul_class: defaults to 'web2py-menu-vertical'
1872 li_class: defaults to 'web2py-menu-expand'
1873
1874 Example:
1875 menu = MENU([['name', False, URL(...), [submenu]], ...])
1876 {{=menu}}
1877 """
1878
1879 tag = 'ul'
1880
1882 self.data = data
1883 self.attributes = args
1884 if not '_class' in self.attributes:
1885 self['_class'] = 'web2py-menu web2py-menu-vertical'
1886 if not 'ul_class' in self.attributes:
1887 self['ul_class'] = 'web2py-menu-vertical'
1888 if not 'li_class' in self.attributes:
1889 self['li_class'] = 'web2py-menu-expand'
1890 if not 'li_active' in self.attributes:
1891 self['li_active'] = 'web2py-menu-active'
1892
1894 if level == 0:
1895 ul = UL(**self.attributes)
1896 else:
1897 ul = UL(_class=self['ul_class'])
1898 for item in data:
1899 (name, active, link) = item[:3]
1900 if isinstance(link,DIV):
1901 li = LI(link)
1902 elif 'no_link_url' in self.attributes and self['no_link_url']==link:
1903 li = LI(DIV(name))
1904 elif link:
1905 li = LI(A(name, _href=link))
1906 else:
1907 li = LI(A(name, _href='#',
1908 _onclick='javascript:void(0);return false;'))
1909 if len(item) > 3 and item[3]:
1910 li['_class'] = self['li_class']
1911 li.append(self.serialize(item[3], level+1))
1912 if active or ('active_url' in self.attributes and self['active_url']==link):
1913 if li['_class']:
1914 li['_class'] = li['_class']+' '+self['li_active']
1915 else:
1916 li['_class'] = self['li_active']
1917 ul.append(li)
1918 return ul
1919
1922
1923
1924 -def embed64(
1925 filename = None,
1926 file = None,
1927 data = None,
1928 extension = 'image/gif',
1929 ):
1930 """
1931 helper to encode the provided (binary) data into base64.
1932
1933 :param filename: if provided, opens and reads this file in 'rb' mode
1934 :param file: if provided, reads this file
1935 :param data: if provided, uses the provided data
1936 """
1937
1938 if filename and os.path.exists(file):
1939 fp = open(filename, 'rb')
1940 data = fp.read()
1941 fp.close()
1942 data = base64.b64encode(data)
1943 return 'data:%s;base64,%s' % (extension, data)
1944
1945
1947 """
1948 Example:
1949
1950 >>> from validators import *
1951 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
1952 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
1953 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
1954 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
1955 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
1956 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
1957 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
1958 <table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table>
1959 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
1960 >>> print form.xml()
1961 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
1962 >>> print form.accepts({'myvar':'34'}, formname=None)
1963 False
1964 >>> print form.xml()
1965 <form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form>
1966 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
1967 True
1968 >>> print form.xml()
1969 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
1970 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
1971 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
1972 True
1973 >>> print form.xml()
1974 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form>
1975 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
1976 >>> print form.accepts({'myvar':'as df'}, formname=None)
1977 False
1978 >>> print form.xml()
1979 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"as df\" /><div class=\"error\" id=\"myvar__error\">only alphanumeric!</div></form>
1980 >>> session={}
1981 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
1982 >>> if form.accepts({}, session,formname=None): print 'passed'
1983 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
1984 """
1985 pass
1986
1987
1989 """
1990 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
1991 obj.tree contains the root of the tree, and tree can be manipulated
1992
1993 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
1994 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
1995 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
1996 '<div>a<span>b</span></div>c'
1997 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
1998 >>> tree.element(_a='b')['_c']=5
1999 >>> str(tree)
2000 'hello<div a="b" c="5">world</div>'
2001 """
2002 - def __init__(self,text,closed=('input','link')):
2003 HTMLParser.__init__(self)
2004 self.tree = self.parent = TAG['']()
2005 self.closed = closed
2006 self.tags = [x for x in __all__ if isinstance(eval(x),DIV)]
2007 self.last = None
2008 self.feed(text)
2010 if tagname.upper() in self.tags:
2011 tag=eval(tagname.upper())
2012 else:
2013 if tagname in self.closed: tagname+='/'
2014 tag = TAG[tagname]()
2015 for key,value in attrs: tag['_'+key]=value
2016 tag.parent = self.parent
2017 self.parent.append(tag)
2018 if not tag.tag.endswith('/'):
2019 self.parent=tag
2020 else:
2021 self.last = tag.tag[:-1]
2023 try:
2024 self.parent.append(data.encode('utf8','xmlcharref'))
2025 except:
2026 self.parent.append(data.decode('latin1').encode('utf8','xmlcharref'))
2035
2036 if tagname==self.last:
2037 return
2038 while True:
2039 try:
2040 parent_tagname=self.parent.tag
2041 self.parent = self.parent.parent
2042 except:
2043 raise RuntimeError, "unable to balance tag %s" % tagname
2044 if parent_tagname[:len(tagname)]==tagname: break
2045
2047 if tag is None: return re.sub('\s+',' ',text)
2048 if tag=='br': return '\n\n'
2049 if tag=='h1': return '#'+text+'\n\n'
2050 if tag=='h2': return '#'*2+text+'\n\n'
2051 if tag=='h3': return '#'*3+text+'\n\n'
2052 if tag=='h4': return '#'*4+text+'\n\n'
2053 if tag=='p': return text+'\n\n'
2054 if tag=='b' or tag=='strong': return '**%s**' % text
2055 if tag=='em' or tag=='i': return '*%s*' % text
2056 if tag=='tt' or tag=='code': return '`%s`' % text
2057 if tag=='a': return '[%s](%s)' % (text,attr.get('_href',''))
2058 if tag=='img': return '' % (attr.get('_alt',''),attr.get('_src',''))
2059 return text
2060
2062 if tag is None: return re.sub('\s+',' ',text)
2063 if tag=='br': return '\n\n'
2064 if tag=='h1': return '# '+text+'\n\n'
2065 if tag=='h2': return '#'*2+' '+text+'\n\n'
2066 if tag=='h3': return '#'*3+' '+text+'\n\n'
2067 if tag=='h4': return '#'*4+' '+text+'\n\n'
2068 if tag=='p': return text+'\n\n'
2069 if tag=='li': return '\n- '+text.replace('\n',' ')
2070 if tag=='tr': return text[3:].replace('\n',' ')+'\n'
2071 if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n'
2072 if tag in ['td','th']: return ' | '+text
2073 if tag in ['b','strong','label']: return '**%s**' % text
2074 if tag in ['em','i']: return "''%s''" % text
2075 if tag in ['tt','code']: return '``%s``' % text
2076 if tag=='a': return '[[%s %s]]' % (text,attr.get('_href',''))
2077 if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src',''))
2078 return text
2079
2080
2082 """
2083 For documentation: http://web2py.com/examples/static/markmin.html
2084 """
2085 - def __init__(self, text, extra={}, allowed={}, sep='p'):
2086 self.text = text
2087 self.extra = extra
2088 self.allowed = allowed
2089 self.sep = sep
2090
2092 """
2093 calls the gluon.contrib.markmin render function to convert the wiki syntax
2094 """
2095 return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep)
2096
2099
2101 """
2102 return the text stored by the MARKMIN object rendered by the render function
2103 """
2104 return self.text
2105
2107 """
2108 to be considered experimental since the behavior of this method is questionable
2109 another options could be TAG(self.text).elements(*args,**kargs)
2110 """
2111 return [self.text]
2112
2113
2114 if __name__ == '__main__':
2115 import doctest
2116 doctest.testmod()
2117