|
Package Dropbox ::
Package web2py ::
Package gluon ::
Module html
|
|
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',
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
604 return getattr(str(self), name)
605
608
610 return str(self)[i:j]
611
613 for c in str(self):
614 yield c
615
617 return len(str(self))
618
620 """
621 return the text stored by the XML object rendered by the render function
622 """
623 if render:
624 return render(self.text, None, {})
625 return self.text
626
628 """
629 to be considered experimental since the behavior of this method is questionable
630 another options could be TAG(self.text).elements(*args,**kargs)
631 """
632 return []
633
638 return marshal.loads(data)
639
643 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
644
645
646 -class DIV(XmlComponent):
647 """
648 HTML helper, for easy generating and manipulating a DOM structure.
649 Little or no validation is done.
650
651 Behaves like a dictionary regarding updating of attributes.
652 Behaves like a list regarding inserting/appending components.
653
654 example::
655
656 >>> DIV('hello', 'world', _style='color:red;').xml()
657 '<div style=\"color:red;\">helloworld</div>'
658
659 all other HTML helpers are derived from DIV.
660
661 _something=\"value\" attributes are transparently translated into
662 something=\"value\" HTML attributes
663 """
664
665
666
667
668 tag = 'div'
669
670 - def __init__(self, *components, **attributes):
671 """
672 :param *components: any components that should be nested in this element
673 :param **attributes: any attributes you want to give to this element
674
675 :raises SyntaxError: when a stand alone tag receives components
676 """
677
678 if self.tag[-1:] == '/' and components:
679 raise SyntaxError('<%s> tags cannot have components'
680 % self.tag)
681 if len(components) == 1 and isinstance(components[0], (list, tuple)):
682 self.components = list(components[0])
683 else:
684 self.components = list(components)
685 self.attributes = attributes
686 self._fixup()
687
688 self.parent = None
689 for c in self.components:
690 self._setnode(c)
691 self._postprocessing()
692
694 """
695 dictionary like updating of the tag attributes
696 """
697
698 for (key, value) in kargs.iteritems():
699 self[key] = value
700 return self
701
703 """
704 list style appending of components
705
706 >>> a=DIV()
707 >>> a.append(SPAN('x'))
708 >>> print a
709 <div><span>x</span></div>
710 """
711 self._setnode(value)
712 ret = self.components.append(value)
713 self._fixup()
714 return ret
715
717 """
718 list style inserting of components
719
720 >>> a=DIV()
721 >>> a.insert(0,SPAN('x'))
722 >>> print a
723 <div><span>x</span></div>
724 """
725 self._setnode(value)
726 ret = self.components.insert(i, value)
727 self._fixup()
728 return ret
729
731 """
732 gets attribute with name 'i' or component #i.
733 If attribute 'i' is not found returns None
734
735 :param i: index
736 if i is a string: the name of the attribute
737 otherwise references to number of the component
738 """
739
740 if isinstance(i, str):
741 try:
742 return self.attributes[i]
743 except KeyError:
744 return None
745 else:
746 return self.components[i]
747
749 """
750 sets attribute with name 'i' or component #i.
751
752 :param i: index
753 if i is a string: the name of the attribute
754 otherwise references to number of the component
755 :param value: the new value
756 """
757 self._setnode(value)
758 if isinstance(i, (str, unicode)):
759 self.attributes[i] = value
760 else:
761 self.components[i] = value
762
764 """
765 deletes attribute with name 'i' or component #i.
766
767 :param i: index
768 if i is a string: the name of the attribute
769 otherwise references to number of the component
770 """
771
772 if isinstance(i, str):
773 del self.attributes[i]
774 else:
775 del self.components[i]
776
778 """
779 returns the number of included components
780 """
781 return len(self.components)
782
784 """
785 always return True
786 """
787 return True
788
790 """
791 Handling of provided components.
792
793 Nothing to fixup yet. May be overridden by subclasses,
794 eg for wrapping some components in another component or blocking them.
795 """
796 return
797
798 - def _wrap_components(self, allowed_parents,
799 wrap_parent=None,
800 wrap_lambda=None):
801 """
802 helper for _fixup. Checks if a component is in allowed_parents,
803 otherwise wraps it in wrap_parent
804
805 :param allowed_parents: (tuple) classes that the component should be an
806 instance of
807 :param wrap_parent: the class to wrap the component in, if needed
808 :param wrap_lambda: lambda to use for wrapping, if needed
809
810 """
811 components = []
812 for c in self.components:
813 if isinstance(c, allowed_parents):
814 pass
815 elif wrap_lambda:
816 c = wrap_lambda(c)
817 else:
818 c = wrap_parent(c)
819 if isinstance(c, DIV):
820 c.parent = self
821 components.append(c)
822 self.components = components
823
824 - def _postprocessing(self):
825 """
826 Handling of attributes (normally the ones not prefixed with '_').
827
828 Nothing to postprocess yet. May be overridden by subclasses
829 """
830 return
831
832 - def _traverse(self, status, hideerror=False):
833
834 newstatus = status
835 for c in self.components:
836 if hasattr(c, '_traverse') and callable(c._traverse):
837 c.vars = self.vars
838 c.request_vars = self.request_vars
839 c.errors = self.errors
840 c.latest = self.latest
841 c.session = self.session
842 c.formname = self.formname
843 if hideerror and not \
844 self.attributes.get('hideerror', False):
845 c['hideerror'] = 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 fa = ''
891 for key in sorted(self.attributes):
892 value = self[key]
893 if key[:1] != '_':
894 continue
895 name = key[1:]
896 if value is True:
897 value = name
898 elif value is False or value is None:
899 continue
900 fa += ' %s="%s"' % (name, xmlescape(value, True))
901
902
903 co = join([xmlescape(component) for component in
904 self.components])
905
906 return (fa, co)
907
909 """
910 generates the xml for this component.
911 """
912
913 (fa, co) = self._xml()
914
915 if not self.tag:
916 return co
917
918 if self.tag[-1:] == '/':
919
920 return '<%s%s />' % (self.tag[:-1], fa)
921
922
923 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
924
926 """
927 str(COMPONENT) returns equals COMPONENT.xml()
928 """
929
930 return self.xml()
931
933 """
934 return the text stored by the DIV object rendered by the render function
935 the render function must take text, tagname, and attributes
936 render=None is equivalent to render=lambda text, tag, attr: text
937
938 >>> markdown = lambda text,tag=None,attributes={}: \
939 {None: re.sub('\s+',' ',text), \
940 'h1':'#'+text+'\\n\\n', \
941 'p':text+'\\n'}.get(tag,text)
942 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
943 >>> a.flatten(markdown)
944 '#Header\\n\\nthis is a test\\n'
945 """
946
947 text = ''
948 for c in self.components:
949 if isinstance(c, XmlComponent):
950 s = c.flatten(render)
951 elif render:
952 s = render(str(c))
953 else:
954 s = str(c)
955 text += s
956 if render:
957 text = render(text, self.tag, self.attributes)
958 return text
959
960 regex_tag = re.compile('^[\w\-\:]+')
961 regex_id = re.compile('#([\w\-]+)')
962 regex_class = re.compile('\.([\w\-]+)')
963 regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]')
964
966 """
967 find all component that match the supplied attribute dictionary,
968 or None if nothing could be found
969
970 All components of the components are searched.
971
972 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
973 >>> for c in a.elements('span',first_only=True): c[0]='z'
974 >>> print a
975 <div><div><span>z</span>3<div><span>y</span></div></div></div>
976 >>> for c in a.elements('span'): c[0]='z'
977 >>> print a
978 <div><div><span>z</span>3<div><span>z</span></div></div></div>
979
980 It also supports a syntax compatible with jQuery
981
982 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
983 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
984 hello
985 world
986 >>> for e in a.elements('#1-1'): print e.flatten()
987 hello
988 >>> a.elements('a[u:v=$]')[0].xml()
989 '<a id="1-1" u:v="$">hello</a>'
990
991 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
992 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
993 >>> a.xml()
994 '<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>'
995
996 Elements that are matched can also be replaced or removed by specifying
997 a "replace" argument (note, a list of the original matching elements
998 is still returned as usual).
999
1000 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1001 >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
1002 >>> print a
1003 <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
1004
1005 "replace" can be a callable, which will be passed the original element and
1006 should return a new element to replace it.
1007
1008 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1009 >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
1010 >>> print a
1011 <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
1012
1013 If replace=None, matching elements will be removed completely.
1014
1015 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1016 >>> b = a.elements('span', find='y', replace=None)
1017 >>> print a
1018 <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
1019
1020 If a "find_text" argument is specified, elements will be searched for text
1021 components that match find_text, and any matching text components will be
1022 replaced (find_text is ignored if "replace" is not also specified).
1023 Like the "find" argument, "find_text" can be a string or a compiled regex.
1024
1025 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1026 >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
1027 >>> print a
1028 <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
1029
1030 If other attributes are specified along with find_text, then only components
1031 that match the specified attributes will be searched for find_text.
1032
1033 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
1034 >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
1035 >>> print a
1036 <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
1037 """
1038 if len(args) == 1:
1039 args = [a.strip() for a in args[0].split(',')]
1040 if len(args) > 1:
1041 subset = [self.elements(a, **kargs) for a in args]
1042 return reduce(lambda a, b: a + b, subset, [])
1043 elif len(args) == 1:
1044 items = args[0].split()
1045 if len(items) > 1:
1046 subset = [a.elements(' '.join(
1047 items[1:]), **kargs) for a in self.elements(items[0])]
1048 return reduce(lambda a, b: a + b, subset, [])
1049 else:
1050 item = items[0]
1051 if '#' in item or '.' in item or '[' in item:
1052 match_tag = self.regex_tag.search(item)
1053 match_id = self.regex_id.search(item)
1054 match_class = self.regex_class.search(item)
1055 match_attr = self.regex_attr.finditer(item)
1056 args = []
1057 if match_tag:
1058 args = [match_tag.group()]
1059 if match_id:
1060 kargs['_id'] = match_id.group(1)
1061 if match_class:
1062 kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' %
1063 match_class.group(1).replace('-', '\\-').replace(':', '\\:'))
1064 for item in match_attr:
1065 kargs['_' + item.group(1)] = item.group(2)
1066 return self.elements(*args, **kargs)
1067
1068 matches = []
1069
1070
1071 check = True
1072 tag = getattr(self, 'tag').replace('/', '')
1073 if args and tag not in args:
1074 check = False
1075 for (key, value) in kargs.iteritems():
1076 if key not in ['first_only', 'replace', 'find_text']:
1077 if isinstance(value, (str, int)):
1078 if self[key] != str(value):
1079 check = False
1080 elif key in self.attributes:
1081 if not value.search(str(self[key])):
1082 check = False
1083 else:
1084 check = False
1085 if 'find' in kargs:
1086 find = kargs['find']
1087 is_regex = not isinstance(find, (str, int))
1088 for c in self.components:
1089 if (isinstance(c, str) and ((is_regex and find.search(c)) or
1090 (str(find) in c))):
1091 check = True
1092
1093 if check:
1094 matches.append(self)
1095
1096 first_only = kargs.get('first_only', False)
1097 replace = kargs.get('replace', False)
1098 find_text = replace is not False and kargs.get('find_text', False)
1099 is_regex = not isinstance(find_text, (str, int, bool))
1100 find_components = not (check and first_only)
1101
1102 def replace_component(i):
1103 if replace is None:
1104 del self[i]
1105 elif callable(replace):
1106 self[i] = replace(self[i])
1107 else:
1108 self[i] = replace
1109
1110 if find_text or find_components:
1111 for i, c in enumerate(self.components):
1112 if check and find_text and isinstance(c, str) and \
1113 ((is_regex and find_text.search(c)) or (str(find_text) in c)):
1114 replace_component(i)
1115 if find_components and isinstance(c, XmlComponent):
1116 child_matches = c.elements(*args, **kargs)
1117 if len(child_matches):
1118 if not find_text and replace is not False and child_matches[0] is c:
1119 replace_component(i)
1120 if first_only:
1121 return child_matches
1122 matches.extend(child_matches)
1123 return matches
1124
1125 - def element(self, *args, **kargs):
1126 """
1127 find the first component that matches the supplied attribute dictionary,
1128 or None if nothing could be found
1129
1130 Also the components of the components are searched.
1131 """
1132 kargs['first_only'] = True
1133 elements = self.elements(*args, **kargs)
1134 if not elements:
1135
1136 return None
1137 return elements[0]
1138
1140 """
1141 find all sibling components that match the supplied argument list
1142 and attribute dictionary, or None if nothing could be found
1143 """
1144 sibs = [s for s in self.parent.components if not s == self]
1145 matches = []
1146 first_only = False
1147 if 'first_only' in kargs:
1148 first_only = kargs.pop('first_only')
1149 for c in sibs:
1150 try:
1151 check = True
1152 tag = getattr(c, 'tag').replace("/", "")
1153 if args and tag not in args:
1154 check = False
1155 for (key, value) in kargs.iteritems():
1156 if c[key] != value:
1157 check = False
1158 if check:
1159 matches.append(c)
1160 if first_only:
1161 break
1162 except:
1163 pass
1164 return matches
1165
1166 - def sibling(self, *args, **kargs):
1167 """
1168 find the first sibling component that match the supplied argument list
1169 and attribute dictionary, or None if nothing could be found
1170 """
1171 kargs['first_only'] = True
1172 sibs = self.siblings(*args, **kargs)
1173 if not sibs:
1174 return None
1175 return sibs[0]
1176
1177
1178 -class CAT(DIV):
1181
1184 return cPickle.loads(data)
1185
1188 d = DIV()
1189 d.__dict__ = data.__dict__
1190 marshal_dump = cPickle.dumps(d)
1191 return (TAG_unpickler, (marshal_dump,))
1192
1195
1196 """
1197 TAG factory example::
1198
1199 >>> print TAG.first(TAG.second('test'), _key = 3)
1200 <first key=\"3\"><second>test</second></first>
1201
1202 """
1203
1206
1215 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
1216 return lambda *a, **b: __tag__(*a, **b)
1217
1220
1221 TAG = __TAG__()
1222
1223
1224 -class HTML(DIV):
1225 """
1226 There are four predefined document type definitions.
1227 They can be specified in the 'doctype' parameter:
1228
1229 -'strict' enables strict doctype
1230 -'transitional' enables transitional doctype (default)
1231 -'frameset' enables frameset doctype
1232 -'html5' enables HTML 5 doctype
1233 -any other string will be treated as user's own doctype
1234
1235 'lang' parameter specifies the language of the document.
1236 Defaults to 'en'.
1237
1238 See also :class:`DIV`
1239 """
1240
1241 tag = 'html'
1242
1243 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1244 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1245 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1246 html5 = '<!DOCTYPE HTML>\n'
1247
1249 lang = self['lang']
1250 if not lang:
1251 lang = 'en'
1252 self.attributes['_lang'] = lang
1253 doctype = self['doctype']
1254 if doctype is None:
1255 doctype = self.transitional
1256 elif doctype == 'strict':
1257 doctype = self.strict
1258 elif doctype == 'transitional':
1259 doctype = self.transitional
1260 elif doctype == 'frameset':
1261 doctype = self.frameset
1262 elif doctype == 'html5':
1263 doctype = self.html5
1264 elif doctype == '':
1265 doctype = ''
1266 else:
1267 doctype = '%s\n' % doctype
1268 (fa, co) = self._xml()
1269 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1270
1273 """
1274 This is XHTML version of the HTML helper.
1275
1276 There are three predefined document type definitions.
1277 They can be specified in the 'doctype' parameter:
1278
1279 -'strict' enables strict doctype
1280 -'transitional' enables transitional doctype (default)
1281 -'frameset' enables frameset doctype
1282 -any other string will be treated as user's own doctype
1283
1284 'lang' parameter specifies the language of the document and the xml document.
1285 Defaults to 'en'.
1286
1287 'xmlns' parameter specifies the xml namespace.
1288 Defaults to 'http://www.w3.org/1999/xhtml'.
1289
1290 See also :class:`DIV`
1291 """
1292
1293 tag = 'html'
1294
1295 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1296 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1297 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1298 xmlns = 'http://www.w3.org/1999/xhtml'
1299
1301 xmlns = self['xmlns']
1302 if xmlns:
1303 self.attributes['_xmlns'] = xmlns
1304 else:
1305 self.attributes['_xmlns'] = self.xmlns
1306 lang = self['lang']
1307 if not lang:
1308 lang = 'en'
1309 self.attributes['_lang'] = lang
1310 self.attributes['_xml:lang'] = lang
1311 doctype = self['doctype']
1312 if doctype:
1313 if doctype == 'strict':
1314 doctype = self.strict
1315 elif doctype == 'transitional':
1316 doctype = self.transitional
1317 elif doctype == 'frameset':
1318 doctype = self.frameset
1319 else:
1320 doctype = '%s\n' % doctype
1321 else:
1322 doctype = self.transitional
1323 (fa, co) = self._xml()
1324 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1325
1326
1327 -class HEAD(DIV):
1330
1335
1340
1341
1342 -class LINK(DIV):
1345
1348
1349 tag = 'script'
1350
1352 (fa, co) = self._xml()
1353
1354 co = '\n'.join([str(component) for component in
1355 self.components])
1356 if co:
1357
1358
1359
1360
1361 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1362 else:
1363 return DIV.xml(self)
1364
1367
1368 tag = 'style'
1369
1371 (fa, co) = self._xml()
1372
1373 co = '\n'.join([str(component) for component in
1374 self.components])
1375 if co:
1376
1377
1378
1379 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1380 else:
1381 return DIV.xml(self)
1382
1383
1384 -class IMG(DIV):
1387
1388
1389 -class SPAN(DIV):
1392
1393
1394 -class BODY(DIV):
1397
1398
1399 -class H1(DIV):
1402
1403
1404 -class H2(DIV):
1407
1408
1409 -class H3(DIV):
1412
1413
1414 -class H4(DIV):
1417
1418
1419 -class H5(DIV):
1422
1423
1424 -class H6(DIV):
1427
1430 """
1431 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1432
1433 see also :class:`DIV`
1434 """
1435
1436 tag = 'p'
1437
1439 text = DIV.xml(self)
1440 if self['cr2br']:
1441 text = text.replace('\n', '<br />')
1442 return text
1443
1448
1453
1454
1455 -class BR(DIV):
1458
1459
1460 -class HR(DIV):
1463
1466
1467 tag = 'a'
1468
1470 if not self.components and self['_href']:
1471 self.append(self['_href'])
1472 if self['delete']:
1473 d = "jQuery(this).closest('%s').remove();" % self['delete']
1474 else:
1475 d = ''
1476 if self['component']:
1477 self['_onclick'] = "web2py_component('%s','%s');%sreturn false;" % \
1478 (self['component'], self['target'] or '', d)
1479 self['_href'] = self['_href'] or '#null'
1480 elif self['callback']:
1481 returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation();"
1482 if d:
1483 self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want o delete this object?')){ajax('%s',[],'%s');%s};%s" % \
1484 (self['callback'], self['target'] or '', d, returnfalse)
1485 else:
1486 self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \
1487 (self['callback'], self['target'] or '', d)
1488 self['_href'] = self['_href'] or '#null'
1489 elif self['cid']:
1490 pre = self['pre_call'] + ';' if self['pre_call'] else ''
1491 self['_onclick'] = '%sweb2py_component("%s","%s");%sreturn false;' % \
1492 (pre,self['_href'], self['cid'], d)
1493 return DIV.xml(self)
1494
1499
1500
1501 -class EM(DIV):
1504
1509
1510
1511 -class TT(DIV):
1514
1515
1516 -class PRE(DIV):
1519
1524
1525
1526 -class CODE(DIV):
1527
1528 """
1529 displays code in HTML with syntax highlighting.
1530
1531 :param attributes: optional attributes:
1532
1533 - language: indicates the language, otherwise PYTHON is assumed
1534 - link: can provide a link
1535 - styles: for styles
1536
1537 Example::
1538
1539 {{=CODE(\"print 'hello world'\", language='python', link=None,
1540 counter=1, styles={}, highlight_line=None)}}
1541
1542
1543 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
1544 \"web2py\", \"html\".
1545 The \"html\" language interprets {{ and }} tags as \"web2py\" code,
1546 \"html_plain\" doesn't.
1547
1548 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1549 the online docs.
1550
1551 the counter is used for line numbering, counter can be None or a prompt
1552 string.
1553 """
1554
1556 language = self['language'] or 'PYTHON'
1557 link = self['link']
1558 counter = self.attributes.get('counter', 1)
1559 highlight_line = self.attributes.get('highlight_line', None)
1560 context_lines = self.attributes.get('context_lines', None)
1561 styles = self['styles'] or {}
1562 return highlight(
1563 join(self.components),
1564 language=language,
1565 link=link,
1566 counter=counter,
1567 styles=styles,
1568 attributes=self.attributes,
1569 highlight_line=highlight_line,
1570 context_lines=context_lines,
1571 )
1572
1577
1578
1579 -class LI(DIV):
1582
1583
1584 -class UL(DIV):
1585 """
1586 UL Component.
1587
1588 If subcomponents are not LI-components they will be wrapped in a LI
1589
1590 see also :class:`DIV`
1591 """
1592
1593 tag = 'ul'
1594
1597
1602
1603
1604 -class TD(DIV):
1607
1608
1609 -class TH(DIV):
1612
1613
1614 -class TR(DIV):
1615 """
1616 TR Component.
1617
1618 If subcomponents are not TD/TH-components they will be wrapped in a TD
1619
1620 see also :class:`DIV`
1621 """
1622
1623 tag = 'tr'
1624
1627
1630
1631 tag = 'thead'
1632
1635
1636
1637 -class TBODY(DIV):
1638
1639 tag = 'tbody'
1640
1643
1651
1652
1653 -class COL(DIV):
1656
1659
1660 tag = 'colgroup'
1661
1664 """
1665 TABLE Component.
1666
1667 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1668 they will be wrapped in a TR
1669
1670 see also :class:`DIV`
1671 """
1672
1673 tag = 'table'
1674
1677
1682
1687
1810
1811
1812 -class TEXTAREA(INPUT):
1813
1814 """
1815 example::
1816
1817 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
1818
1819 'blah blah blah ...' will be the content of the textarea field.
1820 """
1821
1822 tag = 'textarea'
1823
1824 - def _postprocessing(self):
1825 if not '_rows' in self.attributes:
1826 self['_rows'] = 10
1827 if not '_cols' in self.attributes:
1828 self['_cols'] = 40
1829 if not self['value'] is None:
1830 self.components = [self['value']]
1831 elif self.components:
1832 self['value'] = self.components[0]
1833
1836
1837 tag = 'option'
1838
1840 if not '_value' in self.attributes:
1841 self.attributes['_value'] = str(self.components[0])
1842
1847
1850
1851 tag = 'optgroup'
1852
1854 components = []
1855 for c in self.components:
1856 if isinstance(c, OPTION):
1857 components.append(c)
1858 else:
1859 components.append(OPTION(c, _value=str(c)))
1860 self.components = components
1861
1864
1865 """
1866 example::
1867
1868 >>> from validators import IS_IN_SET
1869 >>> SELECT('yes', 'no', _name='selector', value='yes',
1870 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1871 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1872
1873 """
1874
1875 tag = 'select'
1876
1878 components = []
1879 for c in self.components:
1880 if isinstance(c, (OPTION, OPTGROUP)):
1881 components.append(c)
1882 else:
1883 components.append(OPTION(c, _value=str(c)))
1884 self.components = components
1885
1886 - def _postprocessing(self):
1887 component_list = []
1888 for c in self.components:
1889 if isinstance(c, OPTGROUP):
1890 component_list.append(c.components)
1891 else:
1892 component_list.append([c])
1893 options = itertools.chain(*component_list)
1894
1895 value = self['value']
1896 if not value is None:
1897 if not self['_multiple']:
1898 for c in options:
1899 if ((value is not None) and
1900 (str(c['_value']) == str(value))):
1901 c['_selected'] = 'selected'
1902 else:
1903 c['_selected'] = None
1904 else:
1905 if isinstance(value, (list, tuple)):
1906 values = [str(item) for item in value]
1907 else:
1908 values = [str(value)]
1909 for c in options:
1910 if ((value is not None) and
1911 (str(c['_value']) in values)):
1912 c['_selected'] = 'selected'
1913 else:
1914 c['_selected'] = None
1915
1918
1919 tag = 'fieldset'
1920
1925
2206
2209
2210 """
2211 example::
2212
2213 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
2214 '<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>'
2215
2216 turns any list, dictionary, etc into decent looking html.
2217 Two special attributes are
2218 :sorted: a function that takes the dict and returned sorted keys
2219 :keyfilter: a funciton that takes a key and returns its representation
2220 or None if the key is to be skipped. By default key[:1]=='_' is skipped.
2221 """
2222
2223 tag = 'div'
2224
2225 @staticmethod
2227 if key[:1] == '_':
2228 return None
2229 return key
2230
2231 - def __init__(self, component, **attributes):
2232 self.components = [component]
2233 self.attributes = attributes
2234 sorter = attributes.get('sorted', sorted)
2235 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
2236 components = []
2237 attributes = copy.copy(self.attributes)
2238 level = attributes['level'] = attributes.get('level', 6) - 1
2239 if '_class' in attributes:
2240 attributes['_class'] += 'i'
2241 if level == 0:
2242 return
2243 for c in self.components:
2244 if hasattr(c, 'value') and not callable(c.value):
2245 if c.value:
2246 components.append(c.value)
2247 if hasattr(c, 'xml') and callable(c.xml):
2248 components.append(c)
2249 continue
2250 elif hasattr(c, 'keys') and callable(c.keys):
2251 rows = []
2252 try:
2253 keys = (sorter and sorter(c)) or c
2254 for key in keys:
2255 if isinstance(key, (str, unicode)) and keyfilter:
2256 filtered_key = keyfilter(key)
2257 else:
2258 filtered_key = str(key)
2259 if filtered_key is None:
2260 continue
2261 value = c[key]
2262 if isinstance(value, types.LambdaType):
2263 continue
2264 rows.append(
2265 TR(
2266 TD(filtered_key, _style='font-weight:bold;vertical-align:top'),
2267 TD(':', _valign='top'),
2268 TD(BEAUTIFY(value, **attributes))))
2269 components.append(TABLE(*rows, **attributes))
2270 continue
2271 except:
2272 pass
2273 if isinstance(c, str):
2274 components.append(str(c))
2275 elif isinstance(c, unicode):
2276 components.append(c.encode('utf8'))
2277 elif isinstance(c, (list, tuple)):
2278 items = [TR(TD(BEAUTIFY(item, **attributes)))
2279 for item in c]
2280 components.append(TABLE(*items, **attributes))
2281 elif isinstance(c, cgi.FieldStorage):
2282 components.append('FieldStorage object')
2283 else:
2284 components.append(repr(c))
2285 self.components = components
2286
2289 """
2290 Used to build menus
2291
2292 Optional arguments
2293 _class: defaults to 'web2py-menu web2py-menu-vertical'
2294 ul_class: defaults to 'web2py-menu-vertical'
2295 li_class: defaults to 'web2py-menu-expand'
2296 li_first: defaults to 'web2py-menu-first'
2297 li_last: defaults to 'web2py-menu-last'
2298
2299 Example:
2300 menu = MENU([['name', False, URL(...), [submenu]], ...])
2301 {{=menu}}
2302 """
2303
2304 tag = 'ul'
2305
2307 self.data = data
2308 self.attributes = args
2309 self.components = []
2310 if not '_class' in self.attributes:
2311 self['_class'] = 'web2py-menu web2py-menu-vertical'
2312 if not 'ul_class' in self.attributes:
2313 self['ul_class'] = 'web2py-menu-vertical'
2314 if not 'li_class' in self.attributes:
2315 self['li_class'] = 'web2py-menu-expand'
2316 if not 'li_first' in self.attributes:
2317 self['li_first'] = 'web2py-menu-first'
2318 if not 'li_last' in self.attributes:
2319 self['li_last'] = 'web2py-menu-last'
2320 if not 'li_active' in self.attributes:
2321 self['li_active'] = 'web2py-menu-active'
2322 if not 'mobile' in self.attributes:
2323 self['mobile'] = False
2324
2326 if level == 0:
2327 ul = UL(**self.attributes)
2328 else:
2329 ul = UL(_class=self['ul_class'])
2330 for item in data:
2331 (name, active, link) = item[:3]
2332 if isinstance(link, DIV):
2333 li = LI(link)
2334 elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
2335 li = LI(DIV(name))
2336 elif isinstance(link,dict):
2337 li = LI(A(name, **link))
2338 elif link:
2339 li = LI(A(name, _href=link))
2340 elif not link and isinstance(name, A):
2341 li = LI(name)
2342 else:
2343 li = LI(A(name, _href='#',
2344 _onclick='javascript:void(0);return false;'))
2345 if level == 0 and item == data[0]:
2346 li['_class'] = self['li_first']
2347 elif level == 0 and item == data[-1]:
2348 li['_class'] = self['li_last']
2349 if len(item) > 3 and item[3]:
2350 li['_class'] = self['li_class']
2351 li.append(self.serialize(item[3], level + 1))
2352 if active or ('active_url' in self.attributes and self['active_url'] == link):
2353 if li['_class']:
2354 li['_class'] = li['_class'] + ' ' + self['li_active']
2355 else:
2356 li['_class'] = self['li_active']
2357 if len(item) <= 4 or item[4] == True:
2358 ul.append(li)
2359 return ul
2360
2373
2379
2380
2381 -def embed64(
2382 filename=None,
2383 file=None,
2384 data=None,
2385 extension='image/gif',
2386 ):
2387 """
2388 helper to encode the provided (binary) data into base64.
2389
2390 :param filename: if provided, opens and reads this file in 'rb' mode
2391 :param file: if provided, reads this file
2392 :param data: if provided, uses the provided data
2393 """
2394
2395 if filename and os.path.exists(file):
2396 fp = open(filename, 'rb')
2397 data = fp.read()
2398 fp.close()
2399 data = base64.b64encode(data)
2400 return 'data:%s;base64,%s' % (extension, data)
2401
2404 """
2405 Example:
2406
2407 >>> from validators import *
2408 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
2409 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2410 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
2411 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2412 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
2413 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2414 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2415 <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>
2416 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2417 >>> print form.xml()
2418 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2419 >>> print form.accepts({'myvar':'34'}, formname=None)
2420 False
2421 >>> print form.xml()
2422 <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>
2423 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2424 True
2425 >>> print form.xml()
2426 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2427 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2428 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2429 True
2430 >>> print form.xml()
2431 <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>
2432 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2433 >>> print form.accepts({'myvar':'as df'}, formname=None)
2434 False
2435 >>> print form.xml()
2436 <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>
2437 >>> session={}
2438 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
2439 >>> if form.accepts({}, session,formname=None): print 'passed'
2440 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2441 """
2442 pass
2443
2446 """
2447 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2448 obj.tree contains the root of the tree, and tree can be manipulated
2449
2450 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2451 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2452 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2453 '<div>a<span>b</span></div>c'
2454 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2455 >>> tree.element(_a='b')['_c']=5
2456 >>> str(tree)
2457 'hello<div a="b" c="5">world</div>'
2458 """
2459 - def __init__(self, text, closed=('input', 'link')):
2460 HTMLParser.__init__(self)
2461 self.tree = self.parent = TAG['']()
2462 self.closed = closed
2463 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
2464 self.last = None
2465 self.feed(text)
2466
2468 if tagname.upper() in self.tags:
2469 tag = eval(tagname.upper())
2470 else:
2471 if tagname in self.closed:
2472 tagname += '/'
2473 tag = TAG[tagname]()
2474 for key, value in attrs:
2475 tag['_' + key] = value
2476 tag.parent = self.parent
2477 self.parent.append(tag)
2478 if not tag.tag.endswith('/'):
2479 self.parent = tag
2480 else:
2481 self.last = tag.tag[:-1]
2482
2484 if not isinstance(data, unicode):
2485 try:
2486 data = data.decode('utf8')
2487 except:
2488 data = data.decode('latin1')
2489 self.parent.append(data.encode('utf8', 'xmlcharref'))
2490
2496
2499
2501
2502 if tagname == self.last:
2503 return
2504 while True:
2505 try:
2506 parent_tagname = self.parent.tag
2507 self.parent = self.parent.parent
2508 except:
2509 raise RuntimeError("unable to balance tag %s" % tagname)
2510 if parent_tagname[:len(tagname)] == tagname: break
2511
2514 attr = attr or {}
2515 if tag is None:
2516 return re.sub('\s+', ' ', text)
2517 if tag == 'br':
2518 return '\n\n'
2519 if tag == 'h1':
2520 return '#' + text + '\n\n'
2521 if tag == 'h2':
2522 return '#' * 2 + text + '\n\n'
2523 if tag == 'h3':
2524 return '#' * 3 + text + '\n\n'
2525 if tag == 'h4':
2526 return '#' * 4 + text + '\n\n'
2527 if tag == 'p':
2528 return text + '\n\n'
2529 if tag == 'b' or tag == 'strong':
2530 return '**%s**' % text
2531 if tag == 'em' or tag == 'i':
2532 return '*%s*' % text
2533 if tag == 'tt' or tag == 'code':
2534 return '`%s`' % text
2535 if tag == 'a':
2536 return '[%s](%s)' % (text, attr.get('_href', ''))
2537 if tag == 'img':
2538 return '' % (attr.get('_alt', ''), attr.get('_src', ''))
2539 return text
2540
2543 attr = attr or {}
2544
2545 if tag == 'br':
2546 return '\n\n'
2547 if tag == 'h1':
2548 return '# ' + text + '\n\n'
2549 if tag == 'h2':
2550 return '#' * 2 + ' ' + text + '\n\n'
2551 if tag == 'h3':
2552 return '#' * 3 + ' ' + text + '\n\n'
2553 if tag == 'h4':
2554 return '#' * 4 + ' ' + text + '\n\n'
2555 if tag == 'p':
2556 return text + '\n\n'
2557 if tag == 'li':
2558 return '\n- ' + text.replace('\n', ' ')
2559 if tag == 'tr':
2560 return text[3:].replace('\n', ' ') + '\n'
2561 if tag in ['table', 'blockquote']:
2562 return '\n-----\n' + text + '\n------\n'
2563 if tag in ['td', 'th']:
2564 return ' | ' + text
2565 if tag in ['b', 'strong', 'label']:
2566 return '**%s**' % text
2567 if tag in ['em', 'i']:
2568 return "''%s''" % text
2569 if tag in ['tt']:
2570 return '``%s``' % text.strip()
2571 if tag in ['code']:
2572 return '``\n%s``' % text
2573 if tag == 'a':
2574 return '[[%s %s]]' % (text, attr.get('_href', ''))
2575 if tag == 'img':
2576 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
2577 return text
2578
2581 """
2582 For documentation: http://web2py.com/examples/static/markmin.html
2583 """
2584 - def __init__(self, text, extra=None, allowed=None, sep='p',
2585 url=None, environment=None, latex='google',
2586 autolinks='default',
2587 protolinks='default',
2588 class_prefix='',
2589 id_prefix='markmin_'):
2590 self.text = text
2591 self.extra = extra or {}
2592 self.allowed = allowed or {}
2593 self.sep = sep
2594 self.url = URL if url == True else url
2595 self.environment = environment
2596 self.latex = latex
2597 self.autolinks = autolinks
2598 self.protolinks = protolinks
2599 self.class_prefix = class_prefix
2600 self.id_prefix = id_prefix
2601
2603 """
2604 calls the gluon.contrib.markmin render function to convert the wiki syntax
2605 """
2606 from contrib.markmin.markmin2html import render
2607 return render(self.text, extra=self.extra,
2608 allowed=self.allowed, sep=self.sep, latex=self.latex,
2609 URL=self.url, environment=self.environment,
2610 autolinks=self.autolinks, protolinks=self.protolinks,
2611 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2612
2615
2617 """
2618 return the text stored by the MARKMIN object rendered by the render function
2619 """
2620 return self.text
2621
2623 """
2624 to be considered experimental since the behavior of this method is questionable
2625 another options could be TAG(self.text).elements(*args,**kargs)
2626 """
2627 return [self.text]
2628
2629
2630 if __name__ == '__main__':
2631 import doctest
2632 doctest.testmod()
2633