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 Template helpers
10 --------------------------------------------
11 """
12
13 import cgi
14 import os
15 import re
16 import copy
17 import types
18 import urllib
19 import base64
20 import sanitizer
21 import itertools
22 import decoder
23 import copy_reg
24 import cPickle
25 import marshal
26
27 from HTMLParser import HTMLParser
28 from htmlentitydefs import name2codepoint
29
30 from gluon.storage import Storage
31 from gluon.utils import web2py_uuid, simple_hash, compare
32 from gluon.highlight import highlight
33
34 regex_crlf = re.compile('\r|\n')
35
36 join = ''.join
37
38
39 entitydefs = dict(map(lambda (
40 k, v): (k, unichr(v).encode('utf-8')), name2codepoint.iteritems()))
41 entitydefs.setdefault('apos', u"'".encode('utf-8'))
42
43
44 __all__ = [
45 'A',
46 'B',
47 'BEAUTIFY',
48 'BODY',
49 'BR',
50 'BUTTON',
51 'CENTER',
52 'CAT',
53 'CODE',
54 'COL',
55 'COLGROUP',
56 'DIV',
57 'EM',
58 'EMBED',
59 'FIELDSET',
60 'FORM',
61 'H1',
62 'H2',
63 'H3',
64 'H4',
65 'H5',
66 'H6',
67 'HEAD',
68 'HR',
69 'HTML',
70 'I',
71 'IFRAME',
72 'IMG',
73 'INPUT',
74 'LABEL',
75 'LEGEND',
76 'LI',
77 'LINK',
78 'OL',
79 'UL',
80 'MARKMIN',
81 'MENU',
82 'META',
83 'OBJECT',
84 'ON',
85 'OPTION',
86 'P',
87 'PRE',
88 'SCRIPT',
89 'OPTGROUP',
90 'SELECT',
91 'SPAN',
92 'STRONG',
93 'STYLE',
94 'TABLE',
95 'TAG',
96 'TD',
97 'TEXTAREA',
98 'TH',
99 'THEAD',
100 'TBODY',
101 'TFOOT',
102 'TITLE',
103 'TR',
104 'TT',
105 'URL',
106 'XHTML',
107 'XML',
108 'xmlescape',
109 'embed64',
110 ]
114 """
115 Returns an escaped string of the provided data
116
117 Args:
118 data: the data to be escaped
119 quote: optional (default False)
120 """
121
122
123 if hasattr(data, 'xml') and callable(data.xml):
124 return data.xml()
125
126
127 if not isinstance(data, (str, unicode)):
128 data = str(data)
129 elif isinstance(data, unicode):
130 data = data.encode('utf8', 'xmlcharrefreplace')
131
132
133 data = cgi.escape(data, quote).replace("'", "'")
134 return data
135
137 if not isinstance(f, (list,tuple)):
138 f = [f]
139 for item in f:
140 item(*a,**b)
141
143 text = text.decode('utf-8')
144 if len(text) > length:
145 text = text[:length - len(dots)].encode('utf-8') + dots
146 return text
147
148
149 -def URL(
150 a=None,
151 c=None,
152 f=None,
153 r=None,
154 args=None,
155 vars=None,
156 anchor='',
157 extension=None,
158 env=None,
159 hmac_key=None,
160 hash_vars=True,
161 salt=None,
162 user_signature=None,
163 scheme=None,
164 host=None,
165 port=None,
166 encode_embedded_slash=False,
167 url_encode=True,
168 language=None,
169 ):
170 """
171 generates a url '/a/c/f' corresponding to application a, controller c
172 and function f. If r=request is passed, a, c, f are set, respectively,
173 to r.application, r.controller, r.function.
174
175 The more typical usage is:
176
177 URL('index')
178
179 that generates a url for the index function
180 within the present application and controller.
181
182 Args:
183 a: application (default to current if r is given)
184 c: controller (default to current if r is given)
185 f: function (default to current if r is given)
186 r: request (optional)
187 args: any arguments (optional). Additional "path" elements
188 vars: any variables (optional). Querystring elements
189 anchor: anchorname, without # (optional)
190 extension: force an extension
191 hmac_key: key to use when generating hmac signature (optional)
192 hash_vars: which of the vars to include in our hmac signature
193 True (default) - hash all vars, False - hash none of the vars,
194 iterable - hash only the included vars ['key1','key2']
195 salt: salt hashing with this string
196 user_signature: signs automatically the URL in such way that only the
197 user can access the URL (use with `URL.verify` or
198 `auth.requires_signature()`)
199 scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
200 host: string to force absolute URL with host (True means http_host)
201 port: optional port number (forces absolute URL)
202 encode_embedded_slash: encode slash characters included in args
203 url_encode: encode characters included in vars
204
205 Raises:
206 SyntaxError: when no application, controller or function is available
207 or when a CRLF is found in the generated url
208
209 Examples:
210
211 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
212 ... vars={'p':1, 'q':2}, anchor='1'))
213 '/a/c/f/x/y/z?p=1&q=2#1'
214
215 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
216 ... vars={'p':(1,3), 'q':2}, anchor='1'))
217 '/a/c/f/x/y/z?p=1&p=3&q=2#1'
218
219 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
220 ... vars={'p':(3,1), 'q':2}, anchor='1'))
221 '/a/c/f/x/y/z?p=3&p=1&q=2#1'
222
223 >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
224 '/a/c/f#1%2B2'
225
226 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
227 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
228 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1'
229
230 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z']))
231 '/a/c/f/w/x/y/z'
232
233 >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z'], encode_embedded_slash=True))
234 '/a/c/f/w%2Fx/y%2Fz'
235
236 >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=False))
237 '/a/c/f/%(id)d'
238
239 >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=True))
240 '/a/c/f/%25%28id%29d'
241
242 >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=False))
243 '/a/c/f?id=%(id)d'
244
245 >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=True))
246 '/a/c/f?id=%25%28id%29d'
247
248 >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=False))
249 '/a/c/f#%(id)d'
250
251 >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
252 '/a/c/f#%25%28id%29d'
253
254
255
256
257 """
258
259 from rewrite import url_out
260
261 if args in (None, []):
262 args = []
263 vars = vars or {}
264 application = None
265 controller = None
266 function = None
267
268 if not isinstance(args, (list, tuple)):
269 args = [args]
270
271 if not r:
272 if a and not c and not f:
273 (f, a, c) = (a, c, f)
274 elif a and c and not f:
275 (c, f, a) = (a, c, f)
276 from globals import current
277 if hasattr(current, 'request'):
278 r = current.request
279
280 if r:
281 application = r.application
282 controller = r.controller
283 function = r.function
284 env = r.env
285 if extension is None and r.extension != 'html':
286 extension = r.extension
287 if a:
288 application = a
289 if c:
290 controller = c
291 if f:
292 if not isinstance(f, str):
293 if hasattr(f, '__name__'):
294 function = f.__name__
295 else:
296 raise SyntaxError(
297 'when calling URL, function or function name required')
298 elif '/' in f:
299 if f.startswith("/"):
300 f = f[1:]
301 items = f.split('/')
302 function = f = items[0]
303 args = items[1:] + args
304 else:
305 function = f
306
307
308 if controller == 'static':
309 extension = None
310
311 if '.' in function:
312 function, extension = function.rsplit('.', 1)
313
314 function2 = '%s.%s' % (function, extension or 'html')
315
316 if not (application and controller and function):
317 raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function))
318
319 if args:
320 if url_encode:
321 if encode_embedded_slash:
322 other = '/' + '/'.join([urllib.quote(str(
323 x), '') for x in args])
324 else:
325 other = args and urllib.quote(
326 '/' + '/'.join([str(x) for x in args]))
327 else:
328 other = args and ('/' + '/'.join([str(x) for x in args]))
329 else:
330 other = ''
331
332 if other.endswith('/'):
333 other += '/'
334
335 list_vars = []
336 for (key, vals) in sorted(vars.items()):
337 if key == '_signature':
338 continue
339 if not isinstance(vals, (list, tuple)):
340 vals = [vals]
341 for val in vals:
342 list_vars.append((key, val))
343
344 if user_signature:
345 from globals import current
346 if current.session.auth:
347 hmac_key = current.session.auth.hmac_key
348
349 if hmac_key:
350
351
352
353 h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
354
355
356 if hash_vars is True:
357 h_vars = list_vars
358 elif hash_vars is False:
359 h_vars = ''
360 else:
361 if hash_vars and not isinstance(hash_vars, (list, tuple)):
362 hash_vars = [hash_vars]
363 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
364
365
366 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
367 sig = simple_hash(
368 message, hmac_key or '', salt or '', digest_alg='sha1')
369
370 list_vars.append(('_signature', sig))
371
372 if list_vars:
373 if url_encode:
374 other += '?%s' % urllib.urlencode(list_vars)
375 else:
376 other += '?%s' % '&'.join(['%s=%s' % var[:2] for var in list_vars])
377 if anchor:
378 if url_encode:
379 other += '#' + urllib.quote(str(anchor))
380 else:
381 other += '#' + (str(anchor))
382 if extension:
383 function += '.' + extension
384
385 if regex_crlf.search(join([application, controller, function, other])):
386 raise SyntaxError('CRLF Injection Detected')
387
388 url = url_out(r, env, application, controller, function,
389 args, other, scheme, host, port, language=language)
390 return url
391
392
393 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
394 """
395 Verifies that a request's args & vars have not been tampered with by the user
396
397 :param request: web2py's request object
398 :param hmac_key: the key to authenticate with, must be the same one previously
399 used when calling URL()
400 :param hash_vars: which vars to include in our hashing. (Optional)
401 Only uses the 1st value currently
402 True (or undefined) means all, False none,
403 an iterable just the specified keys
404
405 do not call directly. Use instead:
406
407 URL.verify(hmac_key='...')
408
409 the key has to match the one used to generate the URL.
410
411 >>> r = Storage()
412 >>> gv = Storage(p=(1,3),q=2,_signature='a32530f0d0caa80964bb92aad2bedf8a4486a31f')
413 >>> r.update(dict(application='a', controller='c', function='f', extension='html'))
414 >>> r['args'] = ['x', 'y', 'z']
415 >>> r['get_vars'] = gv
416 >>> verifyURL(r, 'key')
417 True
418 >>> verifyURL(r, 'kay')
419 False
420 >>> r.get_vars.p = (3, 1)
421 >>> verifyURL(r, 'key')
422 True
423 >>> r.get_vars.p = (3, 2)
424 >>> verifyURL(r, 'key')
425 False
426
427 """
428
429 if not '_signature' in request.get_vars:
430 return False
431
432
433 if user_signature:
434 from globals import current
435 if not current.session or not current.session.auth:
436 return False
437 hmac_key = current.session.auth.hmac_key
438 if not hmac_key:
439 return False
440
441
442 original_sig = request.get_vars._signature
443
444
445 vars, args = request.get_vars, request.args
446
447
448 request.get_vars.pop('_signature')
449
450
451
452
453 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
454 h_args = '/%s/%s/%s.%s%s' % (request.application,
455 request.controller,
456 request.function,
457 request.extension,
458 other)
459
460
461
462
463 list_vars = []
464 for (key, vals) in sorted(vars.items()):
465 if not isinstance(vals, (list, tuple)):
466 vals = [vals]
467 for val in vals:
468 list_vars.append((key, val))
469
470
471 if hash_vars is True:
472 h_vars = list_vars
473 elif hash_vars is False:
474 h_vars = ''
475 else:
476
477 try:
478 if hash_vars and not isinstance(hash_vars, (list, tuple)):
479 hash_vars = [hash_vars]
480 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
481 except:
482
483 return False
484
485 message = h_args + '?' + urllib.urlencode(sorted(h_vars))
486
487
488 sig = simple_hash(message, str(hmac_key), salt or '', digest_alg='sha1')
489
490
491
492 request.get_vars['_signature'] = original_sig
493
494
495
496
497 return compare(original_sig, sig)
498
499 URL.verify = verifyURL
500
501 ON = True
505 """
506 Abstract root for all Html components
507 """
508
509
510
512 raise NotImplementedError
513
515 return CAT(*[self for i in range(n)])
516
518 if isinstance(self, CAT):
519 components = self.components
520 else:
521 components = [self]
522 if isinstance(other, CAT):
523 components += other.components
524 else:
525 components += [other]
526 return CAT(*components)
527
529 """ add a class to _class attribute """
530 c = self['_class']
531 classes = (set(c.split()) if c else set()) | set(name.split())
532 self['_class'] = ' '.join(classes) if classes else None
533 return self
534
536 """ remove a class from _class attribute """
537 c = self['_class']
538 classes = (set(c.split()) if c else set()) - set(name.split())
539 self['_class'] = ' '.join(classes) if classes else None
540 return self
541
542 -class XML(XmlComponent):
543 """
544 use it to wrap a string that contains XML/HTML so that it will not be
545 escaped by the template
546
547 Examples:
548
549 >>> XML('<h1>Hello</h1>').xml()
550 '<h1>Hello</h1>'
551 """
552
553 - def __init__(
554 self,
555 text,
556 sanitize=False,
557 permitted_tags=[
558 'a',
559 'b',
560 'blockquote',
561 'br/',
562 'i',
563 'li',
564 'ol',
565 'ul',
566 'p',
567 'cite',
568 'code',
569 'pre',
570 'img/',
571 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
572 'table', 'tr', 'td', 'div',
573 'strong','span',
574 ],
575 allowed_attributes={
576 'a': ['href', 'title', 'target'],
577 'img': ['src', 'alt'],
578 'blockquote': ['type'],
579 'td': ['colspan'],
580 },
581 ):
582 """
583 Args:
584 text: the XML text
585 sanitize: sanitize text using the permitted tags and allowed
586 attributes (default False)
587 permitted_tags: list of permitted tags (default: simple list of
588 tags)
589 allowed_attributes: dictionary of allowed attributed (default
590 for A, IMG and BlockQuote).
591 The key is the tag; the value is a list of allowed attributes.
592 """
593
594 if sanitize:
595 text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
596 if isinstance(text, unicode):
597 text = text.encode('utf8', 'xmlcharrefreplace')
598 elif not isinstance(text, str):
599 text = str(text)
600 self.text = text
601
604
607
609 return '%s%s' % (self, other)
610
612 return '%s%s' % (other, self)
613
615 return cmp(str(self), str(other))
616
618 return hash(str(self))
619
620
621
622
623
626
628 return str(self)[i:j]
629
631 for c in str(self):
632 yield c
633
635 return len(str(self))
636
638 """
639 returns the text stored by the XML object rendered
640 by the `render` function
641 """
642 if render:
643 return render(self.text, None, {})
644 return self.text
645
647 """
648 to be considered experimental since the behavior of this method
649 is questionable
650 another option could be `TAG(self.text).elements(*args,**kwargs)`
651 """
652 return []
653
658 return marshal.loads(data)
659
663 copy_reg.pickle(XML, XML_pickle, XML_unpickle)
664
665
666 -class DIV(XmlComponent):
667 """
668 HTML helper, for easy generating and manipulating a DOM structure.
669 Little or no validation is done.
670
671 Behaves like a dictionary regarding updating of attributes.
672 Behaves like a list regarding inserting/appending components.
673
674 Examples:
675
676 >>> DIV('hello', 'world', _style='color:red;').xml()
677 '<div style=\"color:red;\">helloworld</div>'
678
679 All other HTML helpers are derived from `DIV`.
680
681 `_something="value"` attributes are transparently translated into
682 `something="value"` HTML attributes
683 """
684
685
686
687
688 tag = 'div'
689
690 - def __init__(self, *components, **attributes):
691 """
692 Args:
693 components: any components that should be nested in this element
694 attributes: any attributes you want to give to this element
695
696 Raises:
697 SyntaxError: when a stand alone tag receives components
698 """
699
700 if self.tag[-1:] == '/' and components:
701 raise SyntaxError('<%s> tags cannot have components'
702 % self.tag)
703 if len(components) == 1 and isinstance(components[0], (list, tuple)):
704 self.components = list(components[0])
705 else:
706 self.components = list(components)
707 self.attributes = attributes
708 self._fixup()
709
710 self.parent = None
711 for c in self.components:
712 self._setnode(c)
713 self._postprocessing()
714
716 """
717 dictionary like updating of the tag attributes
718 """
719
720 for (key, value) in kargs.iteritems():
721 self[key] = value
722 return self
723
725 """
726 list style appending of components
727
728 Examples:
729
730 >>> a=DIV()
731 >>> a.append(SPAN('x'))
732 >>> print a
733 <div><span>x</span></div>
734 """
735 self._setnode(value)
736 ret = self.components.append(value)
737 self._fixup()
738 return ret
739
741 """
742 List-style inserting of components
743
744 Examples:
745
746 >>> a=DIV()
747 >>> a.insert(0,SPAN('x'))
748 >>> print a
749 <div><span>x</span></div>
750 """
751 self._setnode(value)
752 ret = self.components.insert(i, value)
753 self._fixup()
754 return ret
755
757 """
758 Gets attribute with name 'i' or component #i.
759 If attribute 'i' is not found returns None
760
761 Args:
762 i: index. If i is a string: the name of the attribute
763 otherwise references to number of the component
764 """
765
766 if isinstance(i, str):
767 try:
768 return self.attributes[i]
769 except KeyError:
770 return None
771 else:
772 return self.components[i]
773
775 """
776 Sets attribute with name 'i' or component #i.
777
778 Args:
779 i: index. If i is a string: the name of the attribute
780 otherwise references to number of the component
781 value: the new value
782 """
783 self._setnode(value)
784 if isinstance(i, (str, unicode)):
785 self.attributes[i] = value
786 else:
787 self.components[i] = value
788
790 """
791 Deletes attribute with name 'i' or component #i.
792
793 Args:
794 i: index. If i is a string: the name of the attribute
795 otherwise references to number of the component
796 """
797
798 if isinstance(i, str):
799 del self.attributes[i]
800 else:
801 del self.components[i]
802
804 """
805 Returns the number of included components
806 """
807 return len(self.components)
808
810 """
811 Always returns True
812 """
813 return True
814
816 """
817 Handling of provided components.
818
819 Nothing to fixup yet. May be overridden by subclasses,
820 eg for wrapping some components in another component or blocking them.
821 """
822 return
823
824 - def _wrap_components(self, allowed_parents,
825 wrap_parent=None,
826 wrap_lambda=None):
827 """
828 helper for _fixup. Checks if a component is in allowed_parents,
829 otherwise wraps it in wrap_parent
830
831 Args:
832 allowed_parents: (tuple) classes that the component should be an
833 instance of
834 wrap_parent: the class to wrap the component in, if needed
835 wrap_lambda: lambda to use for wrapping, if needed
836
837 """
838 components = []
839 for c in self.components:
840 if isinstance(c, allowed_parents):
841 pass
842 elif wrap_lambda:
843 c = wrap_lambda(c)
844 else:
845 c = wrap_parent(c)
846 if isinstance(c, DIV):
847 c.parent = self
848 components.append(c)
849 self.components = components
850
851 - def _postprocessing(self):
852 """
853 Handling of attributes (normally the ones not prefixed with '_').
854
855 Nothing to postprocess yet. May be overridden by subclasses
856 """
857 return
858
859 - def _traverse(self, status, hideerror=False):
860
861 newstatus = status
862 for c in self.components:
863 if hasattr(c, '_traverse') and callable(c._traverse):
864 c.vars = self.vars
865 c.request_vars = self.request_vars
866 c.errors = self.errors
867 c.latest = self.latest
868 c.session = self.session
869 c.formname = self.formname
870 if not c.attributes.get('hideerror'):
871 c['hideerror'] = hideerror or self.attributes.get('hideerror')
872 newstatus = c._traverse(status, hideerror) and newstatus
873
874
875
876
877 name = self['_name']
878 if newstatus:
879 newstatus = self._validate()
880 self._postprocessing()
881 elif 'old_value' in self.attributes:
882 self['value'] = self['old_value']
883 self._postprocessing()
884 elif name and name in self.vars:
885 self['value'] = self.vars[name]
886 self._postprocessing()
887 if name:
888 self.latest[name] = self['value']
889 return newstatus
890
892 """
893 nothing to validate yet. May be overridden by subclasses
894 """
895 return True
896
898 if isinstance(value, DIV):
899 value.parent = self
900
902 """
903 Helper for xml generation. Returns separately:
904 - the component attributes
905 - the generated xml of the inner components
906
907 Component attributes start with an underscore ('_') and
908 do not have a False or None value. The underscore is removed.
909 A value of True is replaced with the attribute name.
910
911 Returns:
912 tuple: (attributes, components)
913 """
914
915
916
917 attr = []
918 for key, value in self.attributes.iteritems():
919 if key[:1] != '_':
920 continue
921 name = key[1:]
922 if value is True:
923 value = name
924 elif value is False or value is None:
925 continue
926 attr.append((name, value))
927 data = self.attributes.get('data',{})
928 for key, value in data.iteritems():
929 name = 'data-' + key
930 value = data[key]
931 attr.append((name,value))
932 attr.sort()
933 fa = ''
934 for name,value in attr:
935 fa += ' %s="%s"' % (name, xmlescape(value, True))
936
937 co = join([xmlescape(component) for component in
938 self.components])
939
940 return (fa, co)
941
943 """
944 generates the xml for this component.
945 """
946
947 (fa, co) = self._xml()
948
949 if not self.tag:
950 return co
951
952 if self.tag[-1:] == '/':
953
954 return '<%s%s />' % (self.tag[:-1], fa)
955
956
957 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
958
960 """
961 str(COMPONENT) returns COMPONENT.xml()
962 """
963
964 return self.xml()
965
967 """
968 Returns the text stored by the DIV object rendered by the render function
969 the render function must take text, tagname, and attributes
970 `render=None` is equivalent to `render=lambda text, tag, attr: text`
971
972 Examples:
973
974 >>> markdown = lambda text,tag=None,attributes={}: \
975 {None: re.sub('\s+',' ',text), \
976 'h1':'#'+text+'\\n\\n', \
977 'p':text+'\\n'}.get(tag,text)
978 >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
979 >>> a.flatten(markdown)
980 '#Header\\n\\nthis is a test\\n'
981 """
982
983 text = ''
984 for c in self.components:
985 if isinstance(c, XmlComponent):
986 s = c.flatten(render)
987 elif render:
988 s = render(str(c))
989 else:
990 s = str(c)
991 text += s
992 if render:
993 text = render(text, self.tag, self.attributes)
994 return text
995
996 regex_tag = re.compile('^[\w\-\:]+')
997 regex_id = re.compile('#([\w\-]+)')
998 regex_class = re.compile('\.([\w\-]+)')
999 regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]')
1000
1002 """
1003 Find all components that match the supplied attribute dictionary,
1004 or None if nothing could be found
1005
1006 All components of the components are searched.
1007
1008 Examples:
1009
1010 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
1011 >>> for c in a.elements('span',first_only=True): c[0]='z'
1012 >>> print a
1013 <div><div><span>z</span>3<div><span>y</span></div></div></div>
1014 >>> for c in a.elements('span'): c[0]='z'
1015 >>> print a
1016 <div><div><span>z</span>3<div><span>z</span></div></div></div>
1017
1018 It also supports a syntax compatible with jQuery
1019
1020 Examples:
1021
1022 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
1023 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
1024 hello
1025 world
1026 >>> for e in a.elements('#1-1'): print e.flatten()
1027 hello
1028 >>> a.elements('a[u:v=$]')[0].xml()
1029 '<a id="1-1" u:v="$">hello</a>'
1030 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
1031 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
1032 >>> a.xml()
1033 '<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>'
1034
1035 Elements that are matched can also be replaced or removed by specifying
1036 a "replace" argument (note, a list of the original matching elements
1037 is still returned as usual).
1038
1039 Examples:
1040
1041 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1042 >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
1043 >>> print a
1044 <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
1045
1046 "replace" can be a callable, which will be passed the original element and
1047 should return a new element to replace it.
1048
1049 Examples:
1050
1051 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1052 >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
1053 >>> print a
1054 <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
1055
1056 If replace=None, matching elements will be removed completely.
1057
1058 Examples:
1059
1060 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1061 >>> b = a.elements('span', find='y', replace=None)
1062 >>> print a
1063 <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
1064
1065 If a "find_text" argument is specified, elements will be searched for text
1066 components that match find_text, and any matching text components will be
1067 replaced (find_text is ignored if "replace" is not also specified).
1068 Like the "find" argument, "find_text" can be a string or a compiled regex.
1069
1070 Examples:
1071
1072 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
1073 >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
1074 >>> print a
1075 <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
1076
1077 If other attributes are specified along with find_text, then only components
1078 that match the specified attributes will be searched for find_text.
1079
1080 Examples:
1081
1082 >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
1083 >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
1084 >>> print a
1085 <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
1086 """
1087 if len(args) == 1:
1088 args = [a.strip() for a in args[0].split(',')]
1089 if len(args) > 1:
1090 subset = [self.elements(a, **kargs) for a in args]
1091 return reduce(lambda a, b: a + b, subset, [])
1092 elif len(args) == 1:
1093 items = args[0].split()
1094 if len(items) > 1:
1095 subset = [a.elements(' '.join(
1096 items[1:]), **kargs) for a in self.elements(items[0])]
1097 return reduce(lambda a, b: a + b, subset, [])
1098 else:
1099 item = items[0]
1100 if '#' in item or '.' in item or '[' in item:
1101 match_tag = self.regex_tag.search(item)
1102 match_id = self.regex_id.search(item)
1103 match_class = self.regex_class.search(item)
1104 match_attr = self.regex_attr.finditer(item)
1105 args = []
1106 if match_tag:
1107 args = [match_tag.group()]
1108 if match_id:
1109 kargs['_id'] = match_id.group(1)
1110 if match_class:
1111 kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' %
1112 match_class.group(1).replace('-', '\\-').replace(':', '\\:'))
1113 for item in match_attr:
1114 kargs['_' + item.group(1)] = item.group(2)
1115 return self.elements(*args, **kargs)
1116
1117 matches = []
1118
1119
1120 tag = getattr(self, 'tag').replace('/', '')
1121 check = not (args and tag not in args)
1122 for (key, value) in kargs.iteritems():
1123 if key not in ['first_only', 'replace', 'find_text']:
1124 if isinstance(value, (str, int)):
1125 if self[key] != str(value):
1126 check = False
1127 elif key in self.attributes:
1128 if not value.search(str(self[key])):
1129 check = False
1130 else:
1131 check = False
1132 if 'find' in kargs:
1133 find = kargs['find']
1134 is_regex = not isinstance(find, (str, int))
1135 for c in self.components:
1136 if (isinstance(c, str) and ((is_regex and find.search(c)) or
1137 (str(find) in c))):
1138 check = True
1139
1140 if check:
1141 matches.append(self)
1142
1143 first_only = kargs.get('first_only', False)
1144 replace = kargs.get('replace', False)
1145 find_text = replace is not False and kargs.get('find_text', False)
1146 is_regex = not isinstance(find_text, (str, int, bool))
1147 find_components = not (check and first_only)
1148
1149 def replace_component(i):
1150 if replace is None:
1151 del self[i]
1152 return i
1153 else:
1154 self[i] = replace(self[i]) if callable(replace) else replace
1155 return i+1
1156
1157 if find_text or find_components:
1158 i = 0
1159 while i<len(self.components):
1160 c = self[i]
1161 j = i+1
1162 if check and find_text and isinstance(c, str) and \
1163 ((is_regex and find_text.search(c)) or (str(find_text) in c)):
1164 j = replace_component(i)
1165 elif find_components and isinstance(c, XmlComponent):
1166 child_matches = c.elements(*args, **kargs)
1167 if len(child_matches):
1168 if not find_text and replace is not False and child_matches[0] is c:
1169 j = replace_component(i)
1170 if first_only:
1171 return child_matches
1172 matches.extend(child_matches)
1173 i = j
1174 return matches
1175
1176 - def element(self, *args, **kargs):
1177 """
1178 Finds the first component that matches the supplied attribute dictionary,
1179 or None if nothing could be found
1180
1181 Also the components of the components are searched.
1182 """
1183 kargs['first_only'] = True
1184 elements = self.elements(*args, **kargs)
1185 if not elements:
1186
1187 return None
1188 return elements[0]
1189
1191 """
1192 Finds all sibling components that match the supplied argument list
1193 and attribute dictionary, or None if nothing could be found
1194 """
1195 sibs = [s for s in self.parent.components if not s == self]
1196 matches = []
1197 first_only = False
1198 if 'first_only' in kargs:
1199 first_only = kargs.pop('first_only')
1200 for c in sibs:
1201 try:
1202 check = True
1203 tag = getattr(c, 'tag').replace("/", "")
1204 if args and tag not in args:
1205 check = False
1206 for (key, value) in kargs.iteritems():
1207 if c[key] != value:
1208 check = False
1209 if check:
1210 matches.append(c)
1211 if first_only:
1212 break
1213 except:
1214 pass
1215 return matches
1216
1217 - def sibling(self, *args, **kargs):
1218 """
1219 Finds the first sibling component that match the supplied argument list
1220 and attribute dictionary, or None if nothing could be found
1221 """
1222 kargs['first_only'] = True
1223 sibs = self.siblings(*args, **kargs)
1224 if not sibs:
1225 return None
1226 return sibs[0]
1227
1228
1229 -class CAT(DIV):
1232
1235 return cPickle.loads(data)
1236
1239 d = DIV()
1240 d.__dict__ = data.__dict__
1241 marshal_dump = cPickle.dumps(d)
1242 return (TAG_unpickler, (marshal_dump,))
1243
1249
1250 copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
1253
1254 """
1255 TAG factory
1256
1257 Examples:
1258
1259 >>> print TAG.first(TAG.second('test'), _key = 3)
1260 <first key=\"3\"><second>test</second></first>
1261
1262 """
1263
1266
1273
1276
1277 TAG = __TAG__()
1278
1279
1280 -class HTML(DIV):
1281 """
1282 There are four predefined document type definitions.
1283 They can be specified in the 'doctype' parameter:
1284
1285 - 'strict' enables strict doctype
1286 - 'transitional' enables transitional doctype (default)
1287 - 'frameset' enables frameset doctype
1288 - 'html5' enables HTML 5 doctype
1289 - any other string will be treated as user's own doctype
1290
1291 'lang' parameter specifies the language of the document.
1292 Defaults to 'en'.
1293
1294 See also `DIV`
1295 """
1296
1297 tag = 'html'
1298
1299 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
1300 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
1301 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
1302 html5 = '<!DOCTYPE HTML>\n'
1303
1305 lang = self['lang']
1306 if not lang:
1307 lang = 'en'
1308 self.attributes['_lang'] = lang
1309 doctype = self['doctype']
1310 if doctype is None:
1311 doctype = self.transitional
1312 elif doctype == 'strict':
1313 doctype = self.strict
1314 elif doctype == 'transitional':
1315 doctype = self.transitional
1316 elif doctype == 'frameset':
1317 doctype = self.frameset
1318 elif doctype == 'html5':
1319 doctype = self.html5
1320 elif doctype == '':
1321 doctype = ''
1322 else:
1323 doctype = '%s\n' % doctype
1324 (fa, co) = self._xml()
1325 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1326
1329 """
1330 This is XHTML version of the HTML helper.
1331
1332 There are three predefined document type definitions.
1333 They can be specified in the 'doctype' parameter:
1334
1335 - 'strict' enables strict doctype
1336 - 'transitional' enables transitional doctype (default)
1337 - 'frameset' enables frameset doctype
1338 - any other string will be treated as user's own doctype
1339
1340 'lang' parameter specifies the language of the document and the xml document.
1341 Defaults to 'en'.
1342
1343 'xmlns' parameter specifies the xml namespace.
1344 Defaults to 'http://www.w3.org/1999/xhtml'.
1345
1346 See also `DIV`
1347 """
1348
1349 tag = 'html'
1350
1351 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
1352 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
1353 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
1354 xmlns = 'http://www.w3.org/1999/xhtml'
1355
1357 xmlns = self['xmlns']
1358 if xmlns:
1359 self.attributes['_xmlns'] = xmlns
1360 else:
1361 self.attributes['_xmlns'] = self.xmlns
1362 lang = self['lang']
1363 if not lang:
1364 lang = 'en'
1365 self.attributes['_lang'] = lang
1366 self.attributes['_xml:lang'] = lang
1367 doctype = self['doctype']
1368 if doctype:
1369 if doctype == 'strict':
1370 doctype = self.strict
1371 elif doctype == 'transitional':
1372 doctype = self.transitional
1373 elif doctype == 'frameset':
1374 doctype = self.frameset
1375 else:
1376 doctype = '%s\n' % doctype
1377 else:
1378 doctype = self.transitional
1379 (fa, co) = self._xml()
1380 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
1381
1382
1383 -class HEAD(DIV):
1386
1391
1396
1397
1398 -class LINK(DIV):
1401
1404
1405 tag = 'script'
1406
1408 (fa, co) = self._xml()
1409
1410 co = '\n'.join([str(component) for component in
1411 self.components])
1412 if co:
1413
1414
1415
1416
1417 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
1418 else:
1419 return DIV.xml(self)
1420
1423
1424 tag = 'style'
1425
1427 (fa, co) = self._xml()
1428
1429 co = '\n'.join([str(component) for component in
1430 self.components])
1431 if co:
1432
1433
1434
1435 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
1436 else:
1437 return DIV.xml(self)
1438
1439
1440 -class IMG(DIV):
1443
1444
1445 -class SPAN(DIV):
1448
1449
1450 -class BODY(DIV):
1453
1454
1455 -class H1(DIV):
1458
1459
1460 -class H2(DIV):
1463
1464
1465 -class H3(DIV):
1468
1469
1470 -class H4(DIV):
1473
1474
1475 -class H5(DIV):
1478
1479
1480 -class H6(DIV):
1483
1486 """
1487 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
1488
1489 see also `DIV`
1490 """
1491
1492 tag = 'p'
1493
1495 text = DIV.xml(self)
1496 if self['cr2br']:
1497 text = text.replace('\n', '<br />')
1498 return text
1499
1504
1509
1510
1511 -class BR(DIV):
1514
1515
1516 -class HR(DIV):
1519
1522 """
1523 Generates an A() link.
1524 A() in web2py is really important and with the included web2py.js
1525 allows lots of Ajax interactions in the page
1526
1527 On top of "usual" `_attributes`, it takes
1528
1529 Args:
1530 callback: an url to call but not redirect to
1531 cid: if you want to load the _href into an element of the page (component)
1532 pass its id (without the #) here
1533 delete: element to delete after calling callback
1534 target: same thing as cid
1535 confirm: text to display upon a callback with a delete
1536 noconfirm: don't display alert upon a callback with delete
1537
1538 """
1539
1540 tag = 'a'
1541
1543 if not self.components and self['_href']:
1544 self.append(self['_href'])
1545 disable_needed = ['callback', 'cid', 'delete', 'component', 'target']
1546 disable_needed = any((self[attr] for attr in disable_needed))
1547 if disable_needed:
1548 self['_data-w2p_disable_with'] = self['_disable_with'] or 'default'
1549 self['_disable_with'] = None
1550 if self['callback'] and not self['_id']:
1551 self['_id'] = web2py_uuid()
1552 if self['delete']:
1553 self['_data-w2p_remove'] = self['delete']
1554 if self['target']:
1555 if self['target'] == '<self>':
1556 self['target'] = self['_id']
1557 self['_data-w2p_target'] = self['target']
1558 if self['component']:
1559 self['_data-w2p_method'] = 'GET'
1560 self['_href'] = self['component']
1561 elif self['callback']:
1562 self['_data-w2p_method'] = 'POST'
1563 self['_href'] = self['callback']
1564 if self['delete'] and not self['noconfirm']:
1565 if not self['confirm']:
1566 self['_data-w2p_confirm'] = 'default'
1567 else:
1568 self['_data-w2p_confirm'] = self['confirm']
1569 elif self['cid']:
1570 self['_data-w2p_method'] = 'GET'
1571 self['_data-w2p_target'] = self['cid']
1572 if self['pre_call']:
1573 self['_data-w2p_pre_call'] = self['pre_call']
1574 return DIV.xml(self)
1575
1579
1580
1581 -class EM(DIV):
1584
1589
1590
1591 -class TT(DIV):
1594
1595
1596 -class PRE(DIV):
1599
1604
1605
1606 -class CODE(DIV):
1607
1608 """
1609 Displays code in HTML with syntax highlighting.
1610
1611 Args:
1612 language: indicates the language, otherwise PYTHON is assumed
1613 link: can provide a link
1614 styles: for styles
1615
1616 Examples:
1617
1618 {{=CODE(\"print 'hello world'\", language='python', link=None,
1619 counter=1, styles={}, highlight_line=None)}}
1620
1621
1622 supported languages are
1623
1624 "python", "html_plain", "c", "cpp", "web2py", "html"
1625
1626 The "html" language interprets {{ and }} tags as "web2py" code,
1627 "html_plain" doesn't.
1628
1629 if a link='/examples/global/vars/' is provided web2py keywords are linked to
1630 the online docs.
1631
1632 the counter is used for line numbering, counter can be None or a prompt
1633 string.
1634 """
1635
1637 language = self['language'] or 'PYTHON'
1638 link = self['link']
1639 counter = self.attributes.get('counter', 1)
1640 highlight_line = self.attributes.get('highlight_line', None)
1641 context_lines = self.attributes.get('context_lines', None)
1642 styles = self['styles'] or {}
1643 return highlight(
1644 join(self.components),
1645 language=language,
1646 link=link,
1647 counter=counter,
1648 styles=styles,
1649 attributes=self.attributes,
1650 highlight_line=highlight_line,
1651 context_lines=context_lines,
1652 )
1653
1658
1659
1660 -class LI(DIV):
1663
1664
1665 -class UL(DIV):
1666 """
1667 UL Component.
1668
1669 If subcomponents are not LI-components they will be wrapped in a LI
1670
1671 """
1672
1673 tag = 'ul'
1674
1677
1682
1683
1684 -class TD(DIV):
1687
1688
1689 -class TH(DIV):
1692
1693
1694 -class TR(DIV):
1695 """
1696 TR Component.
1697
1698 If subcomponents are not TD/TH-components they will be wrapped in a TD
1699
1700 """
1701
1702 tag = 'tr'
1703
1706
1709 """
1710 __TRHEAD__ Component, internal only
1711
1712 If subcomponents are not TD/TH-components they will be wrapped in a TH
1713
1714 """
1715
1716 tag = 'tr'
1717
1720
1723
1724 tag = 'thead'
1725
1728
1729
1730 -class TBODY(DIV):
1731
1732 tag = 'tbody'
1733
1736
1744
1745
1746 -class COL(DIV):
1749
1752
1753 tag = 'colgroup'
1754
1757 """
1758 TABLE Component.
1759
1760 If subcomponents are not TR/TBODY/THEAD/TFOOT-components
1761 they will be wrapped in a TR
1762
1763 """
1764
1765 tag = 'table'
1766
1769
1774
1779
1904
1905
1906 -class TEXTAREA(INPUT):
1907
1908 """
1909 Examples::
1910
1911 TEXTAREA(_name='sometext', value='blah ' * 100, requires=IS_NOT_EMPTY())
1912
1913 'blah blah blah ...' will be the content of the textarea field.
1914
1915 """
1916
1917 tag = 'textarea'
1918
1919 - def _postprocessing(self):
1920 if not '_rows' in self.attributes:
1921 self['_rows'] = 10
1922 if not '_cols' in self.attributes:
1923 self['_cols'] = 40
1924 if not self['value'] is None:
1925 self.components = [self['value']]
1926 elif self.components:
1927 self['value'] = self.components[0]
1928
1931
1932 tag = 'option'
1933
1935 if not '_value' in self.attributes:
1936 self.attributes['_value'] = str(self.components[0])
1937
1942
1945
1946 tag = 'optgroup'
1947
1949 components = []
1950 for c in self.components:
1951 if isinstance(c, OPTION):
1952 components.append(c)
1953 else:
1954 components.append(OPTION(c, _value=str(c)))
1955 self.components = components
1956
1959
1960 """
1961 Examples:
1962
1963 >>> from validators import IS_IN_SET
1964 >>> SELECT('yes', 'no', _name='selector', value='yes',
1965 ... requires=IS_IN_SET(['yes', 'no'])).xml()
1966 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
1967
1968 """
1969
1970 tag = 'select'
1971
1973 components = []
1974 for c in self.components:
1975 if isinstance(c, (OPTION, OPTGROUP)):
1976 components.append(c)
1977 else:
1978 components.append(OPTION(c, _value=str(c)))
1979 self.components = components
1980
1981 - def _postprocessing(self):
1982 component_list = []
1983 for c in self.components:
1984 if isinstance(c, OPTGROUP):
1985 component_list.append(c.components)
1986 else:
1987 component_list.append([c])
1988 options = itertools.chain(*component_list)
1989
1990 value = self['value']
1991 if not value is None:
1992 if not self['_multiple']:
1993 for c in options:
1994 if ((value is not None) and
1995 (str(c['_value']) == str(value))):
1996 c['_selected'] = 'selected'
1997 else:
1998 c['_selected'] = None
1999 else:
2000 if isinstance(value, (list, tuple)):
2001 values = [str(item) for item in value]
2002 else:
2003 values = [str(value)]
2004 for c in options:
2005 if ((value is not None) and
2006 (str(c['_value']) in values)):
2007 c['_selected'] = 'selected'
2008 else:
2009 c['_selected'] = None
2010
2013
2014 tag = 'fieldset'
2015
2020
2358 return flatten(d)
2359
2364
2369
2374
2377
2378 """
2379 Turns any list, dictionary, etc into decent looking html.
2380
2381 Two special attributes are
2382
2383 - sorted: a function that takes the dict and returned sorted keys
2384 - keyfilter: a function that takes a key and returns its representation or
2385 None if the key is to be skipped.
2386 By default key[:1]=='_' is skipped.
2387
2388 Examples:
2389
2390 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
2391 '<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>'
2392
2393 """
2394
2395 tag = 'div'
2396
2397 @staticmethod
2399 if key[:1] == '_':
2400 return None
2401 return key
2402
2403 - def __init__(self, component, **attributes):
2404 self.components = [component]
2405 self.attributes = attributes
2406 sorter = attributes.get('sorted', sorted)
2407 keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
2408 components = []
2409 attributes = copy.copy(self.attributes)
2410 level = attributes['level'] = attributes.get('level', 6) - 1
2411 if '_class' in attributes:
2412 attributes['_class'] += 'i'
2413 if level == 0:
2414 return
2415 for c in self.components:
2416 if hasattr(c, 'value') and not callable(c.value):
2417 if c.value:
2418 components.append(c.value)
2419 if hasattr(c, 'xml') and callable(c.xml):
2420 components.append(c)
2421 continue
2422 elif hasattr(c, 'keys') and callable(c.keys):
2423 rows = []
2424 try:
2425 keys = (sorter and sorter(c)) or c
2426 for key in keys:
2427 if isinstance(key, (str, unicode)) and keyfilter:
2428 filtered_key = keyfilter(key)
2429 else:
2430 filtered_key = str(key)
2431 if filtered_key is None:
2432 continue
2433 value = c[key]
2434 if isinstance(value, types.LambdaType):
2435 continue
2436 rows.append(
2437 TR(
2438 TD(filtered_key, _style='font-weight:bold;vertical-align:top;'),
2439 TD(':', _style='vertical-align:top;'),
2440 TD(BEAUTIFY(value, **attributes))))
2441 components.append(TABLE(*rows, **attributes))
2442 continue
2443 except:
2444 pass
2445 if isinstance(c, str):
2446 components.append(str(c))
2447 elif isinstance(c, unicode):
2448 components.append(c.encode('utf8'))
2449 elif isinstance(c, (list, tuple)):
2450 items = [TR(TD(BEAUTIFY(item, **attributes)))
2451 for item in c]
2452 components.append(TABLE(*items, **attributes))
2453 elif isinstance(c, cgi.FieldStorage):
2454 components.append('FieldStorage object')
2455 else:
2456 components.append(repr(c))
2457 self.components = components
2458
2461 """
2462 Used to build menus
2463
2464 Args:
2465 _class: defaults to 'web2py-menu web2py-menu-vertical'
2466 ul_class: defaults to 'web2py-menu-vertical'
2467 li_class: defaults to 'web2py-menu-expand'
2468 li_first: defaults to 'web2py-menu-first'
2469 li_last: defaults to 'web2py-menu-last'
2470
2471 Use like::
2472
2473 menu = MENU([['name', False, URL(...), [submenu]], ...])
2474 {{=menu}}
2475
2476 """
2477
2478 tag = 'ul'
2479
2481 self.data = data
2482 self.attributes = args
2483 self.components = []
2484 if not '_class' in self.attributes:
2485 self['_class'] = 'web2py-menu web2py-menu-vertical'
2486 if not 'ul_class' in self.attributes:
2487 self['ul_class'] = 'web2py-menu-vertical'
2488 if not 'li_class' in self.attributes:
2489 self['li_class'] = 'web2py-menu-expand'
2490 if not 'li_first' in self.attributes:
2491 self['li_first'] = 'web2py-menu-first'
2492 if not 'li_last' in self.attributes:
2493 self['li_last'] = 'web2py-menu-last'
2494 if not 'li_active' in self.attributes:
2495 self['li_active'] = 'web2py-menu-active'
2496 if not 'mobile' in self.attributes:
2497 self['mobile'] = False
2498
2500 if level == 0:
2501 ul = UL(**self.attributes)
2502 else:
2503 ul = UL(_class=self['ul_class'])
2504 for item in data:
2505 if isinstance(item,LI):
2506 ul.append(item)
2507 else:
2508 (name, active, link) = item[:3]
2509 if isinstance(link, DIV):
2510 li = LI(link)
2511 elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
2512 li = LI(DIV(name))
2513 elif isinstance(link,dict):
2514 li = LI(A(name, **link))
2515 elif link:
2516 li = LI(A(name, _href=link))
2517 elif not link and isinstance(name, A):
2518 li = LI(name)
2519 else:
2520 li = LI(A(name, _href='#',
2521 _onclick='javascript:void(0);return false;'))
2522 if level == 0 and item == data[0]:
2523 li['_class'] = self['li_first']
2524 elif level == 0 and item == data[-1]:
2525 li['_class'] = self['li_last']
2526 if len(item) > 3 and item[3]:
2527 li['_class'] = self['li_class']
2528 li.append(self.serialize(item[3], level + 1))
2529 if active or ('active_url' in self.attributes and self['active_url'] == link):
2530 if li['_class']:
2531 li['_class'] = li['_class'] + ' ' + self['li_active']
2532 else:
2533 li['_class'] = self['li_active']
2534 if len(item) <= 4 or item[4] == True:
2535 ul.append(li)
2536 return ul
2537
2539 if not select:
2540 select = SELECT(**self.attributes)
2541 custom_items = []
2542 for item in data:
2543
2544 if len(item) >= 3 and (not item[0]) or (isinstance(item[0], DIV) and not (item[2])):
2545
2546
2547 custom_items.append(item)
2548 elif len(item) <= 4 or item[4] == True:
2549 select.append(OPTION(CAT(prefix, item[0]),
2550 _value=item[2], _selected=item[1]))
2551 if len(item) > 3 and len(item[3]):
2552 self.serialize_mobile(
2553 item[3], select, prefix=CAT(prefix, item[0], '/'))
2554 select['_onchange'] = 'window.location=this.value'
2555
2556 html = DIV(select, self.serialize(custom_items)) if len( custom_items) else select
2557 return html
2558
2564
2565
2566 -def embed64(
2567 filename=None,
2568 file=None,
2569 data=None,
2570 extension='image/gif',
2571 ):
2572 """
2573 helper to encode the provided (binary) data into base64.
2574
2575 Args:
2576 filename: if provided, opens and reads this file in 'rb' mode
2577 file: if provided, reads this file
2578 data: if provided, uses the provided data
2579 """
2580
2581 if filename and os.path.exists(file):
2582 fp = open(filename, 'rb')
2583 data = fp.read()
2584 fp.close()
2585 data = base64.b64encode(data)
2586 return 'data:%s;base64,%s' % (extension, data)
2587
2590 """
2591 Example:
2592
2593 >>> from validators import *
2594 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml()
2595 <div><a href="/a/b/c">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
2596 >>> print DIV(UL("doc","cat","mouse")).xml()
2597 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
2598 >>> print DIV(UL("doc", LI("cat", _class='feline'), 18)).xml()
2599 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
2600 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
2601 <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>
2602 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
2603 >>> print form.xml()
2604 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
2605 >>> print form.accepts({'myvar':'34'}, formname=None)
2606 False
2607 >>> print form.xml()
2608 <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>
2609 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
2610 True
2611 >>> print form.xml()
2612 <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
2613 >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
2614 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
2615 True
2616 >>> print form.xml()
2617 <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>
2618 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
2619 >>> print form.accepts({'myvar':'as df'}, formname=None)
2620 False
2621 >>> print form.xml()
2622 <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>
2623 >>> session={}
2624 >>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
2625 >>> isinstance(form.as_dict(), dict)
2626 True
2627 >>> form.as_dict(flat=True).has_key("vars")
2628 True
2629 >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
2630 True
2631 >>> if form.accepts({}, session,formname=None): print 'passed'
2632 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
2633 """
2634 pass
2635
2638 """
2639 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
2640 obj.tree contains the root of the tree, and tree can be manipulated
2641
2642 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
2643 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
2644 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
2645 '<div>a<span>b</span></div>c'
2646 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
2647 >>> tree.element(_a='b')['_c']=5
2648 >>> str(tree)
2649 'hello<div a="b" c="5">world</div>'
2650 """
2651 - def __init__(self, text, closed=('input', 'link')):
2652 HTMLParser.__init__(self)
2653 self.tree = self.parent = TAG['']()
2654 self.closed = closed
2655 self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
2656 self.last = None
2657 self.feed(text)
2658
2660 if tagname.upper() in self.tags:
2661 tag = eval(tagname.upper())
2662 else:
2663 if tagname in self.closed:
2664 tagname += '/'
2665 tag = TAG[tagname]()
2666 for key, value in attrs:
2667 tag['_' + key] = value
2668 tag.parent = self.parent
2669 self.parent.append(tag)
2670 if not tag.tag.endswith('/'):
2671 self.parent = tag
2672 else:
2673 self.last = tag.tag[:-1]
2674
2676 if not isinstance(data, unicode):
2677 try:
2678 data = data.decode('utf8')
2679 except:
2680 data = data.decode('latin1')
2681 self.parent.append(data.encode('utf8', 'xmlcharref'))
2682
2688
2691
2693
2694 if tagname == self.last:
2695 return
2696 while True:
2697 try:
2698 parent_tagname = self.parent.tag
2699 self.parent = self.parent.parent
2700 except:
2701 raise RuntimeError("unable to balance tag %s" % tagname)
2702 if parent_tagname[:len(tagname)] == tagname: break
2703
2706 attr = attr or {}
2707 if tag is None:
2708 return re.sub('\s+', ' ', text)
2709 if tag == 'br':
2710 return '\n\n'
2711 if tag == 'h1':
2712 return '#' + text + '\n\n'
2713 if tag == 'h2':
2714 return '#' * 2 + text + '\n\n'
2715 if tag == 'h3':
2716 return '#' * 3 + text + '\n\n'
2717 if tag == 'h4':
2718 return '#' * 4 + text + '\n\n'
2719 if tag == 'p':
2720 return text + '\n\n'
2721 if tag == 'b' or tag == 'strong':
2722 return '**%s**' % text
2723 if tag == 'em' or tag == 'i':
2724 return '*%s*' % text
2725 if tag == 'tt' or tag == 'code':
2726 return '`%s`' % text
2727 if tag == 'a':
2728 return '[%s](%s)' % (text, attr.get('_href', ''))
2729 if tag == 'img':
2730 return '' % (attr.get('_alt', ''), attr.get('_src', ''))
2731 return text
2732
2735 attr = attr or {}
2736
2737 if tag == 'br':
2738 return '\n\n'
2739 if tag == 'h1':
2740 return '# ' + text + '\n\n'
2741 if tag == 'h2':
2742 return '#' * 2 + ' ' + text + '\n\n'
2743 if tag == 'h3':
2744 return '#' * 3 + ' ' + text + '\n\n'
2745 if tag == 'h4':
2746 return '#' * 4 + ' ' + text + '\n\n'
2747 if tag == 'p':
2748 return text + '\n\n'
2749 if tag == 'li':
2750 return '\n- ' + text.replace('\n', ' ')
2751 if tag == 'tr':
2752 return text[3:].replace('\n', ' ') + '\n'
2753 if tag in ['table', 'blockquote']:
2754 return '\n-----\n' + text + '\n------\n'
2755 if tag in ['td', 'th']:
2756 return ' | ' + text
2757 if tag in ['b', 'strong', 'label']:
2758 return '**%s**' % text
2759 if tag in ['em', 'i']:
2760 return "''%s''" % text
2761 if tag in ['tt']:
2762 return '``%s``' % text.strip()
2763 if tag in ['code']:
2764 return '``\n%s``' % text
2765 if tag == 'a':
2766 return '[[%s %s]]' % (text, attr.get('_href', ''))
2767 if tag == 'img':
2768 return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
2769 return text
2770
2773 """
2774 For documentation: http://web2py.com/examples/static/markmin.html
2775 """
2776 - def __init__(self,
2777 text, extra=None, allowed=None, sep='p',
2778 url=None, environment=None, latex='google',
2779 autolinks='default',
2780 protolinks='default',
2781 class_prefix='',
2782 id_prefix='markmin_',
2783 **kwargs):
2784 self.text = text
2785 self.extra = extra or {}
2786 self.allowed = allowed or {}
2787 self.sep = sep
2788 self.url = URL if url == True else url
2789 self.environment = environment
2790 self.latex = latex
2791 self.autolinks = autolinks
2792 self.protolinks = protolinks
2793 self.class_prefix = class_prefix
2794 self.id_prefix = id_prefix
2795 self.kwargs = kwargs
2796
2799
2801 from gluon.contrib.markmin.markmin2html import render
2802 html = render(self.text, extra=self.extra,
2803 allowed=self.allowed, sep=self.sep, latex=self.latex,
2804 URL=self.url, environment=self.environment,
2805 autolinks=self.autolinks, protolinks=self.protolinks,
2806 class_prefix=self.class_prefix, id_prefix=self.id_prefix)
2807 return html if not self.kwargs else DIV(XML(html), **self.kwargs).xml()
2808
2811
2812 if __name__ == '__main__':
2813 import doctest
2814 doctest.testmod()
2815