1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 import cgi
11 import os
12 import re
13 import copy
14 import types
15 import urllib
16 import base64
17 import sanitizer
18 import rewrite
19 import itertools
20 import decoder
21 import copy_reg
22 import cPickle
23 import marshal
24 from HTMLParser import HTMLParser
25 from htmlentitydefs import name2codepoint
26 from contrib.markmin.markmin2html import render
27
28 from storage import Storage
29 from highlight import highlight
30 from utils import web2py_uuid, hmac_hash
31
32 import hmac
33 import hashlib
34
35 regex_crlf = re.compile('\r|\n')
36
37 join = ''.join
38
39 __all__ = [
40 'A',
41 'B',
42 'BEAUTIFY',
43 'BODY',
44 'BR',
45 'BUTTON',
46 'CENTER',
47 'CAT',
48 'CODE',
49 'DIV',
50 'EM',
51 'EMBED',
52 'FIELDSET',
53 'FORM',
54 'H1',
55 'H2',
56 'H3',
57 'H4',
58 'H5',
59 'H6',
60 'HEAD',
61 'HR',
62 'HTML',
63 'I',
64 'IFRAME',
65 'IMG',
66 'INPUT',
67 'LABEL',
68 'LEGEND',
69 'LI',
70 'LINK',
71 'OL',
72 'UL',
73 'MARKMIN',
74 'MENU',
75 'META',
76 'OBJECT',
77 'ON',
78 'OPTION',
79 'P',
80 'PRE',
81 'SCRIPT',
82 'OPTGROUP',
83 'SELECT',
84 'SPAN',
85 'STYLE',
86 'TABLE',
87 'TAG',
88 'TD',
89 'TEXTAREA',
90 'TH',
91 'THEAD',
92 'TBODY',
93 'TFOOT',
94 'TITLE',
95 'TR',
96 'TT',
97 'URL',
98 'XHTML',
99 'XML',
100 'xmlescape',
101 'embed64',
102 ]
103
104
106 """
107 returns an escaped string of the provided data
108
109 :param data: the data to be escaped
110 :param quote: optional (default False)
111 """
112
113
114 if hasattr(data,'xml') and callable(data.xml):
115 return data.xml()
116
117
118 if not isinstance(data, (str, unicode)):
119 data = str(data)
120 elif isinstance(data, unicode):
121 data = data.encode('utf8', 'xmlcharrefreplace')
122
123
124 data = cgi.escape(data, quote).replace("'","'")
125 return data
126
127
128 -def URL(
129 a=None,
130 c=None,
131 f=None,
132 r=None,
133 args=[],
134 vars={},
135 anchor='',
136 extension=None,
137 env=None,
138 hmac_key=None,
139 hash_vars=True,
140 salt=None,
141 user_signature=None,
142 scheme=None,
143 host=None,
144 port=None,
145 ):
146 """
147 generate a URL
148
149 example::
150
151 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
152 ... vars={'p':1, 'q':2}, anchor='1'))
153 '/a/c/f/x/y/z?p=1&q=2#1'
154
155 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
156 ... vars={'p':(1,3), 'q':2}, anchor='1'))
157 '/a/c/f/x/y/z?p=1&p=3&q=2#1'
158
159 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
160 ... vars={'p':(3,1), 'q':2}, anchor='1'))
161 '/a/c/f/x/y/z?p=3&p=1&q=2#1'
162
163 >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
164 '/a/c/f#1%2B2'
165
166 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
167 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
168 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1'
169
170 generates a url '/a/c/f' corresponding to application a, controller c
171 and function f. If r=request is passed, a, c, f are set, respectively,
172 to r.application, r.controller, r.function.
173
174 The more typical usage is:
175
176 URL(r=request, f='index') that generates a url for the index function
177 within the present application and controller.
178
179 :param a: application (default to current if r is given)
180 :param c: controller (default to current if r is given)
181 :param f: function (default to current if r is given)
182 :param r: request (optional)
183 :param args: any arguments (optional)
184 :param vars: any variables (optional)
185 :param anchor: anchorname, without # (optional)
186 :param hmac_key: key to use when generating hmac signature (optional)
187 :param hash_vars: which of the vars to include in our hmac signature
188 True (default) - hash all vars, False - hash none of the vars,
189 iterable - hash only the included vars ['key1','key2']
190 :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
191 :param host: string to force absolute URL with host (True means http_host)
192 :param port: optional port number (forces absolute URL)
193
194 :raises SyntaxError: when no application, controller or function is
195 available
196 :raises SyntaxError: when a CRLF is found in the generated url
197 """
198
199 if args in (None,[]): args = []
200 vars = vars or {}
201 application = None
202 controller = None
203 function = None
204
205 if not r:
206 if a and not c and not f: (f,a,c)=(a,c,f)
207 elif a and c and not f: (c,f,a)=(a,c,f)
208 from globals import current
209 if hasattr(current,'request'):
210 r = current.request
211 if r:
212 application = r.application
213 controller = r.controller
214 function = r.function
215 env = r.env
216 if extension is None and r.extension != 'html':
217 extension = r.extension
218 if a:
219 application = a
220 if c:
221 controller = c
222 if f:
223 if not isinstance(f, str):
224 function = f.__name__
225 elif '.' in f:
226 function, extension = f.split('.', 1)
227 else:
228 function = f
229
230 function2 = '%s.%s' % (function,extension or 'html')
231
232 if not (application and controller and function):
233 raise SyntaxError, 'not enough information to build the url'
234
235 if not isinstance(args, (list, tuple)):
236 args = [args]
237 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
238 if other.endswith('/'):
239 other += '/'
240
241 if vars.has_key('_signature'): vars.pop('_signature')
242 list_vars = []
243 for (key, vals) in sorted(vars.items()):
244 if not isinstance(vals, (list, tuple)):
245 vals = [vals]
246 for val in vals:
247 list_vars.append((key, val))
248
249 if user_signature:
250 from globals import current
251 if current.session.auth:
252 hmac_key = current.session.auth.hmac_key
253
254 if hmac_key:
255
256
257
258 h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
259
260
261 if hash_vars is True:
262 h_vars = list_vars
263 elif hash_vars is False:
264 h_vars = ''
265 else:
266 if hash_vars and not isinstance(hash_vars, (list, tuple)):
267 hash_vars = [hash_vars]
268 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
269
270
271 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
272
273 sig = hmac_hash(message,hmac_key,salt=salt)
274
275 list_vars.append(('_signature', sig))
276
277 if list_vars:
278 other += '?%s' % urllib.urlencode(list_vars)
279 if anchor:
280 other += '#' + urllib.quote(str(anchor))
281 if extension:
282 function += '.' + extension
283
284 if regex_crlf.search(join([application, controller, function, other])):
285 raise SyntaxError, 'CRLF Injection Detected'
286 url = rewrite.url_out(r, env, application, controller, function,
287 args, other, scheme, host, port)
288 return url
289
290
291 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
292 """
293 Verifies that a request's args & vars have not been tampered with by the user
294
295 :param request: web2py's request object
296 :param hmac_key: the key to authenticate with, must be the same one previously
297 used when calling URL()
298 :param hash_vars: which vars to include in our hashing. (Optional)
299 Only uses the 1st value currently
300 True (or undefined) means all, False none,
301 an iterable just the specified keys
302
303 do not call directly. Use instead:
304
305 URL.verify(hmac_key='...')
306
307 the key has to match the one used to generate the URL.
308
309 >>> r = Storage()
310 >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6')
311 >>> r.update(dict(application='a', controller='c', function='f'))
312 >>> r['args'] = ['x', 'y', 'z']
313 >>> r['get_vars'] = gv
314 >>> verifyURL(r, 'key')
315 True
316 >>> verifyURL(r, 'kay')
317 False
318 >>> r.get_vars.p = (3, 1)
319 >>> verifyURL(r, 'key')
320 True
321 >>> r.get_vars.p = (3, 2)
322 >>> verifyURL(r, 'key')
323 False
324
325 """
326
327 if not request.get_vars.has_key('_signature'):
328 return False
329
330
331 if user_signature:
332 from globals import current
333 if not current.session:
334 return False
335 hmac_key = current.session.auth.hmac_key
336 if not hmac_key:
337 return False
338
339
340 original_sig = request.get_vars._signature
341
342
343 vars, args = request.get_vars, request.args
344
345
346 request.get_vars.pop('_signature')
347
348
349
350
351 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
352 h_args = '/%s/%s/%s.%s%s' % (request.application,
353 request.controller,
354 request.function,
355 request.extension,
356 other)
357
358
359
360
361 list_vars = []
362 for (key, vals) in sorted(vars.items()):
363 if not isinstance(vals, (list, tuple)):
364 vals = [vals]
365 for val in vals:
366 list_vars.append((key, val))
367
368
369 if hash_vars is True:
370 h_vars = list_vars
371 elif hash_vars is False:
372 h_vars = ''
373 else:
374
375 try:
376 if hash_vars and not isinstance(hash_vars, (list, tuple)):
377 hash_vars = [hash_vars]
378 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
379 except:
380
381 return False
382
383 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
384
385
386 sig = hmac_hash(message,str(hmac_key),salt=salt)
387
388
389
390 request.get_vars['_signature'] = original_sig
391
392
393
394 return original_sig == sig
395
396 URL.verify = verifyURL
397
398 ON = True
399
400
402 """
403 Abstract root for all Html components
404 """
405
406
407
409 raise NotImplementedError
410
411
412 -class XML(XmlComponent):
413 """
414 use it to wrap a string that contains XML/HTML so that it will not be
415 escaped by the template
416
417 example:
418
419 >>> XML('<h1>Hello</h1>').xml()
420 '<h1>Hello</h1>'
421 """
422
423 - def __init__(
424 self,
425 text,
426 sanitize = False,
427 permitted_tags = [
428 'a',
429 'b',
430 'blockquote',
431 'br/',
432 'i',
433 'li',
434 'ol',
435 'ul',
436 'p',
437 'cite',
438 'code',
439 'pre',
440 'img/',
441 'h1','h2','h3','h4','h5','h6',
442 'table','tr','td','div',
443 ],
444 allowed_attributes = {
445 'a': ['href', 'title'],
446 'img': ['src', 'alt'],
447 'blockquote': ['type'],
448 'td': ['colspan'],
449 },
450 ):
451 """
452 :param text: the XML text
453 :param sanitize: sanitize text using the permitted tags and allowed
454 attributes (default False)
455 :param permitted_tags: list of permitted tags (default: simple list of
456 tags)
457 :param allowed_attributes: dictionary of allowed attributed (default
458 for A, IMG and BlockQuote).
459 The key is the tag; the value is a list of allowed attributes.
460 """
461
462 if sanitize:
463 text = sanitizer.sanitize(text, permitted_tags,
464 allowed_attributes)
465 if isinstance(text, unicode):
466 text = text.encode('utf8', 'xmlcharrefreplace')
467 elif not isinstance(text, str):
468 text = str(text)
469 self.text = text
470
473
476
478 return '%s%s' % (self,other)
479
481 return '%s%s' % (other,self)
482
484 return cmp(str(self),str(other))
485
487 return hash(str(self))
488
490 return getattr(str(self),name)
491
494
496 return str(self)[i:j]
497
499 for c in str(self): yield c
500
502 return len(str(self))
503
505 """
506 return the text stored by the XML object rendered by the render function
507 """
508 if render:
509 return render(self.text,None,{})
510 return self.text
511
513 """
514 to be considered experimental since the behavior of this method is questionable
515 another options could be TAG(self.text).elements(*args,**kargs)
516 """
517 return []
518
519
521 return marshal.loads(data)
524 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
525
526
527
528 -class DIV(XmlComponent):
529 """
530 HTML helper, for easy generating and manipulating a DOM structure.
531 Little or no validation is done.
532
533 Behaves like a dictionary regarding updating of attributes.
534 Behaves like a list regarding inserting/appending components.
535
536 example::
537
538 >>> DIV('hello', 'world', _style='color:red;').xml()
539 '<div style=\"color:red;\">helloworld</div>'
540
541 all other HTML helpers are derived from DIV.
542
543 _something=\"value\" attributes are transparently translated into
544 something=\"value\" HTML attributes
545 """
546
547
548
549
550 tag = 'div'
551
552 - def __init__(self, *components, **attributes):
553 """
554 :param *components: any components that should be nested in this element
555 :param **attributes: any attributes you want to give to this element
556
557 :raises SyntaxError: when a stand alone tag receives components
558 """
559
560 if self.tag[-1:] == '/' and components:
561 raise SyntaxError, '<%s> tags cannot have components'\
562 % self.tag
563 if len(components) == 1 and isinstance(components[0], (list,tuple)):
564 self.components = list(components[0])
565 else:
566 self.components = list(components)
567 self.attributes = attributes
568 self._fixup()
569
570 self._postprocessing()
571 self.parent = None
572 for c in self.components:
573 self._setnode(c)
574
576 """
577 dictionary like updating of the tag attributes
578 """
579
580 for (key, value) in kargs.items():
581 self[key] = value
582 return self
583
585 """
586 list style appending of components
587
588 >>> a=DIV()
589 >>> a.append(SPAN('x'))
590 >>> print a
591 <div><span>x</span></div>
592 """
593 self._setnode(value)
594 ret = self.components.append(value)
595 self._fixup()
596 return ret
597
599 """
600 list style inserting of components
601
602 >>> a=DIV()
603 >>> a.insert(0,SPAN('x'))
604 >>> print a
605 <div><span>x</span></div>
606 """
607 self._setnode(value)
608 ret = self.components.insert(i, value)
609 self._fixup()
610 return ret
611
613 """
614 gets attribute with name 'i' or component #i.
615 If attribute 'i' is not found returns None
616
617 :param i: index
618 if i is a string: the name of the attribute
619 otherwise references to number of the component
620 """
621
622 if isinstance(i, str):
623 try:
624 return self.attributes[i]
625 except KeyError:
626 return None
627 else:
628 return self.components[i]
629
631 """
632 sets attribute with name 'i' or component #i.
633
634 :param i: index
635 if i is a string: the name of the attribute
636 otherwise references to number of the component
637 :param value: the new value
638 """
639 self._setnode(value)
640 if isinstance(i, (str, unicode)):
641 self.attributes[i] = value
642 else:
643 self.components[i] = value
644
646 """
647 deletes attribute with name 'i' or component #i.
648
649 :param i: index
650 if i is a string: the name of the attribute
651 otherwise references to number of the component
652 """
653
654 if isinstance(i, str):
655 del self.attributes[i]
656 else:
657 del self.components[i]
658
660 """
661 returns the number of included components
662 """
663 return len(self.components)
664
666 """
667 always return True
668 """
669 return True
670
672 """
673 Handling of provided components.
674
675 Nothing to fixup yet. May be overridden by subclasses,
676 eg for wrapping some components in another component or blocking them.
677 """
678 return
679
680 - def _wrap_components(self, allowed_parents,
681 wrap_parent = None,
682 wrap_lambda = None):
683 """
684 helper for _fixup. Checks if a component is in allowed_parents,
685 otherwise wraps it in wrap_parent
686
687 :param allowed_parents: (tuple) classes that the component should be an
688 instance of
689 :param wrap_parent: the class to wrap the component in, if needed
690 :param wrap_lambda: lambda to use for wrapping, if needed
691
692 """
693 components = []
694 for c in self.components:
695 if isinstance(c, allowed_parents):
696 pass
697 elif wrap_lambda:
698 c = wrap_lambda(c)
699 else:
700 c = wrap_parent(c)
701 if isinstance(c,DIV):
702 c.parent = self
703 components.append(c)
704 self.components = components
705
706 - def _postprocessing(self):
707 """
708 Handling of attributes (normally the ones not prefixed with '_').
709
710 Nothing to postprocess yet. May be overridden by subclasses
711 """
712 return
713
714 - def _traverse(self, status, hideerror=False):
715
716 newstatus = status
717 for c in self.components:
718 if hasattr(c, '_traverse') and callable(c._traverse):
719 c.vars = self.vars
720 c.request_vars = self.request_vars
721 c.errors = self.errors
722 c.latest = self.latest
723 c.session = self.session
724 c.formname = self.formname
725 c['hideerror']=hideerror
726 newstatus = c._traverse(status,hideerror) and newstatus
727
728
729
730
731 name = self['_name']
732 if newstatus:
733 newstatus = self._validate()
734 self._postprocessing()
735 elif 'old_value' in self.attributes:
736 self['value'] = self['old_value']
737 self._postprocessing()
738 elif name and name in self.vars:
739 self['value'] = self.vars[name]
740 self._postprocessing()
741 if name:
742 self.latest[name] = self['value']
743 return newstatus
744
746 """
747 nothing to validate yet. May be overridden by subclasses
748 """
749 return True
750
752 if isinstance(value,DIV):
753 value.parent = self
754
756 """
757 helper for xml generation. Returns separately:
758 - the component attributes
759 - the generated xml of the inner components
760
761 Component attributes start with an underscore ('_') and
762 do not have a False or None value. The underscore is removed.
763 A value of True is replaced with the attribute name.
764
765 :returns: tuple: (attributes, components)
766 """
767
768
769
770 fa = ''
771 for key in sorted(self.attributes):
772 value = self[key]
773 if key[:1] != '_':
774 continue
775 name = key[1:]
776 if value is True:
777 value = name
778 elif value is False or value is None:
779 continue
780 fa += ' %s="%s"' % (name, xmlescape(value, True))
781
782
783 co = join([xmlescape(component) for component in
784 self.components])
785
786 return (fa, co)
787
789 """
790 generates the xml for this component.
791 """
792
793 (fa, co) = self._xml()
794
795 if not self.tag:
796 return co
797
798 if self.tag[-1:] == '/':
799
800 return '<%s%s />' % (self.tag[:-1], fa)
801
802
803 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
804
806 """
807 str(COMPONENT) returns equals COMPONENT.xml()
808 """
809
810 return self.xml()
811
813 """
814 return the text stored by the DIV object rendered by the render function
815 the render function must take text, tagname, and attributes
816 render=None is equivalent to render=lambda text, tag, attr: text
817
818 >>> markdown = lambda text,tag=None,attributes={}: \
819 {None: re.sub('\s+',' ',text), \
820 'h1':'#'+text+'\\n\\n', \
821 'p':text+'\\n'}.get(tag,text)
822 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
823 >>> a.flatten(markdown)
824 '#Header\\n\\nthis is a test\\n'
825 """
826
827 text = ''
828 for c in self.components:
829 if isinstance(c,XmlComponent):
830 s=c.flatten(render)
831 elif render:
832 s=render(str(c))
833 else:
834 s=str(c)
835 text+=s
836 if render:
837 text = render(text,self.tag,self.attributes)
838 return text
839
840 regex_tag=re.compile('^[\w\-\:]+')
841 regex_id=re.compile('#([\w\-]+)')
842 regex_class=re.compile('\.([\w\-]+)')
843 regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]')
844
845
847 """
848 find all component that match the supplied attribute dictionary,
849 or None if nothing could be found
850
851 All components of the components are searched.
852
853 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
854 >>> for c in a.elements('span',first_only=True): c[0]='z'
855 >>> print a
856 <div><div><span>z</span>3<div><span>y</span></div></div></div>
857 >>> for c in a.elements('span'): c[0]='z'
858 >>> print a
859 <div><div><span>z</span>3<div><span>z</span></div></div></div>
860
861 It also supports a syntax compatible with jQuery
862
863 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
864 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
865 hello
866 world
867 >>> for e in a.elements('#1-1'): print e.flatten()
868 hello
869 >>> a.elements('a[u:v=$]')[0].xml()
870 '<a id="1-1" u:v="$">hello</a>'
871
872 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
873 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
874 >>> a.xml()
875 '<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>'
876 """
877 if len(args)==1:
878 args = [a.strip() for a in args[0].split(',')]
879 if len(args)>1:
880 subset = [self.elements(a,**kargs) for a in args]
881 return reduce(lambda a,b:a+b,subset,[])
882 elif len(args)==1:
883 items = args[0].split()
884 if len(items)>1:
885 subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])]
886 return reduce(lambda a,b:a+b,subset,[])
887 else:
888 item=items[0]
889 if '#' in item or '.' in item or '[' in item:
890 match_tag = self.regex_tag.search(item)
891 match_id = self.regex_id.search(item)
892 match_class = self.regex_class.search(item)
893 match_attr = self.regex_attr.finditer(item)
894 args = []
895 if match_tag: args = [match_tag.group()]
896 if match_id: kargs['_id'] = match_id.group(1)
897 if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \
898 match_class.group(1).replace('-','\\-').replace(':','\\:'))
899 for item in match_attr:
900 kargs['_'+item.group(1)]=item.group(2)
901 return self.elements(*args,**kargs)
902
903 matches = []
904 first_only = False
905 if kargs.has_key("first_only"):
906 first_only = kargs["first_only"]
907 del kargs["first_only"]
908
909
910 check = True
911 tag = getattr(self,'tag').replace("/","")
912 if args and tag not in args:
913 check = False
914 for (key, value) in kargs.items():
915 if isinstance(value,(str,int)):
916 if self[key] != str(value):
917 check = False
918 elif key in self.attributes:
919 if not value.search(str(self[key])):
920 check = False
921 else:
922 check = False
923 if 'find' in kargs:
924 find = kargs['find']
925 for c in self.components:
926 if isinstance(find,(str,int)):
927 if isinstance(c,str) and str(find) in c:
928 check = True
929 else:
930 if isinstance(c,str) and find.search(c):
931 check = True
932
933 if check:
934 matches.append(self)
935 if first_only:
936 return matches
937
938 for c in self.components:
939 if isinstance(c, XmlComponent):
940 kargs['first_only'] = first_only
941 child_matches = c.elements( *args, **kargs )
942 if first_only and len(child_matches) != 0:
943 return child_matches
944 matches.extend( child_matches )
945 return matches
946
947
948 - def element(self, *args, **kargs):
949 """
950 find the first component that matches the supplied attribute dictionary,
951 or None if nothing could be found
952
953 Also the components of the components are searched.
954 """
955 kargs['first_only'] = True
956 elements = self.elements(*args, **kargs)
957 if not elements:
958
959 return None
960 return elements[0]
961
963 """
964 find all sibling components that match the supplied argument list
965 and attribute dictionary, or None if nothing could be found
966 """
967 sibs = [s for s in self.parent.components if not s == self]
968 matches = []
969 first_only = False
970 if kargs.has_key("first_only"):
971 first_only = kargs["first_only"]
972 del kargs["first_only"]
973 for c in sibs:
974 try:
975 check = True
976 tag = getattr(c,'tag').replace("/","")
977 if args and tag not in args:
978 check = False
979 for (key, value) in kargs.items():
980 if c[key] != value:
981 check = False
982 if check:
983 matches.append(c)
984 if first_only: break
985 except:
986 pass
987 return matches
988
990 """
991 find the first sibling component that match the supplied argument list
992 and attribute dictionary, or None if nothing could be found
993 """
994 kargs['first_only'] = True
995 sibs = self.siblings(*args, **kargs)
996 if not sibs:
997 return None
998 return sibs[0]
999
1003
1005 return cPickle.loads(data)
1006
1008 d = DIV()
1009 d.__dict__ = data.__dict__
1010 marshal_dump = cPickle.dumps(d)
1011 return (TAG_unpickler, (marshal_dump,))
1012
1014
1015 """
1016 TAG factory example::
1017
1018 >>> print TAG.first(TAG.second('test'), _key = 3)
1019 <first key=\"3\"><second>test</second></first>
1020
1021 """
1022
1025
1033 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
1034 return lambda *a, **b: __tag__(*a, **b)
1035
1038
1039 TAG = __TAG__()
1040
1041
1043 """
1044 There are four predefined document type definitions.
1045 They can be specified in the 'doctype' parameter:
1046
1047 -'strict' enables strict doctype
1048 -'transitional' enables transitional doctype (default)
1049 -'frameset' enables frameset doctype
1050 -'html5' enables HTML 5 doctype
1051 -any other string will be treated as user's own doctype
1052
1053 'lang' parameter specifies the language of the document.
1054 Defaults to 'en'.
1055
1056 See also :class:`DIV`
1057 """
1058
1059 tag = 'html'
1060
1061 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1062 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1063 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1064 html5 = '<!DOCTYPE HTML>\n'
1065
1067 lang = self['lang']
1068 if not lang:
1069 lang = 'en'
1070 self.attributes['_lang'] = lang
1071 doctype = self['doctype']
1072 if doctype:
1073 if doctype == 'strict':
1074 doctype = self.strict
1075 elif doctype == 'transitional':
1076 doctype = self.transitional
1077 elif doctype == 'frameset':
1078 doctype = self.frameset
1079 elif doctype == 'html5':
1080 doctype = self.html5
1081 else:
1082 doctype = '%s\n' % doctype
1083 else:
1084 doctype = self.transitional
1085 (fa, co) = self._xml()
1086 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1087
1089 """
1090 This is XHTML version of the HTML helper.
1091
1092 There are three predefined document type definitions.
1093 They can be specified in the 'doctype' parameter:
1094
1095 -'strict' enables strict doctype
1096 -'transitional' enables transitional doctype (default)
1097 -'frameset' enables frameset doctype
1098 -any other string will be treated as user's own doctype
1099
1100 'lang' parameter specifies the language of the document and the xml document.
1101 Defaults to 'en'.
1102
1103 'xmlns' parameter specifies the xml namespace.
1104 Defaults to 'http://www.w3.org/1999/xhtml'.
1105
1106 See also :class:`DIV`
1107 """
1108
1109 tag = 'html'
1110
1111 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1112 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1113 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1114 xmlns = 'http://www.w3.org/1999/xhtml'
1115
1117 xmlns = self['xmlns']
1118 if xmlns:
1119 self.attributes['_xmlns'] = xmlns
1120 else:
1121 self.attributes['_xmlns'] = self.xmlns
1122 lang = self['lang']
1123 if not lang:
1124 lang = 'en'
1125 self.attributes['_lang'] = lang
1126 self.attributes['_xml:lang'] = lang
1127 doctype = self['doctype']
1128 if doctype:
1129 if doctype == 'strict':
1130 doctype = self.strict
1131 elif doctype == 'transitional':
1132 doctype = self.transitional
1133 elif doctype == 'frameset':
1134 doctype = self.frameset
1135 else:
1136 doctype = '%s\n' % doctype
1137 else:
1138 doctype = self.transitional
1139 (fa, co) = self._xml()
1140 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1141
1142
1146
1150
1151
1155
1156
1160
1161
1163
1164 tag = 'script'
1165
1167 (fa, co) = self._xml()
1168
1169 co = '\n'.join([str(component) for component in
1170 self.components])
1171 if co:
1172
1173
1174
1175
1176 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1177 else:
1178 return DIV.xml(self)
1179
1180
1182
1183 tag = 'style'
1184
1186 (fa, co) = self._xml()
1187
1188 co = '\n'.join([str(component) for component in
1189 self.components])
1190 if co:
1191
1192
1193
1194 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1195 else:
1196 return DIV.xml(self)
1197
1198
1202
1203
1207
1208
1212
1213
1217
1218
1222
1223
1227
1228
1232
1233
1237
1238
1242
1243
1245 """
1246 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1247
1248 see also :class:`DIV`
1249 """
1250
1251 tag = 'p'
1252
1254 text = DIV.xml(self)
1255 if self['cr2br']:
1256 text = text.replace('\n', '<br />')
1257 return text
1258
1259
1263
1264
1268
1269
1273
1274
1276
1277 tag = 'a'
1278
1280 if self['callback']:
1281 self['_onclick']="ajax('%s',[],'%s');return false;" % \
1282 (self['callback'],self['target'] or '')
1283 self['_href'] = self['_href'] or '#null'
1284 elif self['cid']:
1285 self['_onclick']='web2py_component("%s","%s");return false;' % \
1286 (self['_href'],self['cid'])
1287 return DIV.xml(self)
1288
1289
1293
1294
1298
1299
1303
1304
1308
1309
1313
1314
1318
1319
1321
1322 """
1323 displays code in HTML with syntax highlighting.
1324
1325 :param attributes: optional attributes:
1326
1327 - language: indicates the language, otherwise PYTHON is assumed
1328 - link: can provide a link
1329 - styles: for styles
1330
1331 Example::
1332
1333 {{=CODE(\"print 'hello world'\", language='python', link=None,
1334 counter=1, styles={}, highlight_line=None)}}
1335
1336
1337 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1338 \"web2py\", \"html\".
1339 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1340 \"html_plain\" doesn't.
1341
1342 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1343 the online docs.
1344
1345 the counter is used for line numbering, counter can be None or a prompt
1346 string.
1347 """
1348
1350 language = self['language'] or 'PYTHON'
1351 link = self['link']
1352 counter = self.attributes.get('counter', 1)
1353 highlight_line = self.attributes.get('highlight_line', None)
1354 styles = self['styles'] or {}
1355 return highlight(
1356 join(self.components),
1357 language=language,
1358 link=link,
1359 counter=counter,
1360 styles=styles,
1361 attributes=self.attributes,
1362 highlight_line=highlight_line,
1363 )
1364
1365
1369
1370
1374
1375
1377 """
1378 UL Component.
1379
1380 If subcomponents are not LI-components they will be wrapped in a LI
1381
1382 see also :class:`DIV`
1383 """
1384
1385 tag = 'ul'
1386
1389
1390
1394
1395
1399
1400
1404
1405
1407 """
1408 TR Component.
1409
1410 If subcomponents are not TD/TH-components they will be wrapped in a TD
1411
1412 see also :class:`DIV`
1413 """
1414
1415 tag = 'tr'
1416
1419
1421
1422 tag = 'thead'
1423
1426
1427
1429
1430 tag = 'tbody'
1431
1434
1435
1442
1443
1447
1448
1450
1451 tag = 'colgroup'
1452
1453
1455 """
1456 TABLE Component.
1457
1458 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1459 they will be wrapped in a TR
1460
1461 see also :class:`DIV`
1462 """
1463
1464 tag = 'table'
1465
1468
1472
1476
1477
1588
1589
1590 -class TEXTAREA(INPUT):
1591
1592 """
1593 example::
1594
1595 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1596
1597 'blah blah blah ...' will be the content of the textarea field.
1598 """
1599
1600 tag = 'textarea'
1601
1602 - def _postprocessing(self):
1603 if not '_rows' in self.attributes:
1604 self['_rows'] = 10
1605 if not '_cols' in self.attributes:
1606 self['_cols'] = 40
1607 if self['value'] != None:
1608 self.components = [self['value']]
1609 elif self.components:
1610 self['value'] = self.components[0]
1611
1612
1614
1615 tag = 'option'
1616
1618 if not '_value' in self.attributes:
1619 self.attributes['_value'] = str(self.components[0])
1620
1621
1625
1627
1628 tag = 'optgroup'
1629
1631 components = []
1632 for c in self.components:
1633 if isinstance(c, OPTION):
1634 components.append(c)
1635 else:
1636 components.append(OPTION(c, _value=str(c)))
1637 self.components = components
1638
1639
1641
1642 """
1643 example::
1644
1645 >>> from validators import IS_IN_SET
1646 >>> SELECT('yes', 'no', _name='selector', value='yes',
1647 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1648 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1649
1650 """
1651
1652 tag = 'select'
1653
1655 components = []
1656 for c in self.components:
1657 if isinstance(c, (OPTION, OPTGROUP)):
1658 components.append(c)
1659 else:
1660 components.append(OPTION(c, _value=str(c)))
1661 self.components = components
1662
1663 - def _postprocessing(self):
1664 component_list = []
1665 for c in self.components:
1666 if isinstance(c, OPTGROUP):
1667 component_list.append(c.components)
1668 else:
1669 component_list.append([c])
1670 options = itertools.chain(*component_list)
1671
1672 value = self['value']
1673 if value != None:
1674 if not self['_multiple']:
1675 for c in options:
1676 if value and str(c['_value'])==str(value):
1677 c['_selected'] = 'selected'
1678 else:
1679 c['_selected'] = None
1680 else:
1681 if isinstance(value,(list,tuple)):
1682 values = [str(item) for item in value]
1683 else:
1684 values = [str(value)]
1685 for c in options:
1686 if value and str(c['_value']) in values:
1687 c['_selected'] = 'selected'
1688 else:
1689 c['_selected'] = None
1690
1691
1693
1694 tag = 'fieldset'
1695
1696
1700
1701
1909
1910
1912
1913 """
1914 example::
1915
1916 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
1917 '<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>'
1918
1919 turns any list, dictionary, etc into decent looking html.
1920 Two special attributes are
1921 :sorted: a function that takes the dict and returned sorted keys
1922 :keyfilter: a funciton that takes a key and returns its representation
1923 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
1924 """
1925
1926 tag = 'div'
1927
1928 @staticmethod
1930 if key[:1]=='_':
1931 return None
1932 return key
1933
1934 - def __init__(self, component, **attributes):
1935 self.components = [component]
1936 self.attributes = attributes
1937 sorter = attributes.get('sorted',sorted)
1938 keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore)
1939 components = []
1940 attributes = copy.copy(self.attributes)
1941 level = attributes['level'] = attributes.get('level',6) - 1
1942 if '_class' in attributes:
1943 attributes['_class'] += 'i'
1944 if level == 0:
1945 return
1946 for c in self.components:
1947 if hasattr(c,'xml') and callable(c.xml):
1948 components.append(c)
1949 continue
1950 elif hasattr(c,'keys') and callable(c.keys):
1951 rows = []
1952 try:
1953 keys = (sorter and sorter(c)) or c
1954 for key in keys:
1955 if isinstance(key,(str,unicode)) and keyfilter:
1956 filtered_key = keyfilter(key)
1957 else:
1958 filtered_key = str(key)
1959 if filtered_key is None:
1960 continue
1961 value = c[key]
1962 if type(value) == types.LambdaType:
1963 continue
1964 rows.append(TR(TD(filtered_key, _style='font-weight:bold;'),
1965 TD(':',_valign='top'),
1966 TD(BEAUTIFY(value, **attributes))))
1967 components.append(TABLE(*rows, **attributes))
1968 continue
1969 except:
1970 pass
1971 if isinstance(c, str):
1972 components.append(str(c))
1973 elif isinstance(c, unicode):
1974 components.append(c.encode('utf8'))
1975 elif isinstance(c, (list, tuple)):
1976 items = [TR(TD(BEAUTIFY(item, **attributes)))
1977 for item in c]
1978 components.append(TABLE(*items, **attributes))
1979 elif isinstance(c, cgi.FieldStorage):
1980 components.append('FieldStorage object')
1981 else:
1982 components.append(repr(c))
1983 self.components = components
1984
1985
1987 """
1988 Used to build menus
1989
1990 Optional arguments
1991 _class: defaults to 'web2py-menu web2py-menu-vertical'
1992 ul_class: defaults to 'web2py-menu-vertical'
1993 li_class: defaults to 'web2py-menu-expand'
1994
1995 Example:
1996 menu = MENU([['name', False, URL(...), [submenu]], ...])
1997 {{=menu}}
1998 """
1999
2000 tag = 'ul'
2001
2003 self.data = data
2004 self.attributes = args
2005 if not '_class' in self.attributes:
2006 self['_class'] = 'web2py-menu web2py-menu-vertical'
2007 if not 'ul_class' in self.attributes:
2008 self['ul_class'] = 'web2py-menu-vertical'
2009 if not 'li_class' in self.attributes:
2010 self['li_class'] = 'web2py-menu-expand'
2011 if not 'li_active' in self.attributes:
2012 self['li_active'] = 'web2py-menu-active'
2013
2015 if level == 0:
2016 ul = UL(**self.attributes)
2017 else:
2018 ul = UL(_class=self['ul_class'])
2019 for item in data:
2020 (name, active, link) = item[:3]
2021 if isinstance(link,DIV):
2022 li = LI(link)
2023 elif 'no_link_url' in self.attributes and self['no_link_url']==link:
2024 li = LI(DIV(name))
2025 elif link:
2026 li = LI(A(name, _href=link))
2027 else:
2028 li = LI(A(name, _href='#',
2029 _onclick='javascript:void(0);return false;'))
2030 if len(item) > 3 and item[3]:
2031 li['_class'] = self['li_class']
2032 li.append(self.serialize(item[3], level+1))
2033 if active or ('active_url' in self.attributes and self['active_url']==link):
2034 if li['_class']:
2035 li['_class'] = li['_class']+' '+self['li_active']
2036 else:
2037 li['_class'] = self['li_active']
2038 ul.append(li)
2039 return ul
2040
2043
2044
2045 -def embed64(
2046 filename = None,
2047 file = None,
2048 data = None,
2049 extension = 'image/gif',
2050 ):
2051 """
2052 helper to encode the provided (binary) data into base64.
2053
2054 :param filename: if provided, opens and reads this file in 'rb' mode
2055 :param file: if provided, reads this file
2056 :param data: if provided, uses the provided data
2057 """
2058
2059 if filename and os.path.exists(file):
2060 fp = open(filename, 'rb')
2061 data = fp.read()
2062 fp.close()
2063 data = base64.b64encode(data)
2064 return 'data:%s;base64,%s' % (extension, data)
2065
2066
2068 """
2069 Example:
2070
2071 >>> from validators import *
2072 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
2073 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2074 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
2075 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2076 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
2077 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2078 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2079 <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>
2080 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2081 >>> print form.xml()
2082 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2083 >>> print form.accepts({'myvar':'34'}, formname=None)
2084 False
2085 >>> print form.xml()
2086 <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>
2087 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2088 True
2089 >>> print form.xml()
2090 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2091 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2092 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2093 True
2094 >>> print form.xml()
2095 <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>
2096 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2097 >>> print form.accepts({'myvar':'as df'}, formname=None)
2098 False
2099 >>> print form.xml()
2100 <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>
2101 >>> session={}
2102 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
2103 >>> if form.accepts({}, session,formname=None): print 'passed'
2104 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2105 """
2106 pass
2107
2108
2110 """
2111 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2112 obj.tree contains the root of the tree, and tree can be manipulated
2113
2114 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2115 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2116 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2117 '<div>a<span>b</span></div>c'
2118 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2119 >>> tree.element(_a='b')['_c']=5
2120 >>> str(tree)
2121 'hello<div a="b" c="5">world</div>'
2122 """
2123 - def __init__(self,text,closed=('input','link')):
2124 HTMLParser.__init__(self)
2125 self.tree = self.parent = TAG['']()
2126 self.closed = closed
2127 self.tags = [x for x in __all__ if isinstance(eval(x),DIV)]
2128 self.last = None
2129 self.feed(text)
2131 if tagname.upper() in self.tags:
2132 tag=eval(tagname.upper())
2133 else:
2134 if tagname in self.closed: tagname+='/'
2135 tag = TAG[tagname]()
2136 for key,value in attrs: tag['_'+key]=value
2137 tag.parent = self.parent
2138 self.parent.append(tag)
2139 if not tag.tag.endswith('/'):
2140 self.parent=tag
2141 else:
2142 self.last = tag.tag[:-1]
2144 try:
2145 self.parent.append(data.encode('utf8','xmlcharref'))
2146 except:
2147 self.parent.append(data.decode('latin1').encode('utf8','xmlcharref'))
2156
2157 if tagname==self.last:
2158 return
2159 while True:
2160 try:
2161 parent_tagname=self.parent.tag
2162 self.parent = self.parent.parent
2163 except:
2164 raise RuntimeError, "unable to balance tag %s" % tagname
2165 if parent_tagname[:len(tagname)]==tagname: break
2166
2168 if tag is None: return re.sub('\s+',' ',text)
2169 if tag=='br': return '\n\n'
2170 if tag=='h1': return '#'+text+'\n\n'
2171 if tag=='h2': return '#'*2+text+'\n\n'
2172 if tag=='h3': return '#'*3+text+'\n\n'
2173 if tag=='h4': return '#'*4+text+'\n\n'
2174 if tag=='p': return text+'\n\n'
2175 if tag=='b' or tag=='strong': return '**%s**' % text
2176 if tag=='em' or tag=='i': return '*%s*' % text
2177 if tag=='tt' or tag=='code': return '`%s`' % text
2178 if tag=='a': return '[%s](%s)' % (text,attr.get('_href',''))
2179 if tag=='img': return '![%s](%s)' % (attr.get('_alt',''),attr.get('_src',''))
2180 return text
2181
2183
2184 if tag=='br': return '\n\n'
2185 if tag=='h1': return '# '+text+'\n\n'
2186 if tag=='h2': return '#'*2+' '+text+'\n\n'
2187 if tag=='h3': return '#'*3+' '+text+'\n\n'
2188 if tag=='h4': return '#'*4+' '+text+'\n\n'
2189 if tag=='p': return text+'\n\n'
2190 if tag=='li': return '\n- '+text.replace('\n',' ')
2191 if tag=='tr': return text[3:].replace('\n',' ')+'\n'
2192 if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n'
2193 if tag in ['td','th']: return ' | '+text
2194 if tag in ['b','strong','label']: return '**%s**' % text
2195 if tag in ['em','i']: return "''%s''" % text
2196 if tag in ['tt']: return '``%s``' % text.strip()
2197 if tag in ['code']: return '``\n%s``' % text
2198 if tag=='a': return '[[%s %s]]' % (text,attr.get('_href',''))
2199 if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src',''))
2200 return text
2201
2202
2204 """
2205 For documentation: http://web2py.com/examples/static/markmin.html
2206 """
2207 - def __init__(self, text, extra={}, allowed={}, sep='p'):
2208 self.text = text
2209 self.extra = extra
2210 self.allowed = allowed
2211 self.sep = sep
2212
2214 """
2215 calls the gluon.contrib.markmin render function to convert the wiki syntax
2216 """
2217 return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep)
2218
2221
2223 """
2224 return the text stored by the MARKMIN object rendered by the render function
2225 """
2226 return self.text
2227
2229 """
2230 to be considered experimental since the behavior of this method is questionable
2231 another options could be TAG(self.text).elements(*args,**kargs)
2232 """
2233 return [self.text]
2234
2235
2236 if __name__ == '__main__':
2237 import doctest
2238 doctest.testmod()
2239