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