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