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