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