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, simple_hash, compare
29 from highlight import highlight
30
31 regex_crlf = re.compile('\r|\n')
32
33 join = ''.join
34
35
36 entitydefs = dict(map(lambda (
37 k, v): (k, unichr(v).encode('utf-8')), name2codepoint.iteritems()))
38 entitydefs.setdefault('apos', u"'".encode('utf-8'))
39
40
41 __all__ = [
42 'A',
43 'B',
44 'BEAUTIFY',
45 'BODY',
46 'BR',
47 'BUTTON',
48 'CENTER',
49 'CAT',
50 'CODE',
51 'COL',
52 'COLGROUP',
53 'DIV',
54 'EM',
55 'EMBED',
56 'FIELDSET',
57 'FORM',
58 'H1',
59 'H2',
60 'H3',
61 'H4',
62 'H5',
63 'H6',
64 'HEAD',
65 'HR',
66 'HTML',
67 'I',
68 'IFRAME',
69 'IMG',
70 'INPUT',
71 'LABEL',
72 'LEGEND',
73 'LI',
74 'LINK',
75 'OL',
76 'UL',
77 'MARKMIN',
78 'MENU',
79 'META',
80 'OBJECT',
81 'ON',
82 'OPTION',
83 'P',
84 'PRE',
85 'SCRIPT',
86 'OPTGROUP',
87 'SELECT',
88 'SPAN',
89 'STRONG',
90 'STYLE',
91 'TABLE',
92 'TAG',
93 'TD',
94 'TEXTAREA',
95 'TH',
96 'THEAD',
97 'TBODY',
98 'TFOOT',
99 'TITLE',
100 'TR',
101 'TT',
102 'URL',
103 'XHTML',
104 'XML',
105 'xmlescape',
106 'embed64',
107 ]
111 """
112 returns an escaped string of the provided data
113
114 :param data: the data to be escaped
115 :param quote: optional (default False)
116 """
117
118
119 if hasattr(data, 'xml') and callable(data.xml):
120 return data.xml()
121
122
123 if not isinstance(data, (str, unicode)):
124 data = str(data)
125 elif isinstance(data, unicode):
126 data = data.encode('utf8', 'xmlcharrefreplace')
127
128
129 data = cgi.escape(data, quote).replace("'", "'")
130 return data
131
133 if not isinstance(f, (list,tuple)):
134 f = [f]
135 for item in f:
136 item(*a,**b)
137
139 text = text.decode('utf-8')
140 if len(text) > length:
141 text = text[:length - len(dots)].encode('utf-8') + dots
142 return text
143
144
145 -def URL(
146 a=None,
147 c=None,
148 f=None,
149 r=None,
150 args=None,
151 vars=None,
152 anchor='',
153 extension=None,
154 env=None,
155 hmac_key=None,
156 hash_vars=True,
157 salt=None,
158 user_signature=None,
159 scheme=None,
160 host=None,
161 port=None,
162 encode_embedded_slash=False,
163 url_encode=True
164 ):
165 """
166 generate a URL
167
168 example::
169
170 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
171 ... vars={'p':1, 'q':2}, anchor='1'))
172 '/a/c/f/x/y/z?p=1&q=2#1'
173
174 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
175 ... vars={'p':(1,3), 'q':2}, anchor='1'))
176 '/a/c/f/x/y/z?p=1&p=3&q=2#1'
177
178 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
179 ... vars={'p':(3,1), 'q':2}, anchor='1'))
180 '/a/c/f/x/y/z?p=3&p=1&q=2#1'
181
182 >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
183 '/a/c/f#1%2B2'
184
185 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
186 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
187 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1'
188
189 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z']))
190 '/a/c/f/w/x/y/z'
191
192 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z'], encode_embedded_slash=True))
193 '/a/c/f/w%2Fx/y%2Fz'
194
195 >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=False))
196 '/a/c/f/%(id)d'
197
198 >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=True))
199 '/a/c/f/%25%28id%29d'
200
201 >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=False))
202 '/a/c/f?id=%(id)d'
203
204 >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=True))
205 '/a/c/f?id=%25%28id%29d'
206
207 >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=False))
208 '/a/c/f#%(id)d'
209
210 >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
211 '/a/c/f#%25%28id%29d'
212
213 generates a url '/a/c/f' corresponding to application a, controller c
214 and function f. If r=request is passed, a, c, f are set, respectively,
215 to r.application, r.controller, r.function.
216
217 The more typical usage is:
218
219 URL(r=request, f='index') that generates a url for the index function
220 within the present application and controller.
221
222 :param a: application (default to current if r is given)
223 :param c: controller (default to current if r is given)
224 :param f: function (default to current if r is given)
225 :param r: request (optional)
226 :param args: any arguments (optional)
227 :param vars: any variables (optional)
228 :param anchor: anchorname, without # (optional)
229 :param hmac_key: key to use when generating hmac signature (optional)
230 :param hash_vars: which of the vars to include in our hmac signature
231 True (default) - hash all vars, False - hash none of the vars,
232 iterable - hash only the included vars ['key1','key2']
233 :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
234 :param host: string to force absolute URL with host (True means http_host)
235 :param port: optional port number (forces absolute URL)
236
237 :raises SyntaxError: when no application, controller or function is
238 available
239 :raises SyntaxError: when a CRLF is found in the generated url
240 """
241
242 from rewrite import url_out
243
244 if args in (None, []):
245 args = []
246 vars = vars or {}
247 application = None
248 controller = None
249 function = None
250
251 if not isinstance(args, (list, tuple)):
252 args = [args]
253
254 if not r:
255 if a and not c and not f:
256 (f, a, c) = (a, c, f)
257 elif a and c and not f:
258 (c, f, a) = (a, c, f)
259 from globals import current
260 if hasattr(current, 'request'):
261 r = current.request
262
263 if r:
264 application = r.application
265 controller = r.controller
266 function = r.function
267 env = r.env
268 if extension is None and r.extension != 'html':
269 extension = r.extension
270 if a:
271 application = a
272 if c:
273 controller = c
274 if f:
275 if not isinstance(f, str):
276 if hasattr(f, '__name__'):
277 function = f.__name__
278 else:
279 raise SyntaxError(
280 'when calling URL, function or function name required')
281 elif '/' in f:
282 if f.startswith("/"):
283 f = f[1:]
284 items = f.split('/')
285 function = f = items[0]
286 args = items[1:] + args
287 else:
288 function = f
289
290
291 if controller == 'static':
292 extension = None
293
294 if '.' in function:
295 function, extension = function.rsplit('.', 1)
296
297 function2 = '%s.%s' % (function, extension or 'html')
298
299 if not (application and controller and function):
300 raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function))
301
302 if args:
303 if url_encode:
304 if encode_embedded_slash:
305 other = '/' + '/'.join([urllib.quote(str(
306 x), '') for x in args])
307 else:
308 other = args and urllib.quote(
309 '/' + '/'.join([str(x) for x in args]))
310 else:
311 other = args and ('/' + '/'.join([str(x) for x in args]))
312 else:
313 other = ''
314
315 if other.endswith('/'):
316 other += '/'
317
318 list_vars = []
319 for (key, vals) in sorted(vars.items()):
320 if key == '_signature':
321 continue
322 if not isinstance(vals, (list, tuple)):
323 vals = [vals]
324 for val in vals:
325 list_vars.append((key, val))
326
327 if user_signature:
328 from globals import current
329 if current.session.auth:
330 hmac_key = current.session.auth.hmac_key
331
332 if hmac_key:
333
334
335
336 h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
337
338
339 if hash_vars is True:
340 h_vars = list_vars
341 elif hash_vars is False:
342 h_vars = ''
343 else:
344 if hash_vars and not isinstance(hash_vars, (list, tuple)):
345 hash_vars = [hash_vars]
346 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
347
348
349 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
350 sig = simple_hash(
351 message, hmac_key or '', salt or '', digest_alg='sha1')
352
353 list_vars.append(('_signature', sig))
354
355 if list_vars:
356 if url_encode:
357 other += '?%s' % urllib.urlencode(list_vars)
358 else:
359 other += '?%s' % '&'.join(['%s=%s' % var[:2] for var in list_vars])
360 if anchor:
361 if url_encode:
362 other += '#' + urllib.quote(str(anchor))
363 else:
364 other += '#' + (str(anchor))
365 if extension:
366 function += '.' + extension
367
368 if regex_crlf.search(join([application, controller, function, other])):
369 raise SyntaxError('CRLF Injection Detected')
370
371 url = url_out(r, env, application, controller, function,
372 args, other, scheme, host, port)
373 return url
374
375
376 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
377 """
378 Verifies that a request's args & vars have not been tampered with by the user
379
380 :param request: web2py's request object
381 :param hmac_key: the key to authenticate with, must be the same one previously
382 used when calling URL()
383 :param hash_vars: which vars to include in our hashing. (Optional)
384 Only uses the 1st value currently
385 True (or undefined) means all, False none,
386 an iterable just the specified keys
387
388 do not call directly. Use instead:
389
390 URL.verify(hmac_key='...')
391
392 the key has to match the one used to generate the URL.
393
394 >>> r = Storage()
395 >>> gv = Storage(p=(1,3),q=2,_signature='a32530f0d0caa80964bb92aad2bedf8a4486a31f')
396 >>> r.update(dict(application='a', controller='c', function='f', extension='html'))
397 >>> r['args'] = ['x', 'y', 'z']
398 >>> r['get_vars'] = gv
399 >>> verifyURL(r, 'key')
400 True
401 >>> verifyURL(r, 'kay')
402 False
403 >>> r.get_vars.p = (3, 1)
404 >>> verifyURL(r, 'key')
405 True
406 >>> r.get_vars.p = (3, 2)
407 >>> verifyURL(r, 'key')
408 False
409
410 """
411
412 if not '_signature' in request.get_vars:
413 return False
414
415
416 if user_signature:
417 from globals import current
418 if not current.session or not current.session.auth:
419 return False
420 hmac_key = current.session.auth.hmac_key
421 if not hmac_key:
422 return False
423
424
425 original_sig = request.get_vars._signature
426
427
428 vars, args = request.get_vars, request.args
429
430
431 request.get_vars.pop('_signature')
432
433
434
435
436 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
437 h_args = '/%s/%s/%s.%s%s' % (request.application,
438 request.controller,
439 request.function,
440 request.extension,
441 other)
442
443
444
445
446 list_vars = []
447 for (key, vals) in sorted(vars.items()):
448 if not isinstance(vals, (list, tuple)):
449 vals = [vals]
450 for val in vals:
451 list_vars.append((key, val))
452
453
454 if hash_vars is True:
455 h_vars = list_vars
456 elif hash_vars is False:
457 h_vars = ''
458 else:
459
460 try:
461 if hash_vars and not isinstance(hash_vars, (list, tuple)):
462 hash_vars = [hash_vars]
463 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
464 except:
465
466 return False
467
468 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
469
470
471 sig = simple_hash(message, str(hmac_key), salt or '', digest_alg='sha1')
472
473
474
475 request.get_vars['_signature'] = original_sig
476
477
478
479
480 return compare(original_sig, sig)
481
482 URL.verify = verifyURL
483
484 ON = True
488 """
489 Abstract root for all Html components
490 """
491
492
493
495 raise NotImplementedError
496
498 return CAT(*[self for i in range(n)])
499
501 if isinstance(self, CAT):
502 components = self.components
503 else:
504 components = [self]
505 if isinstance(other, CAT):
506 components += other.components
507 else:
508 components += [other]
509 return CAT(*components)
510
512 """ add a class to _class attribute """
513 c = self['_class']
514 classes = (set(c.split()) if c else set()) | set(name.split())
515 self['_class'] = ' '.join(classes) if classes else None
516 return self
517
519 """ remove a class from _class attribute """
520 c = self['_class']
521 classes = (set(c.split()) if c else set()) - set(name.split())
522 self['_class'] = ' '.join(classes) if classes else None
523 return self
524
525 -class XML(XmlComponent):
526 """
527 use it to wrap a string that contains XML/HTML so that it will not be
528 escaped by the template
529
530 example:
531
532 >>> XML('<h1>Hello</h1>').xml()
533 '<h1>Hello</h1>'
534 """
535
536 - def __init__(
537 self,
538 text,
539 sanitize=False,
540 permitted_tags=[
541 'a',
542 'b',
543 'blockquote',
544 'br/',
545 'i',
546 'li',
547 'ol',
548 'ul',
549 'p',
550 'cite',
551 'code',
552 'pre',
553 'img/',
554 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
555 'table', 'tr', 'td', 'div',
556 'strong','span',
557 ],
558 allowed_attributes={
559 'a': ['href', 'title', 'target'],
560 'img': ['src', 'alt'],
561 'blockquote': ['type'],
562 'td': ['colspan'],
563 },
564 ):
565 """
566 :param text: the XML text
567 :param sanitize: sanitize text using the permitted tags and allowed
568 attributes (default False)
569 :param permitted_tags: list of permitted tags (default: simple list of
570 tags)
571 :param allowed_attributes: dictionary of allowed attributed (default
572 for A, IMG and BlockQuote).
573 The key is the tag; the value is a list of allowed attributes.
574 """
575
576 if sanitize:
577 text = sanitizer.sanitize(text, permitted_tags,
578 allowed_attributes)
579 if isinstance(text, unicode):
580 text = text.encode('utf8', 'xmlcharrefreplace')
581 elif not isinstance(text, str):
582 text = str(text)
583 self.text = text
584
587
590
592 return '%s%s' % (self, other)
593
595 return '%s%s' % (other, self)
596
598 return cmp(str(self), str(other))
599
601 return hash(str(self))
602
603
604
605
606
609
611 return str(self)[i:j]
612
614 for c in str(self):
615 yield c
616
618 return len(str(self))
619
621 """
622 return the text stored by the XML object rendered by the render function
623 """
624 if render:
625 return render(self.text, None, {})
626 return self.text
627
629 """
630 to be considered experimental since the behavior of this method is questionable
631 another options could be TAG(self.text).elements(*args,**kargs)
632 """
633 return []
634
639 return marshal.loads(data)
640
644 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
645
646
647 -class DIV(XmlComponent):
648 """
649 HTML helper, for easy generating and manipulating a DOM structure.
650 Little or no validation is done.
651
652 Behaves like a dictionary regarding updating of attributes.
653 Behaves like a list regarding inserting/appending components.
654
655 example::
656
657 >>> DIV('hello', 'world', _style='color:red;').xml()
658 '<div style=\"color:red;\">helloworld</div>'
659
660 all other HTML helpers are derived from DIV.
661
662 _something=\"value\" attributes are transparently translated into
663 something=\"value\" HTML attributes
664 """
665
666
667
668
669 tag = 'div'
670
671 - def __init__(self, *components, **attributes):
672 """
673 :param *components: any components that should be nested in this element
674 :param **attributes: any attributes you want to give to this element
675
676 :raises SyntaxError: when a stand alone tag receives components
677 """
678
679 if self.tag[-1:] == '/' and components:
680 raise SyntaxError('<%s> tags cannot have components'
681 % self.tag)
682 if len(components) == 1 and isinstance(components[0], (list, tuple)):
683 self.components = list(components[0])
684 else:
685 self.components = list(components)
686 self.attributes = attributes
687 self._fixup()
688
689 self.parent = None
690 for c in self.components:
691 self._setnode(c)
692 self._postprocessing()
693
695 """
696 dictionary like updating of the tag attributes
697 """
698
699 for (key, value) in kargs.iteritems():
700 self[key] = value
701 return self
702
704 """
705 list style appending of components
706
707 >>> a=DIV()
708 >>> a.append(SPAN('x'))
709 >>> print a
710 <div><span>x</span></div>
711 """
712 self._setnode(value)
713 ret = self.components.append(value)
714 self._fixup()
715 return ret
716
718 """
719 list style inserting of components
720
721 >>> a=DIV()
722 >>> a.insert(0,SPAN('x'))
723 >>> print a
724 <div><span>x</span></div>
725 """
726 self._setnode(value)
727 ret = self.components.insert(i, value)
728 self._fixup()
729 return ret
730
732 """
733 gets attribute with name 'i' or component #i.
734 If attribute 'i' is not found returns None
735
736 :param i: index
737 if i is a string: the name of the attribute
738 otherwise references to number of the component
739 """
740
741 if isinstance(i, str):
742 try:
743 return self.attributes[i]
744 except KeyError:
745 return None
746 else:
747 return self.components[i]
748
750 """
751 sets attribute with name 'i' or component #i.
752
753 :param i: index
754 if i is a string: the name of the attribute
755 otherwise references to number of the component
756 :param value: the new value
757 """
758 self._setnode(value)
759 if isinstance(i, (str, unicode)):
760 self.attributes[i] = value
761 else:
762 self.components[i] = value
763
765 """
766 deletes attribute with name 'i' or component #i.
767
768 :param i: index
769 if i is a string: the name of the attribute
770 otherwise references to number of the component
771 """
772
773 if isinstance(i, str):
774 del self.attributes[i]
775 else:
776 del self.components[i]
777
779 """
780 returns the number of included components
781 """
782 return len(self.components)
783
785 """
786 always return True
787 """
788 return True
789
791 """
792 Handling of provided components.
793
794 Nothing to fixup yet. May be overridden by subclasses,
795 eg for wrapping some components in another component or blocking them.
796 """
797 return
798
799 - def _wrap_components(self, allowed_parents,
800 wrap_parent=None,
801 wrap_lambda=None):
802 """
803 helper for _fixup. Checks if a component is in allowed_parents,
804 otherwise wraps it in wrap_parent
805
806 :param allowed_parents: (tuple) classes that the component should be an
807 instance of
808 :param wrap_parent: the class to wrap the component in, if needed
809 :param wrap_lambda: lambda to use for wrapping, if needed
810
811 """
812 components = []
813 for c in self.components:
814 if isinstance(c, allowed_parents):
815 pass
816 elif wrap_lambda:
817 c = wrap_lambda(c)
818 else:
819 c = wrap_parent(c)
820 if isinstance(c, DIV):
821 c.parent = self
822 components.append(c)
823 self.components = components
824
825 - def _postprocessing(self):
826 """
827 Handling of attributes (normally the ones not prefixed with '_').
828
829 Nothing to postprocess yet. May be overridden by subclasses
830 """
831 return
832
833 - def _traverse(self, status, hideerror=False):
834
835 newstatus = status
836 for c in self.components:
837 if hasattr(c, '_traverse') and callable(c._traverse):
838 c.vars = self.vars
839 c.request_vars = self.request_vars
840 c.errors = self.errors
841 c.latest = self.latest
842 c.session = self.session
843 c.formname = self.formname
844 c['hideerror'] = hideerror or \
845 self.attributes.get('hideerror', False)
846 newstatus = c._traverse(status, hideerror) and newstatus
847
848
849
850
851 name = self['_name']
852 if newstatus:
853 newstatus = self._validate()
854 self._postprocessing()
855 elif 'old_value' in self.attributes:
856 self['value'] = self['old_value']
857 self._postprocessing()
858 elif name and name in self.vars:
859 self['value'] = self.vars[name]
860 self._postprocessing()
861 if name:
862 self.latest[name] = self['value']
863 return newstatus
864
866 """
867 nothing to validate yet. May be overridden by subclasses
868 """
869 return True
870
872 if isinstance(value, DIV):
873 value.parent = self
874
876 """
877 helper for xml generation. Returns separately:
878 - the component attributes
879 - the generated xml of the inner components
880
881 Component attributes start with an underscore ('_') and
882 do not have a False or None value. The underscore is removed.
883 A value of True is replaced with the attribute name.
884
885 :returns: tuple: (attributes, components)
886 """
887
888
889
890 attr = []
891 for key, value in self.attributes.iteritems():
892 if key[:1] != '_':
893 continue
894 name = key[1:]
895 if value is True:
896 value = name
897 elif value is False or value is None:
898 continue
899 attr.append((name, value))
900 data = self.attributes.get('data',{})
901 for key, value in data.iteritems():
902 name = 'data-' + key
903 value = data[key]
904 attr.append((name,value))
905 attr.sort()
906 fa = ''
907 for name,value in attr:
908 fa += ' %s="%s"' % (name, xmlescape(value, True))
909
910 co = join([xmlescape(component) for component in
911 self.components])
912
913 return (fa, co)
914
916 """
917 generates the xml for this component.
918 """
919
920 (fa, co) = self._xml()
921
922 if not self.tag:
923 return co
924
925 if self.tag[-1:] == '/':
926
927 return '<%s%s />' % (self.tag[:-1], fa)
928
929
930 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
931
933 """
934 str(COMPONENT) returns equals COMPONENT.xml()
935 """
936
937 return self.xml()
938
940 """
941 return the text stored by the DIV object rendered by the render function
942 the render function must take text, tagname, and attributes
943 render=None is equivalent to render=lambda text, tag, attr: text
944
945 >>> markdown = lambda text,tag=None,attributes={}: \
946 {None: re.sub('\s+',' ',text), \
947 'h1':'#'+text+'\\n\\n', \
948 'p':text+'\\n'}.get(tag,text)
949 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
950 >>> a.flatten(markdown)
951 '#Header\\n\\nthis is a test\\n'
952 """
953
954 text = ''
955 for c in self.components:
956 if isinstance(c, XmlComponent):
957 s = c.flatten(render)
958 elif render:
959 s = render(str(c))
960 else:
961 s = str(c)
962 text += s
963 if render:
964 text = render(text, self.tag, self.attributes)
965 return text
966
967 regex_tag = re.compile('^[\w\-\:]+')
968 regex_id = re.compile('#([\w\-]+)')
969 regex_class = re.compile('\.([\w\-]+)')
970 regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]')
971
973 """
974 find all component that match the supplied attribute dictionary,
975 or None if nothing could be found
976
977 All components of the components are searched.
978
979 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
980 >>> for c in a.elements('span',first_only=True): c[0]='z'
981 >>> print a
982 <div><div><span>z</span>3<div><span>y</span></div></div></div>
983 >>> for c in a.elements('span'): c[0]='z'
984 >>> print a
985 <div><div><span>z</span>3<div><span>z</span></div></div></div>
986
987 It also supports a syntax compatible with jQuery
988
989 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
990 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
991 hello
992 world
993 >>> for e in a.elements('#1-1'): print e.flatten()
994 hello
995 >>> a.elements('a[u:v=$]')[0].xml()
996 '<a id="1-1" u:v="$">hello</a>'
997
998 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
999 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
1000 >>> a.xml()
1001 '<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>'
1002
1003 Elements that are matched can also be replaced or removed by specifying
1004 a "replace" argument (note, a list of the original matching elements
1005 is still returned as usual).
1006
1007 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1008 >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
1009 >>> print a
1010 <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
1011
1012 "replace" can be a callable, which will be passed the original element and
1013 should return a new element to replace it.
1014
1015 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1016 >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
1017 >>> print a
1018 <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
1019
1020 If replace=None, matching elements will be removed completely.
1021
1022 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1023 >>> b = a.elements('span', find='y', replace=None)
1024 >>> print a
1025 <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
1026
1027 If a "find_text" argument is specified, elements will be searched for text
1028 components that match find_text, and any matching text components will be
1029 replaced (find_text is ignored if "replace" is not also specified).
1030 Like the "find" argument, "find_text" can be a string or a compiled regex.
1031
1032 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1033 >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
1034 >>> print a
1035 <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
1036
1037 If other attributes are specified along with find_text, then only components
1038 that match the specified attributes will be searched for find_text.
1039
1040 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
1041 >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
1042 >>> print a
1043 <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
1044 """
1045 if len(args) == 1:
1046 args = [a.strip() for a in args[0].split(',')]
1047 if len(args) > 1:
1048 subset = [self.elements(a, **kargs) for a in args]
1049 return reduce(lambda a, b: a + b, subset, [])
1050 elif len(args) == 1:
1051 items = args[0].split()
1052 if len(items) > 1:
1053 subset = [a.elements(' '.join(
1054 items[1:]), **kargs) for a in self.elements(items[0])]
1055 return reduce(lambda a, b: a + b, subset, [])
1056 else:
1057 item = items[0]
1058 if '#' in item or '.' in item or '[' in item:
1059 match_tag = self.regex_tag.search(item)
1060 match_id = self.regex_id.search(item)
1061 match_class = self.regex_class.search(item)
1062 match_attr = self.regex_attr.finditer(item)
1063 args = []
1064 if match_tag:
1065 args = [match_tag.group()]
1066 if match_id:
1067 kargs['_id'] = match_id.group(1)
1068 if match_class:
1069 kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' %
1070 match_class.group(1).replace('-', '\\-').replace(':', '\\:'))
1071 for item in match_attr:
1072 kargs['_' + item.group(1)] = item.group(2)
1073 return self.elements(*args, **kargs)
1074
1075 matches = []
1076
1077
1078 check = True
1079 tag = getattr(self, 'tag').replace('/', '')
1080 if args and tag not in args:
1081 check = False
1082 for (key, value) in kargs.iteritems():
1083 if key not in ['first_only', 'replace', 'find_text']:
1084 if isinstance(value, (str, int)):
1085 if self[key] != str(value):
1086 check = False
1087 elif key in self.attributes:
1088 if not value.search(str(self[key])):
1089 check = False
1090 else:
1091 check = False
1092 if 'find' in kargs:
1093 find = kargs['find']
1094 is_regex = not isinstance(find, (str, int))
1095 for c in self.components:
1096 if (isinstance(c, str) and ((is_regex and find.search(c)) or
1097 (str(find) in c))):
1098 check = True
1099
1100 if check:
1101 matches.append(self)
1102
1103 first_only = kargs.get('first_only', False)
1104 replace = kargs.get('replace', False)
1105 find_text = replace is not False and kargs.get('find_text', False)
1106 is_regex = not isinstance(find_text, (str, int, bool))
1107 find_components = not (check and first_only)
1108
1109 def replace_component(i):
1110 if replace is None:
1111 del self[i]
1112 elif callable(replace):
1113 self[i] = replace(self[i])
1114 else:
1115 self[i] = replace
1116
1117 if find_text or find_components:
1118 for i, c in enumerate(self.components):
1119 if check and find_text and isinstance(c, str) and \
1120 ((is_regex and find_text.search(c)) or (str(find_text) in c)):
1121 replace_component(i)
1122 if find_components and isinstance(c, XmlComponent):
1123 child_matches = c.elements(*args, **kargs)
1124 if len(child_matches):
1125 if not find_text and replace is not False and child_matches[0] is c:
1126 replace_component(i)
1127 if first_only:
1128 return child_matches
1129 matches.extend(child_matches)
1130 return matches
1131
1132 - def element(self, *args, **kargs):
1133 """
1134 find the first component that matches the supplied attribute dictionary,
1135 or None if nothing could be found
1136
1137 Also the components of the components are searched.
1138 """
1139 kargs['first_only'] = True
1140 elements = self.elements(*args, **kargs)
1141 if not elements:
1142
1143 return None
1144 return elements[0]
1145
1147 """
1148 find all sibling components that match the supplied argument list
1149 and attribute dictionary, or None if nothing could be found
1150 """
1151 sibs = [s for s in self.parent.components if not s == self]
1152 matches = []
1153 first_only = False
1154 if 'first_only' in kargs:
1155 first_only = kargs.pop('first_only')
1156 for c in sibs:
1157 try:
1158 check = True
1159 tag = getattr(c, 'tag').replace("/", "")
1160 if args and tag not in args:
1161 check = False
1162 for (key, value) in kargs.iteritems():
1163 if c[key] != value:
1164 check = False
1165 if check:
1166 matches.append(c)
1167 if first_only:
1168 break
1169 except:
1170 pass
1171 return matches
1172
1173 - def sibling(self, *args, **kargs):
1174 """
1175 find the first sibling component that match the supplied argument list
1176 and attribute dictionary, or None if nothing could be found
1177 """
1178 kargs['first_only'] = True
1179 sibs = self.siblings(*args, **kargs)
1180 if not sibs:
1181 return None
1182 return sibs[0]
1183
1184
1185 -class CAT(DIV):
1188
1191 return cPickle.loads(data)
1192
1195 d = DIV()
1196 d.__dict__ = data.__dict__
1197 marshal_dump = cPickle.dumps(d)
1198 return (TAG_unpickler, (marshal_dump,))
1199
1205
1206 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
1209
1210 """
1211 TAG factory example::
1212
1213 >>> print TAG.first(TAG.second('test'), _key = 3)
1214 <first key=\"3\"><second>test</second></first>
1215
1216 """
1217
1220
1227
1230
1231 TAG = __TAG__()
1232
1233
1234 -class HTML(DIV):
1235 """
1236 There are four predefined document type definitions.
1237 They can be specified in the 'doctype' parameter:
1238
1239 -'strict' enables strict doctype
1240 -'transitional' enables transitional doctype (default)
1241 -'frameset' enables frameset doctype
1242 -'html5' enables HTML 5 doctype
1243 -any other string will be treated as user's own doctype
1244
1245 'lang' parameter specifies the language of the document.
1246 Defaults to 'en'.
1247
1248 See also :class:`DIV`
1249 """
1250
1251 tag = 'html'
1252
1253 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1254 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1255 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1256 html5 = '<!DOCTYPE HTML>\n'
1257
1259 lang = self['lang']
1260 if not lang:
1261 lang = 'en'
1262 self.attributes['_lang'] = lang
1263 doctype = self['doctype']
1264 if doctype is None:
1265 doctype = self.transitional
1266 elif doctype == 'strict':
1267 doctype = self.strict
1268 elif doctype == 'transitional':
1269 doctype = self.transitional
1270 elif doctype == 'frameset':
1271 doctype = self.frameset
1272 elif doctype == 'html5':
1273 doctype = self.html5
1274 elif doctype == '':
1275 doctype = ''
1276 else:
1277 doctype = '%s\n' % doctype
1278 (fa, co) = self._xml()
1279 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1280
1283 """
1284 This is XHTML version of the HTML helper.
1285
1286 There are three predefined document type definitions.
1287 They can be specified in the 'doctype' parameter:
1288
1289 -'strict' enables strict doctype
1290 -'transitional' enables transitional doctype (default)
1291 -'frameset' enables frameset doctype
1292 -any other string will be treated as user's own doctype
1293
1294 'lang' parameter specifies the language of the document and the xml document.
1295 Defaults to 'en'.
1296
1297 'xmlns' parameter specifies the xml namespace.
1298 Defaults to 'http://www.w3.org/1999/xhtml'.
1299
1300 See also :class:`DIV`
1301 """
1302
1303 tag = 'html'
1304
1305 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1306 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1307 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1308 xmlns = 'http://www.w3.org/1999/xhtml'
1309
1311 xmlns = self['xmlns']
1312 if xmlns:
1313 self.attributes['_xmlns'] = xmlns
1314 else:
1315 self.attributes['_xmlns'] = self.xmlns
1316 lang = self['lang']
1317 if not lang:
1318 lang = 'en'
1319 self.attributes['_lang'] = lang
1320 self.attributes['_xml:lang'] = lang
1321 doctype = self['doctype']
1322 if doctype:
1323 if doctype == 'strict':
1324 doctype = self.strict
1325 elif doctype == 'transitional':
1326 doctype = self.transitional
1327 elif doctype == 'frameset':
1328 doctype = self.frameset
1329 else:
1330 doctype = '%s\n' % doctype
1331 else:
1332 doctype = self.transitional
1333 (fa, co) = self._xml()
1334 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1335
1336
1337 -class HEAD(DIV):
1340
1345
1350
1351
1352 -class LINK(DIV):
1355
1358
1359 tag = 'script'
1360
1362 (fa, co) = self._xml()
1363
1364 co = '\n'.join([str(component) for component in
1365 self.components])
1366 if co:
1367
1368
1369
1370
1371 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1372 else:
1373 return DIV.xml(self)
1374
1377
1378 tag = 'style'
1379
1381 (fa, co) = self._xml()
1382
1383 co = '\n'.join([str(component) for component in
1384 self.components])
1385 if co:
1386
1387
1388
1389 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1390 else:
1391 return DIV.xml(self)
1392
1393
1394 -class IMG(DIV):
1397
1398
1399 -class SPAN(DIV):
1402
1403
1404 -class BODY(DIV):
1407
1408
1409 -class H1(DIV):
1412
1413
1414 -class H2(DIV):
1417
1418
1419 -class H3(DIV):
1422
1423
1424 -class H4(DIV):
1427
1428
1429 -class H5(DIV):
1432
1433
1434 -class H6(DIV):
1437
1440 """
1441 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1442
1443 see also :class:`DIV`
1444 """
1445
1446 tag = 'p'
1447
1449 text = DIV.xml(self)
1450 if self['cr2br']:
1451 text = text.replace('\n', '<br />')
1452 return text
1453
1458
1463
1464
1465 -class BR(DIV):
1468
1469
1470 -class HR(DIV):
1473
1476
1477 tag = 'a'
1478
1480 if not self.components and self['_href']:
1481 self.append(self['_href'])
1482 if self['delete']:
1483 d = "jQuery(this).closest('%s').remove();" % self['delete']
1484 else:
1485 d = ''
1486 if self['component']:
1487 self['_onclick'] = "web2py_component('%s','%s');%sreturn false;" % \
1488 (self['component'], self['target'] or '', d)
1489 self['_href'] = self['_href'] or '#null'
1490 elif self['callback']:
1491 returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) {e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault();}"
1492 if d and not self['noconfirm']:
1493 self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want to delete this object?')){ajax('%s',[],'%s');%s};%s" % \
1494 (self['callback'], self['target'] or '', d, returnfalse)
1495 else:
1496 self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \
1497 (self['callback'], self['target'] or '', d)
1498 self['_href'] = self['_href'] or '#null'
1499 elif self['cid']:
1500 pre = self['pre_call'] + ';' if self['pre_call'] else ''
1501 self['_onclick'] = '%sweb2py_component("%s","%s");%sreturn false;' % \
1502 (pre,self['_href'], self['cid'], d)
1503 return DIV.xml(self)
1504
1509
1510
1511 -class EM(DIV):
1514
1519
1520
1521 -class TT(DIV):
1524
1525
1526 -class PRE(DIV):
1529
1534
1535
1536 -class CODE(DIV):
1537
1538 """
1539 displays code in HTML with syntax highlighting.
1540
1541 :param attributes: optional attributes:
1542
1543 - language: indicates the language, otherwise PYTHON is assumed
1544 - link: can provide a link
1545 - styles: for styles
1546
1547 Example::
1548
1549 {{=CODE(\"print 'hello world'\", language='python', link=None,
1550 counter=1, styles={}, highlight_line=None)}}
1551
1552
1553 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1554 \"web2py\", \"html\".
1555 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1556 \"html_plain\" doesn't.
1557
1558 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1559 the online docs.
1560
1561 the counter is used for line numbering, counter can be None or a prompt
1562 string.
1563 """
1564
1566 language = self['language'] or 'PYTHON'
1567 link = self['link']
1568 counter = self.attributes.get('counter', 1)
1569 highlight_line = self.attributes.get('highlight_line', None)
1570 context_lines = self.attributes.get('context_lines', None)
1571 styles = self['styles'] or {}
1572 return highlight(
1573 join(self.components),
1574 language=language,
1575 link=link,
1576 counter=counter,
1577 styles=styles,
1578 attributes=self.attributes,
1579 highlight_line=highlight_line,
1580 context_lines=context_lines,
1581 )
1582
1587
1588
1589 -class LI(DIV):
1592
1593
1594 -class UL(DIV):
1595 """
1596 UL Component.
1597
1598 If subcomponents are not LI-components they will be wrapped in a LI
1599
1600 see also :class:`DIV`
1601 """
1602
1603 tag = 'ul'
1604
1607
1612
1613
1614 -class TD(DIV):
1617
1618
1619 -class TH(DIV):
1622
1623
1624 -class TR(DIV):
1625 """
1626 TR Component.
1627
1628 If subcomponents are not TD/TH-components they will be wrapped in a TD
1629
1630 see also :class:`DIV`
1631 """
1632
1633 tag = 'tr'
1634
1637
1640
1641 tag = 'thead'
1642
1645
1646
1647 -class TBODY(DIV):
1648
1649 tag = 'tbody'
1650
1653
1661
1662
1663 -class COL(DIV):
1666
1669
1670 tag = 'colgroup'
1671
1674 """
1675 TABLE Component.
1676
1677 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1678 they will be wrapped in a TR
1679
1680 see also :class:`DIV`
1681 """
1682
1683 tag = 'table'
1684
1687
1692
1697
1820
1821
1822 -class TEXTAREA(INPUT):
1823
1824 """
1825 example::
1826
1827 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1828
1829 'blah blah blah ...' will be the content of the textarea field.
1830 """
1831
1832 tag = 'textarea'
1833
1834 - def _postprocessing(self):
1835 if not '_rows' in self.attributes:
1836 self['_rows'] = 10
1837 if not '_cols' in self.attributes:
1838 self['_cols'] = 40
1839 if not self['value'] is None:
1840 self.components = [self['value']]
1841 elif self.components:
1842 self['value'] = self.components[0]
1843
1846
1847 tag = 'option'
1848
1850 if not '_value' in self.attributes:
1851 self.attributes['_value'] = str(self.components[0])
1852
1857
1860
1861 tag = 'optgroup'
1862
1864 components = []
1865 for c in self.components:
1866 if isinstance(c, OPTION):
1867 components.append(c)
1868 else:
1869 components.append(OPTION(c, _value=str(c)))
1870 self.components = components
1871
1874
1875 """
1876 example::
1877
1878 >>> from validators import IS_IN_SET
1879 >>> SELECT('yes', 'no', _name='selector', value='yes',
1880 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1881 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1882
1883 """
1884
1885 tag = 'select'
1886
1888 components = []
1889 for c in self.components:
1890 if isinstance(c, (OPTION, OPTGROUP)):
1891 components.append(c)
1892 else:
1893 components.append(OPTION(c, _value=str(c)))
1894 self.components = components
1895
1896 - def _postprocessing(self):
1897 component_list = []
1898 for c in self.components:
1899 if isinstance(c, OPTGROUP):
1900 component_list.append(c.components)
1901 else:
1902 component_list.append([c])
1903 options = itertools.chain(*component_list)
1904
1905 value = self['value']
1906 if not value is None:
1907 if not self['_multiple']:
1908 for c in options:
1909 if ((value is not None) and
1910 (str(c['_value']) == str(value))):
1911 c['_selected'] = 'selected'
1912 else:
1913 c['_selected'] = None
1914 else:
1915 if isinstance(value, (list, tuple)):
1916 values = [str(item) for item in value]
1917 else:
1918 values = [str(value)]
1919 for c in options:
1920 if ((value is not None) and
1921 (str(c['_value']) in values)):
1922 c['_selected'] = 'selected'
1923 else:
1924 c['_selected'] = None
1925
1928
1929 tag = 'fieldset'
1930
1935
2262 return flatten(d)
2263
2268
2273
2278
2281
2282 """
2283 example::
2284
2285 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
2286 '<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>'
2287
2288 turns any list, dictionary, etc into decent looking html.
2289 Two special attributes are
2290 :sorted: a function that takes the dict and returned sorted keys
2291 :keyfilter: a funciton that takes a key and returns its representation
2292 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
2293 """
2294
2295 tag = 'div'
2296
2297 @staticmethod
2299 if key[:1] == '_':
2300 return None
2301 return key
2302
2303 - def __init__(self, component, **attributes):
2304 self.components = [component]
2305 self.attributes = attributes
2306 sorter = attributes.get('sorted', sorted)
2307 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
2308 components = []
2309 attributes = copy.copy(self.attributes)
2310 level = attributes['level'] = attributes.get('level', 6) - 1
2311 if '_class' in attributes:
2312 attributes['_class'] += 'i'
2313 if level == 0:
2314 return
2315 for c in self.components:
2316 if hasattr(c, 'value') and not callable(c.value):
2317 if c.value:
2318 components.append(c.value)
2319 if hasattr(c, 'xml') and callable(c.xml):
2320 components.append(c)
2321 continue
2322 elif hasattr(c, 'keys') and callable(c.keys):
2323 rows = []
2324 try:
2325 keys = (sorter and sorter(c)) or c
2326 for key in keys:
2327 if isinstance(key, (str, unicode)) and keyfilter:
2328 filtered_key = keyfilter(key)
2329 else:
2330 filtered_key = str(key)
2331 if filtered_key is None:
2332 continue
2333 value = c[key]
2334 if isinstance(value, types.LambdaType):
2335 continue
2336 rows.append(
2337 TR(
2338 TD(filtered_key, _style='font-weight:bold;vertical-align:top'),
2339 TD(':', _valign='top'),
2340 TD(BEAUTIFY(value, **attributes))))
2341 components.append(TABLE(*rows, **attributes))
2342 continue
2343 except:
2344 pass
2345 if isinstance(c, str):
2346 components.append(str(c))
2347 elif isinstance(c, unicode):
2348 components.append(c.encode('utf8'))
2349 elif isinstance(c, (list, tuple)):
2350 items = [TR(TD(BEAUTIFY(item, **attributes)))
2351 for item in c]
2352 components.append(TABLE(*items, **attributes))
2353 elif isinstance(c, cgi.FieldStorage):
2354 components.append('FieldStorage object')
2355 else:
2356 components.append(repr(c))
2357 self.components = components
2358
2361 """
2362 Used to build menus
2363
2364 Optional arguments
2365 _class: defaults to 'web2py-menu web2py-menu-vertical'
2366 ul_class: defaults to 'web2py-menu-vertical'
2367 li_class: defaults to 'web2py-menu-expand'
2368 li_first: defaults to 'web2py-menu-first'
2369 li_last: defaults to 'web2py-menu-last'
2370
2371 Example:
2372 menu = MENU([['name', False, URL(...), [submenu]], ...])
2373 {{=menu}}
2374 """
2375
2376 tag = 'ul'
2377
2379 self.data = data
2380 self.attributes = args
2381 self.components = []
2382 if not '_class' in self.attributes:
2383 self['_class'] = 'web2py-menu web2py-menu-vertical'
2384 if not 'ul_class' in self.attributes:
2385 self['ul_class'] = 'web2py-menu-vertical'
2386 if not 'li_class' in self.attributes:
2387 self['li_class'] = 'web2py-menu-expand'
2388 if not 'li_first' in self.attributes:
2389 self['li_first'] = 'web2py-menu-first'
2390 if not 'li_last' in self.attributes:
2391 self['li_last'] = 'web2py-menu-last'
2392 if not 'li_active' in self.attributes:
2393 self['li_active'] = 'web2py-menu-active'
2394 if not 'mobile' in self.attributes:
2395 self['mobile'] = False
2396
2398 if level == 0:
2399 ul = UL(**self.attributes)
2400 else:
2401 ul = UL(_class=self['ul_class'])
2402 for item in data:
2403 if isinstance(item,LI):
2404 ul.append(item)
2405 else:
2406 (name, active, link) = item[:3]
2407 if isinstance(link, DIV):
2408 li = LI(link)
2409 elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
2410 li = LI(DIV(name))
2411 elif isinstance(link,dict):
2412 li = LI(A(name, **link))
2413 elif link:
2414 li = LI(A(name, _href=link))
2415 elif not link and isinstance(name, A):
2416 li = LI(name)
2417 else:
2418 li = LI(A(name, _href='#',
2419 _onclick='javascript:void(0);return false;'))
2420 if level == 0 and item == data[0]:
2421 li['_class'] = self['li_first']
2422 elif level == 0 and item == data[-1]:
2423 li['_class'] = self['li_last']
2424 if len(item) > 3 and item[3]:
2425 li['_class'] = self['li_class']
2426 li.append(self.serialize(item[3], level + 1))
2427 if active or ('active_url' in self.attributes and self['active_url'] == link):
2428 if li['_class']:
2429 li['_class'] = li['_class'] + ' ' + self['li_active']
2430 else:
2431 li['_class'] = self['li_active']
2432 if len(item) <= 4 or item[4] == True:
2433 ul.append(li)
2434 return ul
2435
2448
2454
2455
2456 -def embed64(
2457 filename=None,
2458 file=None,
2459 data=None,
2460 extension='image/gif',
2461 ):
2462 """
2463 helper to encode the provided (binary) data into base64.
2464
2465 :param filename: if provided, opens and reads this file in 'rb' mode
2466 :param file: if provided, reads this file
2467 :param data: if provided, uses the provided data
2468 """
2469
2470 if filename and os.path.exists(file):
2471 fp = open(filename, 'rb')
2472 data = fp.read()
2473 fp.close()
2474 data = base64.b64encode(data)
2475 return 'data:%s;base64,%s' % (extension, data)
2476
2479 """
2480 Example:
2481
2482 >>> from validators import *
2483 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
2484 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2485 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
2486 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2487 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
2488 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2489 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2490 <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>
2491 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2492 >>> print form.xml()
2493 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2494 >>> print form.accepts({'myvar':'34'}, formname=None)
2495 False
2496 >>> print form.xml()
2497 <form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error_wrapper"><div class="error" id="myvar__error">invalid expression</div></div></form>
2498 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2499 True
2500 >>> print form.xml()
2501 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2502 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2503 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2504 True
2505 >>> print form.xml()
2506 <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>
2507 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2508 >>> print form.accepts({'myvar':'as df'}, formname=None)
2509 False
2510 >>> print form.xml()
2511 <form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="as df" /><div class="error_wrapper"><div class="error" id="myvar__error">only alphanumeric!</div></div></form>
2512 >>> session={}
2513 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
2514 >>> isinstance(form.as_dict(), dict)
2515 True
2516 >>> form.as_dict(flat=True).has_key("vars")
2517 True
2518 >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
2519 True
2520 >>> if form.accepts({}, session,formname=None): print 'passed'
2521 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2522 """
2523 pass
2524
2527 """
2528 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2529 obj.tree contains the root of the tree, and tree can be manipulated
2530
2531 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2532 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2533 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2534 '<div>a<span>b</span></div>c'
2535 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2536 >>> tree.element(_a='b')['_c']=5
2537 >>> str(tree)
2538 'hello<div a="b" c="5">world</div>'
2539 """
2540 - def __init__(self, text, closed=('input', 'link')):
2541 HTMLParser.__init__(self)
2542 self.tree = self.parent = TAG['']()
2543 self.closed = closed
2544 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
2545 self.last = None
2546 self.feed(text)
2547
2549 if tagname.upper() in self.tags:
2550 tag = eval(tagname.upper())
2551 else:
2552 if tagname in self.closed:
2553 tagname += '/'
2554 tag = TAG[tagname]()
2555 for key, value in attrs:
2556 tag['_' + key] = value
2557 tag.parent = self.parent
2558 self.parent.append(tag)
2559 if not tag.tag.endswith('/'):
2560 self.parent = tag
2561 else:
2562 self.last = tag.tag[:-1]
2563
2565 if not isinstance(data, unicode):
2566 try:
2567 data = data.decode('utf8')
2568 except:
2569 data = data.decode('latin1')
2570 self.parent.append(data.encode('utf8', 'xmlcharref'))
2571
2577
2580
2582
2583 if tagname == self.last:
2584 return
2585 while True:
2586 try:
2587 parent_tagname = self.parent.tag
2588 self.parent = self.parent.parent
2589 except:
2590 raise RuntimeError("unable to balance tag %s" % tagname)
2591 if parent_tagname[:len(tagname)] == tagname: break
2592
2595 attr = attr or {}
2596 if tag is None:
2597 return re.sub('\s+', ' ', text)
2598 if tag == 'br':
2599 return '\n\n'
2600 if tag == 'h1':
2601 return '#' + text + '\n\n'
2602 if tag == 'h2':
2603 return '#' * 2 + text + '\n\n'
2604 if tag == 'h3':
2605 return '#' * 3 + text + '\n\n'
2606 if tag == 'h4':
2607 return '#' * 4 + text + '\n\n'
2608 if tag == 'p':
2609 return text + '\n\n'
2610 if tag == 'b' or tag == 'strong':
2611 return '**%s**' % text
2612 if tag == 'em' or tag == 'i':
2613 return '*%s*' % text
2614 if tag == 'tt' or tag == 'code':
2615 return '`%s`' % text
2616 if tag == 'a':
2617 return '[%s](%s)' % (text, attr.get('_href', ''))
2618 if tag == 'img':
2619 return '' % (attr.get('_alt', ''), attr.get('_src', ''))
2620 return text
2621
2624 attr = attr or {}
2625
2626 if tag == 'br':
2627 return '\n\n'
2628 if tag == 'h1':
2629 return '# ' + text + '\n\n'
2630 if tag == 'h2':
2631 return '#' * 2 + ' ' + text + '\n\n'
2632 if tag == 'h3':
2633 return '#' * 3 + ' ' + text + '\n\n'
2634 if tag == 'h4':
2635 return '#' * 4 + ' ' + text + '\n\n'
2636 if tag == 'p':
2637 return text + '\n\n'
2638 if tag == 'li':
2639 return '\n- ' + text.replace('\n', ' ')
2640 if tag == 'tr':
2641 return text[3:].replace('\n', ' ') + '\n'
2642 if tag in ['table', 'blockquote']:
2643 return '\n-----\n' + text + '\n------\n'
2644 if tag in ['td', 'th']:
2645 return ' | ' + text
2646 if tag in ['b', 'strong', 'label']:
2647 return '**%s**' % text
2648 if tag in ['em', 'i']:
2649 return "''%s''" % text
2650 if tag in ['tt']:
2651 return '``%s``' % text.strip()
2652 if tag in ['code']:
2653 return '``\n%s``' % text
2654 if tag == 'a':
2655 return '[[%s %s]]' % (text, attr.get('_href', ''))
2656 if tag == 'img':
2657 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
2658 return text
2659
2662 """
2663 For documentation: http://web2py.com/examples/static/markmin.html
2664 """
2665 - def __init__(self, text, extra=None, allowed=None, sep='p',
2666 url=None, environment=None, latex='google',
2667 autolinks='default',
2668 protolinks='default',
2669 class_prefix='',
2670 id_prefix='markmin_'):
2671 self.text = text
2672 self.extra = extra or {}
2673 self.allowed = allowed or {}
2674 self.sep = sep
2675 self.url = URL if url == True else url
2676 self.environment = environment
2677 self.latex = latex
2678 self.autolinks = autolinks
2679 self.protolinks = protolinks
2680 self.class_prefix = class_prefix
2681 self.id_prefix = id_prefix
2682
2684 """
2685 calls the gluon.contrib.markmin render function to convert the wiki syntax
2686 """
2687 from contrib.markmin.markmin2html import render
2688 return render(self.text, extra=self.extra,
2689 allowed=self.allowed, sep=self.sep, latex=self.latex,
2690 URL=self.url, environment=self.environment,
2691 autolinks=self.autolinks, protolinks=self.protolinks,
2692 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2693
2696
2698 """
2699 return the text stored by the MARKMIN object rendered by the render function
2700 """
2701 return self.text
2702
2704 """
2705 to be considered experimental since the behavior of this method is questionable
2706 another options could be TAG(self.text).elements(*args,**kargs)
2707 """
2708 return [self.text]
2709
2710
2711 if __name__ == '__main__':
2712 import doctest
2713 doctest.testmod()
2714