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 gluon.storage import Storage
28 from gluon.utils import web2py_uuid, simple_hash, compare
29 from gluon.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 if not c.attributes.get('hideerror'):
845 c['hideerror'] = hideerror or self.attributes.get('hideerror')
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 tag = getattr(self, 'tag').replace('/', '')
1079 check = not (args and tag not in args)
1080 for (key, value) in kargs.iteritems():
1081 if key not in ['first_only', 'replace', 'find_text']:
1082 if isinstance(value, (str, int)):
1083 if self[key] != str(value):
1084 check = False
1085 elif key in self.attributes:
1086 if not value.search(str(self[key])):
1087 check = False
1088 else:
1089 check = False
1090 if 'find' in kargs:
1091 find = kargs['find']
1092 is_regex = not isinstance(find, (str, int))
1093 for c in self.components:
1094 if (isinstance(c, str) and ((is_regex and find.search(c)) or
1095 (str(find) in c))):
1096 check = True
1097
1098 if check:
1099 matches.append(self)
1100
1101 first_only = kargs.get('first_only', False)
1102 replace = kargs.get('replace', False)
1103 find_text = replace is not False and kargs.get('find_text', False)
1104 is_regex = not isinstance(find_text, (str, int, bool))
1105 find_components = not (check and first_only)
1106
1107 def replace_component(i):
1108 if replace is None:
1109 del self[i]
1110 return i
1111 else:
1112 self[i] = replace(self[i]) if callable(replace) else replace
1113 return i+1
1114
1115 if find_text or find_components:
1116 i = 0
1117 while i<len(self.components):
1118 c = self[i]
1119 j = i+1
1120 if check and find_text and isinstance(c, str) and \
1121 ((is_regex and find_text.search(c)) or (str(find_text) in c)):
1122 j = replace_component(i)
1123 elif find_components and isinstance(c, XmlComponent):
1124 child_matches = c.elements(*args, **kargs)
1125 if len(child_matches):
1126 if not find_text and replace is not False and child_matches[0] is c:
1127 j = replace_component(i)
1128 if first_only:
1129 return child_matches
1130 matches.extend(child_matches)
1131 i = j
1132 return matches
1133
1134 - def element(self, *args, **kargs):
1135 """
1136 find the first component that matches the supplied attribute dictionary,
1137 or None if nothing could be found
1138
1139 Also the components of the components are searched.
1140 """
1141 kargs['first_only'] = True
1142 elements = self.elements(*args, **kargs)
1143 if not elements:
1144
1145 return None
1146 return elements[0]
1147
1149 """
1150 find all sibling components that match the supplied argument list
1151 and attribute dictionary, or None if nothing could be found
1152 """
1153 sibs = [s for s in self.parent.components if not s == self]
1154 matches = []
1155 first_only = False
1156 if 'first_only' in kargs:
1157 first_only = kargs.pop('first_only')
1158 for c in sibs:
1159 try:
1160 check = True
1161 tag = getattr(c, 'tag').replace("/", "")
1162 if args and tag not in args:
1163 check = False
1164 for (key, value) in kargs.iteritems():
1165 if c[key] != value:
1166 check = False
1167 if check:
1168 matches.append(c)
1169 if first_only:
1170 break
1171 except:
1172 pass
1173 return matches
1174
1175 - def sibling(self, *args, **kargs):
1176 """
1177 find the first sibling component that match the supplied argument list
1178 and attribute dictionary, or None if nothing could be found
1179 """
1180 kargs['first_only'] = True
1181 sibs = self.siblings(*args, **kargs)
1182 if not sibs:
1183 return None
1184 return sibs[0]
1185
1186
1187 -class CAT(DIV):
1190
1193 return cPickle.loads(data)
1194
1197 d = DIV()
1198 d.__dict__ = data.__dict__
1199 marshal_dump = cPickle.dumps(d)
1200 return (TAG_unpickler, (marshal_dump,))
1201
1207
1208 copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
1211
1212 """
1213 TAG factory example::
1214
1215 >>> print TAG.first(TAG.second('test'), _key = 3)
1216 <first key=\"3\"><second>test</second></first>
1217
1218 """
1219
1222
1229
1232
1233 TAG = __TAG__()
1234
1235
1236 -class HTML(DIV):
1237 """
1238 There are four predefined document type definitions.
1239 They can be specified in the 'doctype' parameter:
1240
1241 -'strict' enables strict doctype
1242 -'transitional' enables transitional doctype (default)
1243 -'frameset' enables frameset doctype
1244 -'html5' enables HTML 5 doctype
1245 -any other string will be treated as user's own doctype
1246
1247 'lang' parameter specifies the language of the document.
1248 Defaults to 'en'.
1249
1250 See also :class:`DIV`
1251 """
1252
1253 tag = 'html'
1254
1255 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1256 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1257 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1258 html5 = '<!DOCTYPE HTML>\n'
1259
1261 lang = self['lang']
1262 if not lang:
1263 lang = 'en'
1264 self.attributes['_lang'] = lang
1265 doctype = self['doctype']
1266 if doctype is None:
1267 doctype = self.transitional
1268 elif doctype == 'strict':
1269 doctype = self.strict
1270 elif doctype == 'transitional':
1271 doctype = self.transitional
1272 elif doctype == 'frameset':
1273 doctype = self.frameset
1274 elif doctype == 'html5':
1275 doctype = self.html5
1276 elif doctype == '':
1277 doctype = ''
1278 else:
1279 doctype = '%s\n' % doctype
1280 (fa, co) = self._xml()
1281 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1282
1285 """
1286 This is XHTML version of the HTML helper.
1287
1288 There are three predefined document type definitions.
1289 They can be specified in the 'doctype' parameter:
1290
1291 -'strict' enables strict doctype
1292 -'transitional' enables transitional doctype (default)
1293 -'frameset' enables frameset doctype
1294 -any other string will be treated as user's own doctype
1295
1296 'lang' parameter specifies the language of the document and the xml document.
1297 Defaults to 'en'.
1298
1299 'xmlns' parameter specifies the xml namespace.
1300 Defaults to 'http://www.w3.org/1999/xhtml'.
1301
1302 See also :class:`DIV`
1303 """
1304
1305 tag = 'html'
1306
1307 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1308 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1309 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1310 xmlns = 'http://www.w3.org/1999/xhtml'
1311
1313 xmlns = self['xmlns']
1314 if xmlns:
1315 self.attributes['_xmlns'] = xmlns
1316 else:
1317 self.attributes['_xmlns'] = self.xmlns
1318 lang = self['lang']
1319 if not lang:
1320 lang = 'en'
1321 self.attributes['_lang'] = lang
1322 self.attributes['_xml:lang'] = lang
1323 doctype = self['doctype']
1324 if doctype:
1325 if doctype == 'strict':
1326 doctype = self.strict
1327 elif doctype == 'transitional':
1328 doctype = self.transitional
1329 elif doctype == 'frameset':
1330 doctype = self.frameset
1331 else:
1332 doctype = '%s\n' % doctype
1333 else:
1334 doctype = self.transitional
1335 (fa, co) = self._xml()
1336 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1337
1338
1339 -class HEAD(DIV):
1342
1347
1352
1353
1354 -class LINK(DIV):
1357
1360
1361 tag = 'script'
1362
1364 (fa, co) = self._xml()
1365
1366 co = '\n'.join([str(component) for component in
1367 self.components])
1368 if co:
1369
1370
1371
1372
1373 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1374 else:
1375 return DIV.xml(self)
1376
1379
1380 tag = 'style'
1381
1383 (fa, co) = self._xml()
1384
1385 co = '\n'.join([str(component) for component in
1386 self.components])
1387 if co:
1388
1389
1390
1391 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1392 else:
1393 return DIV.xml(self)
1394
1395
1396 -class IMG(DIV):
1399
1400
1401 -class SPAN(DIV):
1404
1405
1406 -class BODY(DIV):
1409
1410
1411 -class H1(DIV):
1414
1415
1416 -class H2(DIV):
1419
1420
1421 -class H3(DIV):
1424
1425
1426 -class H4(DIV):
1429
1430
1431 -class H5(DIV):
1434
1435
1436 -class H6(DIV):
1439
1442 """
1443 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1444
1445 see also :class:`DIV`
1446 """
1447
1448 tag = 'p'
1449
1451 text = DIV.xml(self)
1452 if self['cr2br']:
1453 text = text.replace('\n', '<br />')
1454 return text
1455
1460
1465
1466
1467 -class BR(DIV):
1470
1471
1472 -class HR(DIV):
1475
1478
1479 tag = 'a'
1480
1482 if not self.components and self['_href']:
1483 self.append(self['_href'])
1484 if not self['_disable_with']:
1485 self['_data-w2p_disable_with'] = 'default'
1486 if self['callback'] and not self['_id']:
1487 self['_id'] = web2py_uuid()
1488 if self['delete']:
1489 self['_data-w2p_remove'] = self['delete']
1490 if self['target']:
1491 if self['target'] == '<self>':
1492 self['target'] = self['_id']
1493 self['_data-w2p_target'] = self['target']
1494 if self['component']:
1495 self['_data-w2p_method'] = 'GET'
1496 self['_href'] = self['component']
1497 elif self['callback']:
1498 self['_data-w2p_method'] = 'POST'
1499 self['_href'] = self['callback']
1500 if self['delete'] and not self['noconfirm']:
1501 if not self['confirm']:
1502 self['_data-w2p_confirm'] = 'default'
1503 else:
1504 self['_data-w2p_confirm'] = self['confirm']
1505 elif self['cid']:
1506 self['_data-w2p_method'] = 'GET'
1507 self['_data-w2p_target'] = self['cid']
1508 if self['pre_call']:
1509 self['_data-w2p_pre_call'] = self['pre_call']
1510 return DIV.xml(self)
1511
1515
1516
1517 -class EM(DIV):
1520
1525
1526
1527 -class TT(DIV):
1530
1531
1532 -class PRE(DIV):
1535
1540
1541
1542 -class CODE(DIV):
1543
1544 """
1545 displays code in HTML with syntax highlighting.
1546
1547 :param attributes: optional attributes:
1548
1549 - language: indicates the language, otherwise PYTHON is assumed
1550 - link: can provide a link
1551 - styles: for styles
1552
1553 Example::
1554
1555 {{=CODE(\"print 'hello world'\", language='python', link=None,
1556 counter=1, styles={}, highlight_line=None)}}
1557
1558
1559 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1560 \"web2py\", \"html\".
1561 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1562 \"html_plain\" doesn't.
1563
1564 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1565 the online docs.
1566
1567 the counter is used for line numbering, counter can be None or a prompt
1568 string.
1569 """
1570
1572 language = self['language'] or 'PYTHON'
1573 link = self['link']
1574 counter = self.attributes.get('counter', 1)
1575 highlight_line = self.attributes.get('highlight_line', None)
1576 context_lines = self.attributes.get('context_lines', None)
1577 styles = self['styles'] or {}
1578 return highlight(
1579 join(self.components),
1580 language=language,
1581 link=link,
1582 counter=counter,
1583 styles=styles,
1584 attributes=self.attributes,
1585 highlight_line=highlight_line,
1586 context_lines=context_lines,
1587 )
1588
1593
1594
1595 -class LI(DIV):
1598
1599
1600 -class UL(DIV):
1601 """
1602 UL Component.
1603
1604 If subcomponents are not LI-components they will be wrapped in a LI
1605
1606 see also :class:`DIV`
1607 """
1608
1609 tag = 'ul'
1610
1613
1618
1619
1620 -class TD(DIV):
1623
1624
1625 -class TH(DIV):
1628
1629
1630 -class TR(DIV):
1631 """
1632 TR Component.
1633
1634 If subcomponents are not TD/TH-components they will be wrapped in a TD
1635
1636 see also :class:`DIV`
1637 """
1638
1639 tag = 'tr'
1640
1643
1646 """
1647 __TRHEAD__ Component, internal only
1648
1649 If subcomponents are not TD/TH-components they will be wrapped in a TH
1650
1651 see also :class:`DIV`
1652 """
1653
1654 tag = 'tr'
1655
1658
1661
1662 tag = 'thead'
1663
1666
1667
1668 -class TBODY(DIV):
1669
1670 tag = 'tbody'
1671
1674
1682
1683
1684 -class COL(DIV):
1687
1690
1691 tag = 'colgroup'
1692
1695 """
1696 TABLE Component.
1697
1698 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1699 they will be wrapped in a TR
1700
1701 see also :class:`DIV`
1702 """
1703
1704 tag = 'table'
1705
1708
1713
1718
1841
1842
1843 -class TEXTAREA(INPUT):
1844
1845 """
1846 example::
1847
1848 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1849
1850 'blah blah blah ...' will be the content of the textarea field.
1851 """
1852
1853 tag = 'textarea'
1854
1855 - def _postprocessing(self):
1856 if not '_rows' in self.attributes:
1857 self['_rows'] = 10
1858 if not '_cols' in self.attributes:
1859 self['_cols'] = 40
1860 if not self['value'] is None:
1861 self.components = [self['value']]
1862 elif self.components:
1863 self['value'] = self.components[0]
1864
1867
1868 tag = 'option'
1869
1871 if not '_value' in self.attributes:
1872 self.attributes['_value'] = str(self.components[0])
1873
1878
1881
1882 tag = 'optgroup'
1883
1885 components = []
1886 for c in self.components:
1887 if isinstance(c, OPTION):
1888 components.append(c)
1889 else:
1890 components.append(OPTION(c, _value=str(c)))
1891 self.components = components
1892
1895
1896 """
1897 example::
1898
1899 >>> from validators import IS_IN_SET
1900 >>> SELECT('yes', 'no', _name='selector', value='yes',
1901 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1902 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1903
1904 """
1905
1906 tag = 'select'
1907
1909 components = []
1910 for c in self.components:
1911 if isinstance(c, (OPTION, OPTGROUP)):
1912 components.append(c)
1913 else:
1914 components.append(OPTION(c, _value=str(c)))
1915 self.components = components
1916
1917 - def _postprocessing(self):
1918 component_list = []
1919 for c in self.components:
1920 if isinstance(c, OPTGROUP):
1921 component_list.append(c.components)
1922 else:
1923 component_list.append([c])
1924 options = itertools.chain(*component_list)
1925
1926 value = self['value']
1927 if not value is None:
1928 if not self['_multiple']:
1929 for c in options:
1930 if ((value is not None) and
1931 (str(c['_value']) == str(value))):
1932 c['_selected'] = 'selected'
1933 else:
1934 c['_selected'] = None
1935 else:
1936 if isinstance(value, (list, tuple)):
1937 values = [str(item) for item in value]
1938 else:
1939 values = [str(value)]
1940 for c in options:
1941 if ((value is not None) and
1942 (str(c['_value']) in values)):
1943 c['_selected'] = 'selected'
1944 else:
1945 c['_selected'] = None
1946
1949
1950 tag = 'fieldset'
1951
1956
2289 return flatten(d)
2290
2295
2300
2305
2308
2309 """
2310 example::
2311
2312 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
2313 '<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 style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>'
2314
2315 turns any list, dictionary, etc into decent looking html.
2316 Two special attributes are
2317 :sorted: a function that takes the dict and returned sorted keys
2318 :keyfilter: a funciton that takes a key and returns its representation
2319 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
2320 """
2321
2322 tag = 'div'
2323
2324 @staticmethod
2326 if key[:1] == '_':
2327 return None
2328 return key
2329
2330 - def __init__(self, component, **attributes):
2331 self.components = [component]
2332 self.attributes = attributes
2333 sorter = attributes.get('sorted', sorted)
2334 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
2335 components = []
2336 attributes = copy.copy(self.attributes)
2337 level = attributes['level'] = attributes.get('level', 6) - 1
2338 if '_class' in attributes:
2339 attributes['_class'] += 'i'
2340 if level == 0:
2341 return
2342 for c in self.components:
2343 if hasattr(c, 'value') and not callable(c.value):
2344 if c.value:
2345 components.append(c.value)
2346 if hasattr(c, 'xml') and callable(c.xml):
2347 components.append(c)
2348 continue
2349 elif hasattr(c, 'keys') and callable(c.keys):
2350 rows = []
2351 try:
2352 keys = (sorter and sorter(c)) or c
2353 for key in keys:
2354 if isinstance(key, (str, unicode)) and keyfilter:
2355 filtered_key = keyfilter(key)
2356 else:
2357 filtered_key = str(key)
2358 if filtered_key is None:
2359 continue
2360 value = c[key]
2361 if isinstance(value, types.LambdaType):
2362 continue
2363 rows.append(
2364 TR(
2365 TD(filtered_key, _style='font-weight:bold;vertical-align:top;'),
2366 TD(':', _style='vertical-align:top;'),
2367 TD(BEAUTIFY(value, **attributes))))
2368 components.append(TABLE(*rows, **attributes))
2369 continue
2370 except:
2371 pass
2372 if isinstance(c, str):
2373 components.append(str(c))
2374 elif isinstance(c, unicode):
2375 components.append(c.encode('utf8'))
2376 elif isinstance(c, (list, tuple)):
2377 items = [TR(TD(BEAUTIFY(item, **attributes)))
2378 for item in c]
2379 components.append(TABLE(*items, **attributes))
2380 elif isinstance(c, cgi.FieldStorage):
2381 components.append('FieldStorage object')
2382 else:
2383 components.append(repr(c))
2384 self.components = components
2385
2388 """
2389 Used to build menus
2390
2391 Optional arguments
2392 _class: defaults to 'web2py-menu web2py-menu-vertical'
2393 ul_class: defaults to 'web2py-menu-vertical'
2394 li_class: defaults to 'web2py-menu-expand'
2395 li_first: defaults to 'web2py-menu-first'
2396 li_last: defaults to 'web2py-menu-last'
2397
2398 Example:
2399 menu = MENU([['name', False, URL(...), [submenu]], ...])
2400 {{=menu}}
2401 """
2402
2403 tag = 'ul'
2404
2406 self.data = data
2407 self.attributes = args
2408 self.components = []
2409 if not '_class' in self.attributes:
2410 self['_class'] = 'web2py-menu web2py-menu-vertical'
2411 if not 'ul_class' in self.attributes:
2412 self['ul_class'] = 'web2py-menu-vertical'
2413 if not 'li_class' in self.attributes:
2414 self['li_class'] = 'web2py-menu-expand'
2415 if not 'li_first' in self.attributes:
2416 self['li_first'] = 'web2py-menu-first'
2417 if not 'li_last' in self.attributes:
2418 self['li_last'] = 'web2py-menu-last'
2419 if not 'li_active' in self.attributes:
2420 self['li_active'] = 'web2py-menu-active'
2421 if not 'mobile' in self.attributes:
2422 self['mobile'] = False
2423
2425 if level == 0:
2426 ul = UL(**self.attributes)
2427 else:
2428 ul = UL(_class=self['ul_class'])
2429 for item in data:
2430 if isinstance(item,LI):
2431 ul.append(item)
2432 else:
2433 (name, active, link) = item[:3]
2434 if isinstance(link, DIV):
2435 li = LI(link)
2436 elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
2437 li = LI(DIV(name))
2438 elif isinstance(link,dict):
2439 li = LI(A(name, **link))
2440 elif link:
2441 li = LI(A(name, _href=link))
2442 elif not link and isinstance(name, A):
2443 li = LI(name)
2444 else:
2445 li = LI(A(name, _href='#',
2446 _onclick='javascript:void(0);return false;'))
2447 if level == 0 and item == data[0]:
2448 li['_class'] = self['li_first']
2449 elif level == 0 and item == data[-1]:
2450 li['_class'] = self['li_last']
2451 if len(item) > 3 and item[3]:
2452 li['_class'] = self['li_class']
2453 li.append(self.serialize(item[3], level + 1))
2454 if active or ('active_url' in self.attributes and self['active_url'] == link):
2455 if li['_class']:
2456 li['_class'] = li['_class'] + ' ' + self['li_active']
2457 else:
2458 li['_class'] = self['li_active']
2459 if len(item) <= 4 or item[4] == True:
2460 ul.append(li)
2461 return ul
2462
2464 if not select:
2465 select = SELECT(**self.attributes)
2466 custom_items = []
2467 for item in data:
2468
2469 if len(item) >= 3 and (not item[0]) or (isinstance(item[0], DIV) and not (item[2])):
2470
2471
2472 custom_items.append(item)
2473 elif len(item) <= 4 or item[4] == True:
2474 select.append(OPTION(CAT(prefix, item[0]),
2475 _value=item[2], _selected=item[1]))
2476 if len(item) > 3 and len(item[3]):
2477 self.serialize_mobile(
2478 item[3], select, prefix=CAT(prefix, item[0], '/'))
2479 select['_onchange'] = 'window.location=this.value'
2480
2481 html = DIV(select, self.serialize(custom_items)) if len( custom_items) else select
2482 return html
2483
2489
2490
2491 -def embed64(
2492 filename=None,
2493 file=None,
2494 data=None,
2495 extension='image/gif',
2496 ):
2497 """
2498 helper to encode the provided (binary) data into base64.
2499
2500 :param filename: if provided, opens and reads this file in 'rb' mode
2501 :param file: if provided, reads this file
2502 :param data: if provided, uses the provided data
2503 """
2504
2505 if filename and os.path.exists(file):
2506 fp = open(filename, 'rb')
2507 data = fp.read()
2508 fp.close()
2509 data = base64.b64encode(data)
2510 return 'data:%s;base64,%s' % (extension, data)
2511
2514 """
2515 Example:
2516
2517 >>> from validators import *
2518 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml()
2519 <div><a data-w2p_disable_with="default" href="/a/b/c">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2520 >>> print DIV(UL("doc","cat","mouse")).xml()
2521 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2522 >>> print DIV(UL("doc", LI("cat", _class='feline'), 18)).xml()
2523 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2524 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2525 <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>
2526 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2527 >>> print form.xml()
2528 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2529 >>> print form.accepts({'myvar':'34'}, formname=None)
2530 False
2531 >>> print form.xml()
2532 <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>
2533 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2534 True
2535 >>> print form.xml()
2536 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2537 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2538 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2539 True
2540 >>> print form.xml()
2541 <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>
2542 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2543 >>> print form.accepts({'myvar':'as df'}, formname=None)
2544 False
2545 >>> print form.xml()
2546 <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>
2547 >>> session={}
2548 >>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
2549 >>> isinstance(form.as_dict(), dict)
2550 True
2551 >>> form.as_dict(flat=True).has_key("vars")
2552 True
2553 >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
2554 True
2555 >>> if form.accepts({}, session,formname=None): print 'passed'
2556 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2557 """
2558 pass
2559
2562 """
2563 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2564 obj.tree contains the root of the tree, and tree can be manipulated
2565
2566 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2567 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2568 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2569 '<div>a<span>b</span></div>c'
2570 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2571 >>> tree.element(_a='b')['_c']=5
2572 >>> str(tree)
2573 'hello<div a="b" c="5">world</div>'
2574 """
2575 - def __init__(self, text, closed=('input', 'link')):
2576 HTMLParser.__init__(self)
2577 self.tree = self.parent = TAG['']()
2578 self.closed = closed
2579 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
2580 self.last = None
2581 self.feed(text)
2582
2584 if tagname.upper() in self.tags:
2585 tag = eval(tagname.upper())
2586 else:
2587 if tagname in self.closed:
2588 tagname += '/'
2589 tag = TAG[tagname]()
2590 for key, value in attrs:
2591 tag['_' + key] = value
2592 tag.parent = self.parent
2593 self.parent.append(tag)
2594 if not tag.tag.endswith('/'):
2595 self.parent = tag
2596 else:
2597 self.last = tag.tag[:-1]
2598
2600 if not isinstance(data, unicode):
2601 try:
2602 data = data.decode('utf8')
2603 except:
2604 data = data.decode('latin1')
2605 self.parent.append(data.encode('utf8', 'xmlcharref'))
2606
2612
2615
2617
2618 if tagname == self.last:
2619 return
2620 while True:
2621 try:
2622 parent_tagname = self.parent.tag
2623 self.parent = self.parent.parent
2624 except:
2625 raise RuntimeError("unable to balance tag %s" % tagname)
2626 if parent_tagname[:len(tagname)] == tagname: break
2627
2630 attr = attr or {}
2631 if tag is None:
2632 return re.sub('\s+', ' ', text)
2633 if tag == 'br':
2634 return '\n\n'
2635 if tag == 'h1':
2636 return '#' + text + '\n\n'
2637 if tag == 'h2':
2638 return '#' * 2 + text + '\n\n'
2639 if tag == 'h3':
2640 return '#' * 3 + text + '\n\n'
2641 if tag == 'h4':
2642 return '#' * 4 + text + '\n\n'
2643 if tag == 'p':
2644 return text + '\n\n'
2645 if tag == 'b' or tag == 'strong':
2646 return '**%s**' % text
2647 if tag == 'em' or tag == 'i':
2648 return '*%s*' % text
2649 if tag == 'tt' or tag == 'code':
2650 return '`%s`' % text
2651 if tag == 'a':
2652 return '[%s](%s)' % (text, attr.get('_href', ''))
2653 if tag == 'img':
2654 return '' % (attr.get('_alt', ''), attr.get('_src', ''))
2655 return text
2656
2659 attr = attr or {}
2660
2661 if tag == 'br':
2662 return '\n\n'
2663 if tag == 'h1':
2664 return '# ' + text + '\n\n'
2665 if tag == 'h2':
2666 return '#' * 2 + ' ' + text + '\n\n'
2667 if tag == 'h3':
2668 return '#' * 3 + ' ' + text + '\n\n'
2669 if tag == 'h4':
2670 return '#' * 4 + ' ' + text + '\n\n'
2671 if tag == 'p':
2672 return text + '\n\n'
2673 if tag == 'li':
2674 return '\n- ' + text.replace('\n', ' ')
2675 if tag == 'tr':
2676 return text[3:].replace('\n', ' ') + '\n'
2677 if tag in ['table', 'blockquote']:
2678 return '\n-----\n' + text + '\n------\n'
2679 if tag in ['td', 'th']:
2680 return ' | ' + text
2681 if tag in ['b', 'strong', 'label']:
2682 return '**%s**' % text
2683 if tag in ['em', 'i']:
2684 return "''%s''" % text
2685 if tag in ['tt']:
2686 return '``%s``' % text.strip()
2687 if tag in ['code']:
2688 return '``\n%s``' % text
2689 if tag == 'a':
2690 return '[[%s %s]]' % (text, attr.get('_href', ''))
2691 if tag == 'img':
2692 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
2693 return text
2694
2697 """
2698 For documentation: http://web2py.com/examples/static/markmin.html
2699 """
2700 - def __init__(self, text, extra=None, allowed=None, sep='p',
2701 url=None, environment=None, latex='google',
2702 autolinks='default',
2703 protolinks='default',
2704 class_prefix='',
2705 id_prefix='markmin_'):
2706 self.text = text
2707 self.extra = extra or {}
2708 self.allowed = allowed or {}
2709 self.sep = sep
2710 self.url = URL if url == True else url
2711 self.environment = environment
2712 self.latex = latex
2713 self.autolinks = autolinks
2714 self.protolinks = protolinks
2715 self.class_prefix = class_prefix
2716 self.id_prefix = id_prefix
2717
2719 """
2720 calls the gluon.contrib.markmin render function to convert the wiki syntax
2721 """
2722 from gluon.contrib.markmin.markmin2html import render
2723 return render(self.text, extra=self.extra,
2724 allowed=self.allowed, sep=self.sep, latex=self.latex,
2725 URL=self.url, environment=self.environment,
2726 autolinks=self.autolinks, protolinks=self.protolinks,
2727 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2728
2731
2733 """
2734 return the text stored by the MARKMIN object rendered by the render function
2735 """
2736 return self.text
2737
2739 """
2740 to be considered experimental since the behavior of this method is questionable
2741 another options could be TAG(self.text).elements(*args,**kargs)
2742 """
2743 return [self.text]
2744
2745
2746 if __name__ == '__main__':
2747 import doctest
2748 doctest.testmod()
2749