PageRenderTime 83ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 1ms

/gluon/html.py

https://github.com/gokceneraslan/web2py
Python | 2732 lines | 2574 code | 76 blank | 82 comment | 118 complexity | 4c2d525a4d2df0a97db141bf31b57ec8 MD5 | raw file
Possible License(s): BSD-2-Clause, MIT, BSD-3-Clause
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This file is part of the web2py Web Framework
  5. Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
  6. License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
  7. """
  8. import cgi
  9. import os
  10. import re
  11. import copy
  12. import types
  13. import urllib
  14. import base64
  15. import sanitizer
  16. import itertools
  17. import decoder
  18. import copy_reg
  19. import cPickle
  20. import marshal
  21. from HTMLParser import HTMLParser
  22. from htmlentitydefs import name2codepoint
  23. from storage import Storage
  24. from utils import web2py_uuid, simple_hash, compare
  25. from highlight import highlight
  26. regex_crlf = re.compile('\r|\n')
  27. join = ''.join
  28. # name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing.
  29. entitydefs = dict(map(lambda (
  30. k, v): (k, unichr(v).encode('utf-8')), name2codepoint.iteritems()))
  31. entitydefs.setdefault('apos', u"'".encode('utf-8'))
  32. __all__ = [
  33. 'A',
  34. 'B',
  35. 'BEAUTIFY',
  36. 'BODY',
  37. 'BR',
  38. 'BUTTON',
  39. 'CENTER',
  40. 'CAT',
  41. 'CODE',
  42. 'COL',
  43. 'COLGROUP',
  44. 'DIV',
  45. 'EM',
  46. 'EMBED',
  47. 'FIELDSET',
  48. 'FORM',
  49. 'H1',
  50. 'H2',
  51. 'H3',
  52. 'H4',
  53. 'H5',
  54. 'H6',
  55. 'HEAD',
  56. 'HR',
  57. 'HTML',
  58. 'I',
  59. 'IFRAME',
  60. 'IMG',
  61. 'INPUT',
  62. 'LABEL',
  63. 'LEGEND',
  64. 'LI',
  65. 'LINK',
  66. 'OL',
  67. 'UL',
  68. 'MARKMIN',
  69. 'MENU',
  70. 'META',
  71. 'OBJECT',
  72. 'ON',
  73. 'OPTION',
  74. 'P',
  75. 'PRE',
  76. 'SCRIPT',
  77. 'OPTGROUP',
  78. 'SELECT',
  79. 'SPAN',
  80. 'STRONG',
  81. 'STYLE',
  82. 'TABLE',
  83. 'TAG',
  84. 'TD',
  85. 'TEXTAREA',
  86. 'TH',
  87. 'THEAD',
  88. 'TBODY',
  89. 'TFOOT',
  90. 'TITLE',
  91. 'TR',
  92. 'TT',
  93. 'URL',
  94. 'XHTML',
  95. 'XML',
  96. 'xmlescape',
  97. 'embed64',
  98. ]
  99. def xmlescape(data, quote=True):
  100. """
  101. returns an escaped string of the provided data
  102. :param data: the data to be escaped
  103. :param quote: optional (default False)
  104. """
  105. # first try the xml function
  106. if hasattr(data, 'xml') and callable(data.xml):
  107. return data.xml()
  108. # otherwise, make it a string
  109. if not isinstance(data, (str, unicode)):
  110. data = str(data)
  111. elif isinstance(data, unicode):
  112. data = data.encode('utf8', 'xmlcharrefreplace')
  113. # ... and do the escaping
  114. data = cgi.escape(data, quote).replace("'", "&#x27;")
  115. return data
  116. def call_as_list(f,*a,**b):
  117. if not isinstance(f, (list,tuple)):
  118. f = [f]
  119. for item in f:
  120. item(*a,**b)
  121. def truncate_string(text, length, dots='...'):
  122. text = text.decode('utf-8')
  123. if len(text) > length:
  124. text = text[:length - len(dots)].encode('utf-8') + dots
  125. return text
  126. def URL(
  127. a=None,
  128. c=None,
  129. f=None,
  130. r=None,
  131. args=None,
  132. vars=None,
  133. anchor='',
  134. extension=None,
  135. env=None,
  136. hmac_key=None,
  137. hash_vars=True,
  138. salt=None,
  139. user_signature=None,
  140. scheme=None,
  141. host=None,
  142. port=None,
  143. encode_embedded_slash=False,
  144. url_encode=True
  145. ):
  146. """
  147. generate a URL
  148. example::
  149. >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
  150. ... vars={'p':1, 'q':2}, anchor='1'))
  151. '/a/c/f/x/y/z?p=1&q=2#1'
  152. >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
  153. ... vars={'p':(1,3), 'q':2}, anchor='1'))
  154. '/a/c/f/x/y/z?p=1&p=3&q=2#1'
  155. >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
  156. ... vars={'p':(3,1), 'q':2}, anchor='1'))
  157. '/a/c/f/x/y/z?p=3&p=1&q=2#1'
  158. >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
  159. '/a/c/f#1%2B2'
  160. >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
  161. ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
  162. '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1'
  163. >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z']))
  164. '/a/c/f/w/x/y/z'
  165. >>> str(URL(a='a', c='c', f='f', args=['w/x', 'y/z'], encode_embedded_slash=True))
  166. '/a/c/f/w%2Fx/y%2Fz'
  167. >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=False))
  168. '/a/c/f/%(id)d'
  169. >>> str(URL(a='a', c='c', f='f', args=['%(id)d'], url_encode=True))
  170. '/a/c/f/%25%28id%29d'
  171. >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=False))
  172. '/a/c/f?id=%(id)d'
  173. >>> str(URL(a='a', c='c', f='f', vars={'id' : '%(id)d' }, url_encode=True))
  174. '/a/c/f?id=%25%28id%29d'
  175. >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=False))
  176. '/a/c/f#%(id)d'
  177. >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
  178. '/a/c/f#%25%28id%29d'
  179. generates a url '/a/c/f' corresponding to application a, controller c
  180. and function f. If r=request is passed, a, c, f are set, respectively,
  181. to r.application, r.controller, r.function.
  182. The more typical usage is:
  183. URL(r=request, f='index') that generates a url for the index function
  184. within the present application and controller.
  185. :param a: application (default to current if r is given)
  186. :param c: controller (default to current if r is given)
  187. :param f: function (default to current if r is given)
  188. :param r: request (optional)
  189. :param args: any arguments (optional)
  190. :param vars: any variables (optional)
  191. :param anchor: anchorname, without # (optional)
  192. :param hmac_key: key to use when generating hmac signature (optional)
  193. :param hash_vars: which of the vars to include in our hmac signature
  194. True (default) - hash all vars, False - hash none of the vars,
  195. iterable - hash only the included vars ['key1','key2']
  196. :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
  197. :param host: string to force absolute URL with host (True means http_host)
  198. :param port: optional port number (forces absolute URL)
  199. :raises SyntaxError: when no application, controller or function is
  200. available
  201. :raises SyntaxError: when a CRLF is found in the generated url
  202. """
  203. from rewrite import url_out # done here in case used not-in web2py
  204. if args in (None, []):
  205. args = []
  206. vars = vars or {}
  207. application = None
  208. controller = None
  209. function = None
  210. if not isinstance(args, (list, tuple)):
  211. args = [args]
  212. if not r:
  213. if a and not c and not f:
  214. (f, a, c) = (a, c, f)
  215. elif a and c and not f:
  216. (c, f, a) = (a, c, f)
  217. from globals import current
  218. if hasattr(current, 'request'):
  219. r = current.request
  220. if r:
  221. application = r.application
  222. controller = r.controller
  223. function = r.function
  224. env = r.env
  225. if extension is None and r.extension != 'html':
  226. extension = r.extension
  227. if a:
  228. application = a
  229. if c:
  230. controller = c
  231. if f:
  232. if not isinstance(f, str):
  233. if hasattr(f, '__name__'):
  234. function = f.__name__
  235. else:
  236. raise SyntaxError(
  237. 'when calling URL, function or function name required')
  238. elif '/' in f:
  239. if f.startswith("/"):
  240. f = f[1:]
  241. items = f.split('/')
  242. function = f = items[0]
  243. args = items[1:] + args
  244. else:
  245. function = f
  246. # if the url gets a static resource, don't force extention
  247. if controller == 'static':
  248. extension = None
  249. if '.' in function:
  250. function, extension = function.rsplit('.', 1)
  251. function2 = '%s.%s' % (function, extension or 'html')
  252. if not (application and controller and function):
  253. raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function))
  254. if args:
  255. if url_encode:
  256. if encode_embedded_slash:
  257. other = '/' + '/'.join([urllib.quote(str(
  258. x), '') for x in args])
  259. else:
  260. other = args and urllib.quote(
  261. '/' + '/'.join([str(x) for x in args]))
  262. else:
  263. other = args and ('/' + '/'.join([str(x) for x in args]))
  264. else:
  265. other = ''
  266. if other.endswith('/'):
  267. other += '/' # add trailing slash to make last trailing empty arg explicit
  268. list_vars = []
  269. for (key, vals) in sorted(vars.items()):
  270. if key == '_signature':
  271. continue
  272. if not isinstance(vals, (list, tuple)):
  273. vals = [vals]
  274. for val in vals:
  275. list_vars.append((key, val))
  276. if user_signature:
  277. from globals import current
  278. if current.session.auth:
  279. hmac_key = current.session.auth.hmac_key
  280. if hmac_key:
  281. # generate an hmac signature of the vars & args so can later
  282. # verify the user hasn't messed with anything
  283. h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
  284. # how many of the vars should we include in our hash?
  285. if hash_vars is True: # include them all
  286. h_vars = list_vars
  287. elif hash_vars is False: # include none of them
  288. h_vars = ''
  289. else: # include just those specified
  290. if hash_vars and not isinstance(hash_vars, (list, tuple)):
  291. hash_vars = [hash_vars]
  292. h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
  293. # re-assembling the same way during hash authentication
  294. message = h_args + '?' + urllib.urlencode(sorted(h_vars))
  295. sig = simple_hash(
  296. message, hmac_key or '', salt or '', digest_alg='sha1')
  297. # add the signature into vars
  298. list_vars.append(('_signature', sig))
  299. if list_vars:
  300. if url_encode:
  301. other += '?%s' % urllib.urlencode(list_vars)
  302. else:
  303. other += '?%s' % '&'.join(['%s=%s' % var[:2] for var in list_vars])
  304. if anchor:
  305. if url_encode:
  306. other += '#' + urllib.quote(str(anchor))
  307. else:
  308. other += '#' + (str(anchor))
  309. if extension:
  310. function += '.' + extension
  311. if regex_crlf.search(join([application, controller, function, other])):
  312. raise SyntaxError('CRLF Injection Detected')
  313. url = url_out(r, env, application, controller, function,
  314. args, other, scheme, host, port)
  315. return url
  316. def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None):
  317. """
  318. Verifies that a request's args & vars have not been tampered with by the user
  319. :param request: web2py's request object
  320. :param hmac_key: the key to authenticate with, must be the same one previously
  321. used when calling URL()
  322. :param hash_vars: which vars to include in our hashing. (Optional)
  323. Only uses the 1st value currently
  324. True (or undefined) means all, False none,
  325. an iterable just the specified keys
  326. do not call directly. Use instead:
  327. URL.verify(hmac_key='...')
  328. the key has to match the one used to generate the URL.
  329. >>> r = Storage()
  330. >>> gv = Storage(p=(1,3),q=2,_signature='a32530f0d0caa80964bb92aad2bedf8a4486a31f')
  331. >>> r.update(dict(application='a', controller='c', function='f', extension='html'))
  332. >>> r['args'] = ['x', 'y', 'z']
  333. >>> r['get_vars'] = gv
  334. >>> verifyURL(r, 'key')
  335. True
  336. >>> verifyURL(r, 'kay')
  337. False
  338. >>> r.get_vars.p = (3, 1)
  339. >>> verifyURL(r, 'key')
  340. True
  341. >>> r.get_vars.p = (3, 2)
  342. >>> verifyURL(r, 'key')
  343. False
  344. """
  345. if not '_signature' in request.get_vars:
  346. return False # no signature in the request URL
  347. # check if user_signature requires
  348. if user_signature:
  349. from globals import current
  350. if not current.session or not current.session.auth:
  351. return False
  352. hmac_key = current.session.auth.hmac_key
  353. if not hmac_key:
  354. return False
  355. # get our sig from request.get_vars for later comparison
  356. original_sig = request.get_vars._signature
  357. # now generate a new hmac for the remaining args & vars
  358. vars, args = request.get_vars, request.args
  359. # remove the signature var since it was not part of our signed message
  360. request.get_vars.pop('_signature')
  361. # join all the args & vars into one long string
  362. # always include all of the args
  363. other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
  364. h_args = '/%s/%s/%s.%s%s' % (request.application,
  365. request.controller,
  366. request.function,
  367. request.extension,
  368. other)
  369. # but only include those vars specified (allows more flexibility for use with
  370. # forms or ajax)
  371. list_vars = []
  372. for (key, vals) in sorted(vars.items()):
  373. if not isinstance(vals, (list, tuple)):
  374. vals = [vals]
  375. for val in vals:
  376. list_vars.append((key, val))
  377. # which of the vars are to be included?
  378. if hash_vars is True: # include them all
  379. h_vars = list_vars
  380. elif hash_vars is False: # include none of them
  381. h_vars = ''
  382. else: # include just those specified
  383. # wrap in a try - if the desired vars have been removed it'll fail
  384. try:
  385. if hash_vars and not isinstance(hash_vars, (list, tuple)):
  386. hash_vars = [hash_vars]
  387. h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
  388. except:
  389. # user has removed one of our vars! Immediate fail
  390. return False
  391. # build the full message string with both args & vars
  392. message = h_args + '?' + urllib.urlencode(sorted(h_vars))
  393. # hash with the hmac_key provided
  394. sig = simple_hash(message, str(hmac_key), salt or '', digest_alg='sha1')
  395. # put _signature back in get_vars just in case a second call to URL.verify is performed
  396. # (otherwise it'll immediately return false)
  397. request.get_vars['_signature'] = original_sig
  398. # return whether or not the signature in the request matched the one we just generated
  399. # (I.E. was the message the same as the one we originally signed)
  400. return compare(original_sig, sig)
  401. URL.verify = verifyURL
  402. ON = True
  403. class XmlComponent(object):
  404. """
  405. Abstract root for all Html components
  406. """
  407. # TODO: move some DIV methods to here
  408. def xml(self):
  409. raise NotImplementedError
  410. def __mul__(self, n):
  411. return CAT(*[self for i in range(n)])
  412. def __add__(self, other):
  413. if isinstance(self, CAT):
  414. components = self.components
  415. else:
  416. components = [self]
  417. if isinstance(other, CAT):
  418. components += other.components
  419. else:
  420. components += [other]
  421. return CAT(*components)
  422. def add_class(self, name):
  423. """ add a class to _class attribute """
  424. c = self['_class']
  425. classes = (set(c.split()) if c else set()) | set(name.split())
  426. self['_class'] = ' '.join(classes) if classes else None
  427. return self
  428. def remove_class(self, name):
  429. """ remove a class from _class attribute """
  430. c = self['_class']
  431. classes = (set(c.split()) if c else set()) - set(name.split())
  432. self['_class'] = ' '.join(classes) if classes else None
  433. return self
  434. class XML(XmlComponent):
  435. """
  436. use it to wrap a string that contains XML/HTML so that it will not be
  437. escaped by the template
  438. example:
  439. >>> XML('<h1>Hello</h1>').xml()
  440. '<h1>Hello</h1>'
  441. """
  442. def __init__(
  443. self,
  444. text,
  445. sanitize=False,
  446. permitted_tags=[
  447. 'a',
  448. 'b',
  449. 'blockquote',
  450. 'br/',
  451. 'i',
  452. 'li',
  453. 'ol',
  454. 'ul',
  455. 'p',
  456. 'cite',
  457. 'code',
  458. 'pre',
  459. 'img/',
  460. 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
  461. 'table', 'tr', 'td', 'div',
  462. 'strong','span',
  463. ],
  464. allowed_attributes={
  465. 'a': ['href', 'title', 'target'],
  466. 'img': ['src', 'alt'],
  467. 'blockquote': ['type'],
  468. 'td': ['colspan'],
  469. },
  470. ):
  471. """
  472. :param text: the XML text
  473. :param sanitize: sanitize text using the permitted tags and allowed
  474. attributes (default False)
  475. :param permitted_tags: list of permitted tags (default: simple list of
  476. tags)
  477. :param allowed_attributes: dictionary of allowed attributed (default
  478. for A, IMG and BlockQuote).
  479. The key is the tag; the value is a list of allowed attributes.
  480. """
  481. if sanitize:
  482. text = sanitizer.sanitize(text, permitted_tags,
  483. allowed_attributes)
  484. if isinstance(text, unicode):
  485. text = text.encode('utf8', 'xmlcharrefreplace')
  486. elif not isinstance(text, str):
  487. text = str(text)
  488. self.text = text
  489. def xml(self):
  490. return self.text
  491. def __str__(self):
  492. return self.text
  493. def __add__(self, other):
  494. return '%s%s' % (self, other)
  495. def __radd__(self, other):
  496. return '%s%s' % (other, self)
  497. def __cmp__(self, other):
  498. return cmp(str(self), str(other))
  499. def __hash__(self):
  500. return hash(str(self))
  501. # why was this here? Break unpickling in sessions
  502. # def __getattr__(self, name):
  503. # return getattr(str(self), name)
  504. def __getitem__(self, i):
  505. return str(self)[i]
  506. def __getslice__(self, i, j):
  507. return str(self)[i:j]
  508. def __iter__(self):
  509. for c in str(self):
  510. yield c
  511. def __len__(self):
  512. return len(str(self))
  513. def flatten(self, render=None):
  514. """
  515. return the text stored by the XML object rendered by the render function
  516. """
  517. if render:
  518. return render(self.text, None, {})
  519. return self.text
  520. def elements(self, *args, **kargs):
  521. """
  522. to be considered experimental since the behavior of this method is questionable
  523. another options could be TAG(self.text).elements(*args,**kargs)
  524. """
  525. return []
  526. ### important to allow safe session.flash=T(....)
  527. def XML_unpickle(data):
  528. return marshal.loads(data)
  529. def XML_pickle(data):
  530. return XML_unpickle, (marshal.dumps(str(data)),)
  531. copy_reg.pickle(XML, XML_pickle, XML_unpickle)
  532. class DIV(XmlComponent):
  533. """
  534. HTML helper, for easy generating and manipulating a DOM structure.
  535. Little or no validation is done.
  536. Behaves like a dictionary regarding updating of attributes.
  537. Behaves like a list regarding inserting/appending components.
  538. example::
  539. >>> DIV('hello', 'world', _style='color:red;').xml()
  540. '<div style=\"color:red;\">helloworld</div>'
  541. all other HTML helpers are derived from DIV.
  542. _something=\"value\" attributes are transparently translated into
  543. something=\"value\" HTML attributes
  544. """
  545. # name of the tag, subclasses should update this
  546. # tags ending with a '/' denote classes that cannot
  547. # contain components
  548. tag = 'div'
  549. def __init__(self, *components, **attributes):
  550. """
  551. :param *components: any components that should be nested in this element
  552. :param **attributes: any attributes you want to give to this element
  553. :raises SyntaxError: when a stand alone tag receives components
  554. """
  555. if self.tag[-1:] == '/' and components:
  556. raise SyntaxError('<%s> tags cannot have components'
  557. % self.tag)
  558. if len(components) == 1 and isinstance(components[0], (list, tuple)):
  559. self.components = list(components[0])
  560. else:
  561. self.components = list(components)
  562. self.attributes = attributes
  563. self._fixup()
  564. # converts special attributes in components attributes
  565. self.parent = None
  566. for c in self.components:
  567. self._setnode(c)
  568. self._postprocessing()
  569. def update(self, **kargs):
  570. """
  571. dictionary like updating of the tag attributes
  572. """
  573. for (key, value) in kargs.iteritems():
  574. self[key] = value
  575. return self
  576. def append(self, value):
  577. """
  578. list style appending of components
  579. >>> a=DIV()
  580. >>> a.append(SPAN('x'))
  581. >>> print a
  582. <div><span>x</span></div>
  583. """
  584. self._setnode(value)
  585. ret = self.components.append(value)
  586. self._fixup()
  587. return ret
  588. def insert(self, i, value):
  589. """
  590. list style inserting of components
  591. >>> a=DIV()
  592. >>> a.insert(0,SPAN('x'))
  593. >>> print a
  594. <div><span>x</span></div>
  595. """
  596. self._setnode(value)
  597. ret = self.components.insert(i, value)
  598. self._fixup()
  599. return ret
  600. def __getitem__(self, i):
  601. """
  602. gets attribute with name 'i' or component #i.
  603. If attribute 'i' is not found returns None
  604. :param i: index
  605. if i is a string: the name of the attribute
  606. otherwise references to number of the component
  607. """
  608. if isinstance(i, str):
  609. try:
  610. return self.attributes[i]
  611. except KeyError:
  612. return None
  613. else:
  614. return self.components[i]
  615. def __setitem__(self, i, value):
  616. """
  617. sets attribute with name 'i' or component #i.
  618. :param i: index
  619. if i is a string: the name of the attribute
  620. otherwise references to number of the component
  621. :param value: the new value
  622. """
  623. self._setnode(value)
  624. if isinstance(i, (str, unicode)):
  625. self.attributes[i] = value
  626. else:
  627. self.components[i] = value
  628. def __delitem__(self, i):
  629. """
  630. deletes attribute with name 'i' or component #i.
  631. :param i: index
  632. if i is a string: the name of the attribute
  633. otherwise references to number of the component
  634. """
  635. if isinstance(i, str):
  636. del self.attributes[i]
  637. else:
  638. del self.components[i]
  639. def __len__(self):
  640. """
  641. returns the number of included components
  642. """
  643. return len(self.components)
  644. def __nonzero__(self):
  645. """
  646. always return True
  647. """
  648. return True
  649. def _fixup(self):
  650. """
  651. Handling of provided components.
  652. Nothing to fixup yet. May be overridden by subclasses,
  653. eg for wrapping some components in another component or blocking them.
  654. """
  655. return
  656. def _wrap_components(self, allowed_parents,
  657. wrap_parent=None,
  658. wrap_lambda=None):
  659. """
  660. helper for _fixup. Checks if a component is in allowed_parents,
  661. otherwise wraps it in wrap_parent
  662. :param allowed_parents: (tuple) classes that the component should be an
  663. instance of
  664. :param wrap_parent: the class to wrap the component in, if needed
  665. :param wrap_lambda: lambda to use for wrapping, if needed
  666. """
  667. components = []
  668. for c in self.components:
  669. if isinstance(c, allowed_parents):
  670. pass
  671. elif wrap_lambda:
  672. c = wrap_lambda(c)
  673. else:
  674. c = wrap_parent(c)
  675. if isinstance(c, DIV):
  676. c.parent = self
  677. components.append(c)
  678. self.components = components
  679. def _postprocessing(self):
  680. """
  681. Handling of attributes (normally the ones not prefixed with '_').
  682. Nothing to postprocess yet. May be overridden by subclasses
  683. """
  684. return
  685. def _traverse(self, status, hideerror=False):
  686. # TODO: docstring
  687. newstatus = status
  688. for c in self.components:
  689. if hasattr(c, '_traverse') and callable(c._traverse):
  690. c.vars = self.vars
  691. c.request_vars = self.request_vars
  692. c.errors = self.errors
  693. c.latest = self.latest
  694. c.session = self.session
  695. c.formname = self.formname
  696. c['hideerror'] = hideerror or \
  697. self.attributes.get('hideerror', False)
  698. newstatus = c._traverse(status, hideerror) and newstatus
  699. # for input, textarea, select, option
  700. # deal with 'value' and 'validation'
  701. name = self['_name']
  702. if newstatus:
  703. newstatus = self._validate()
  704. self._postprocessing()
  705. elif 'old_value' in self.attributes:
  706. self['value'] = self['old_value']
  707. self._postprocessing()
  708. elif name and name in self.vars:
  709. self['value'] = self.vars[name]
  710. self._postprocessing()
  711. if name:
  712. self.latest[name] = self['value']
  713. return newstatus
  714. def _validate(self):
  715. """
  716. nothing to validate yet. May be overridden by subclasses
  717. """
  718. return True
  719. def _setnode(self, value):
  720. if isinstance(value, DIV):
  721. value.parent = self
  722. def _xml(self):
  723. """
  724. helper for xml generation. Returns separately:
  725. - the component attributes
  726. - the generated xml of the inner components
  727. Component attributes start with an underscore ('_') and
  728. do not have a False or None value. The underscore is removed.
  729. A value of True is replaced with the attribute name.
  730. :returns: tuple: (attributes, components)
  731. """
  732. # get the attributes for this component
  733. # (they start with '_', others may have special meanings)
  734. attr = []
  735. for key, value in self.attributes.iteritems():
  736. if key[:1] != '_':
  737. continue
  738. name = key[1:]
  739. if value is True:
  740. value = name
  741. elif value is False or value is None:
  742. continue
  743. attr.append((name, value))
  744. data = self.attributes.get('data',{})
  745. for key, value in data.iteritems():
  746. name = 'data-' + key
  747. value = data[key]
  748. attr.append((name,value))
  749. attr.sort()
  750. fa = ''
  751. for name,value in attr:
  752. fa += ' %s="%s"' % (name, xmlescape(value, True))
  753. # get the xml for the inner components
  754. co = join([xmlescape(component) for component in
  755. self.components])
  756. return (fa, co)
  757. def xml(self):
  758. """
  759. generates the xml for this component.
  760. """
  761. (fa, co) = self._xml()
  762. if not self.tag:
  763. return co
  764. if self.tag[-1:] == '/':
  765. # <tag [attributes] />
  766. return '<%s%s />' % (self.tag[:-1], fa)
  767. # else: <tag [attributes]> inner components xml </tag>
  768. return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag)
  769. def __str__(self):
  770. """
  771. str(COMPONENT) returns equals COMPONENT.xml()
  772. """
  773. return self.xml()
  774. def flatten(self, render=None):
  775. """
  776. return the text stored by the DIV object rendered by the render function
  777. the render function must take text, tagname, and attributes
  778. render=None is equivalent to render=lambda text, tag, attr: text
  779. >>> markdown = lambda text,tag=None,attributes={}: \
  780. {None: re.sub('\s+',' ',text), \
  781. 'h1':'#'+text+'\\n\\n', \
  782. 'p':text+'\\n'}.get(tag,text)
  783. >>> a=TAG('<h1>Header</h1><p>this is a test</p>')
  784. >>> a.flatten(markdown)
  785. '#Header\\n\\nthis is a test\\n'
  786. """
  787. text = ''
  788. for c in self.components:
  789. if isinstance(c, XmlComponent):
  790. s = c.flatten(render)
  791. elif render:
  792. s = render(str(c))
  793. else:
  794. s = str(c)
  795. text += s
  796. if render:
  797. text = render(text, self.tag, self.attributes)
  798. return text
  799. regex_tag = re.compile('^[\w\-\:]+')
  800. regex_id = re.compile('#([\w\-]+)')
  801. regex_class = re.compile('\.([\w\-]+)')
  802. regex_attr = re.compile('\[([\w\-\:]+)=(.*?)\]')
  803. def elements(self, *args, **kargs):
  804. """
  805. find all component that match the supplied attribute dictionary,
  806. or None if nothing could be found
  807. All components of the components are searched.
  808. >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
  809. >>> for c in a.elements('span',first_only=True): c[0]='z'
  810. >>> print a
  811. <div><div><span>z</span>3<div><span>y</span></div></div></div>
  812. >>> for c in a.elements('span'): c[0]='z'
  813. >>> print a
  814. <div><div><span>z</span>3<div><span>z</span></div></div></div>
  815. It also supports a syntax compatible with jQuery
  816. >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
  817. >>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
  818. hello
  819. world
  820. >>> for e in a.elements('#1-1'): print e.flatten()
  821. hello
  822. >>> a.elements('a[u:v=$]')[0].xml()
  823. '<a id="1-1" u:v="$">hello</a>'
  824. >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
  825. >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
  826. >>> a.xml()
  827. '<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>'
  828. Elements that are matched can also be replaced or removed by specifying
  829. a "replace" argument (note, a list of the original matching elements
  830. is still returned as usual).
  831. >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
  832. >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
  833. >>> print a
  834. <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
  835. "replace" can be a callable, which will be passed the original element and
  836. should return a new element to replace it.
  837. >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
  838. >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
  839. >>> print a
  840. <div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
  841. If replace=None, matching elements will be removed completely.
  842. >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
  843. >>> b = a.elements('span', find='y', replace=None)
  844. >>> print a
  845. <div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
  846. If a "find_text" argument is specified, elements will be searched for text
  847. components that match find_text, and any matching text components will be
  848. replaced (find_text is ignored if "replace" is not also specified).
  849. Like the "find" argument, "find_text" can be a string or a compiled regex.
  850. >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
  851. >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
  852. >>> print a
  853. <div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
  854. If other attributes are specified along with find_text, then only components
  855. that match the specified attributes will be searched for find_text.
  856. >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
  857. >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
  858. >>> print a
  859. <div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
  860. """
  861. if len(args) == 1:
  862. args = [a.strip() for a in args[0].split(',')]
  863. if len(args) > 1:
  864. subset = [self.elements(a, **kargs) for a in args]
  865. return reduce(lambda a, b: a + b, subset, [])
  866. elif len(args) == 1:
  867. items = args[0].split()
  868. if len(items) > 1:
  869. subset = [a.elements(' '.join(
  870. items[1:]), **kargs) for a in self.elements(items[0])]
  871. return reduce(lambda a, b: a + b, subset, [])
  872. else:
  873. item = items[0]
  874. if '#' in item or '.' in item or '[' in item:
  875. match_tag = self.regex_tag.search(item)
  876. match_id = self.regex_id.search(item)
  877. match_class = self.regex_class.search(item)
  878. match_attr = self.regex_attr.finditer(item)
  879. args = []
  880. if match_tag:
  881. args = [match_tag.group()]
  882. if match_id:
  883. kargs['_id'] = match_id.group(1)
  884. if match_class:
  885. kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' %
  886. match_class.group(1).replace('-', '\\-').replace(':', '\\:'))
  887. for item in match_attr:
  888. kargs['_' + item.group(1)] = item.group(2)
  889. return self.elements(*args, **kargs)
  890. # make a copy of the components
  891. matches = []
  892. # check if the component has an attribute with the same
  893. # value as provided
  894. check = True
  895. tag = getattr(self, 'tag').replace('/', '')
  896. if args and tag not in args:
  897. check = False
  898. for (key, value) in kargs.iteritems():
  899. if key not in ['first_only', 'replace', 'find_text']:
  900. if isinstance(value, (str, int)):
  901. if self[key] != str(value):
  902. check = False
  903. elif key in self.attributes:
  904. if not value.search(str(self[key])):
  905. check = False
  906. else:
  907. check = False
  908. if 'find' in kargs:
  909. find = kargs['find']
  910. is_regex = not isinstance(find, (str, int))
  911. for c in self.components:
  912. if (isinstance(c, str) and ((is_regex and find.search(c)) or
  913. (str(find) in c))):
  914. check = True
  915. # if found, return the component
  916. if check:
  917. matches.append(self)
  918. first_only = kargs.get('first_only', False)
  919. replace = kargs.get('replace', False)
  920. find_text = replace is not False and kargs.get('find_text', False)
  921. is_regex = not isinstance(find_text, (str, int, bool))
  922. find_components = not (check and first_only)
  923. def replace_component(i):
  924. if replace is None:
  925. del self[i]
  926. elif callable(replace):
  927. self[i] = replace(self[i])
  928. else:
  929. self[i] = replace
  930. # loop the components
  931. if find_text or find_components:
  932. for i, c in enumerate(self.components):
  933. if check and find_text and isinstance(c, str) and \
  934. ((is_regex and find_text.search(c)) or (str(find_text) in c)):
  935. replace_component(i)
  936. if find_components and isinstance(c, XmlComponent):
  937. child_matches = c.elements(*args, **kargs)
  938. if len(child_matches):
  939. if not find_text and replace is not False and child_matches[0] is c:
  940. replace_component(i)
  941. if first_only:
  942. return child_matches
  943. matches.extend(child_matches)
  944. return matches
  945. def element(self, *args, **kargs):
  946. """
  947. find the first component that matches the supplied attribute dictionary,
  948. or None if nothing could be found
  949. Also the components of the components are searched.
  950. """
  951. kargs['first_only'] = True
  952. elements = self.elements(*args, **kargs)
  953. if not elements:
  954. # we found nothing
  955. return None
  956. return elements[0]
  957. def siblings(self, *args, **kargs):
  958. """
  959. find all sibling components that match the supplied argument list
  960. and attribute dictionary, or None if nothing could be found
  961. """
  962. sibs = [s for s in self.parent.components if not s == self]
  963. matches = []
  964. first_only = False
  965. if 'first_only' in kargs:
  966. first_only = kargs.pop('first_only')
  967. for c in sibs:
  968. try:
  969. check = True
  970. tag = getattr(c, 'tag').replace("/", "")
  971. if args and tag not in args:
  972. check = False
  973. for (key, value) in kargs.iteritems():
  974. if c[key] != value:
  975. check = False
  976. if check:
  977. matches.append(c)
  978. if first_only:
  979. break
  980. except:
  981. pass
  982. return matches
  983. def sibling(self, *args, **kargs):
  984. """
  985. find the first sibling component that match the supplied argument list
  986. and attribute dictionary, or None if nothing could be found
  987. """
  988. kargs['first_only'] = True
  989. sibs = self.siblings(*args, **kargs)
  990. if not sibs:
  991. return None
  992. return sibs[0]
  993. class CAT(DIV):
  994. tag = ''
  995. def TAG_unpickler(data):
  996. return cPickle.loads(data)
  997. def TAG_pickler(data):
  998. d = DIV()
  999. d.__dict__ = data.__dict__
  1000. marshal_dump = cPickle.dumps(d)
  1001. return (TAG_unpickler, (marshal_dump,))
  1002. class __tag_div__(DIV):
  1003. def __init__(self,name,*a,**b):
  1004. DIV.__init__(self,*a,**b)
  1005. self.tag = name
  1006. copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
  1007. class __TAG__(XmlComponent):
  1008. """
  1009. TAG factory example::
  1010. >>> print TAG.first(TAG.second('test'), _key = 3)
  1011. <first key=\"3\"><second>test</second></first>
  1012. """
  1013. def __getitem__(self, name):
  1014. return self.__getattr__(name)
  1015. def __getattr__(self, name):
  1016. if name[-1:] == '_':
  1017. name = name[:-1] + '/'
  1018. if isinstance(name, unicode):
  1019. name = name.encode('utf-8')
  1020. return lambda *a,**b: __tag_div__(name,*a,**b)
  1021. def __call__(self, html):
  1022. return web2pyHTMLParser(decoder.decoder(html)).tree
  1023. TAG = __TAG__()
  1024. class HTML(DIV):
  1025. """
  1026. There are four predefined document type definitions.
  1027. They can be specified in the 'doctype' parameter:
  1028. -'strict' enables strict doctype
  1029. -'transitional' enables transitional doctype (default)
  1030. -'frameset' enables frameset doctype
  1031. -'html5' enables HTML 5 doctype
  1032. -any other string will be treated as user's own doctype
  1033. 'lang' parameter specifies the language of the document.
  1034. Defaults to 'en'.
  1035. See also :class:`DIV`
  1036. """
  1037. tag = 'html'
  1038. strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
  1039. transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
  1040. frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
  1041. html5 = '<!DOCTYPE HTML>\n'
  1042. def xml(self):
  1043. lang = self['lang']
  1044. if not lang:
  1045. lang = 'en'
  1046. self.attributes['_lang'] = lang
  1047. doctype = self['doctype']
  1048. if doctype is None:
  1049. doctype = self.transitional
  1050. elif doctype == 'strict':
  1051. doctype = self.strict
  1052. elif doctype == 'transitional':
  1053. doctype = self.transitional
  1054. elif doctype == 'frameset':
  1055. doctype = self.frameset
  1056. elif doctype == 'html5':
  1057. doctype = self.html5
  1058. elif doctype == '':
  1059. doctype = ''
  1060. else:
  1061. doctype = '%s\n' % doctype
  1062. (fa, co) = self._xml()
  1063. return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
  1064. class XHTML(DIV):
  1065. """
  1066. This is XHTML version of the HTML helper.
  1067. There are three predefined document type definitions.
  1068. They can be specified in the 'doctype' parameter:
  1069. -'strict' enables strict doctype
  1070. -'transitional' enables transitional doctype (default)
  1071. -'frameset' enables frameset doctype
  1072. -any other string will be treated as user's own doctype
  1073. 'lang' parameter specifies the language of the document and the xml document.
  1074. Defaults to 'en'.
  1075. 'xmlns' parameter specifies the xml namespace.
  1076. Defaults to 'http://www.w3.org/1999/xhtml'.
  1077. See also :class:`DIV`
  1078. """
  1079. tag = 'html'
  1080. strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
  1081. transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
  1082. frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n'
  1083. xmlns = 'http://www.w3.org/1999/xhtml'
  1084. def xml(self):
  1085. xmlns = self['xmlns']
  1086. if xmlns:
  1087. self.attributes['_xmlns'] = xmlns
  1088. else:
  1089. self.attributes['_xmlns'] = self.xmlns
  1090. lang = self['lang']
  1091. if not lang:
  1092. lang = 'en'
  1093. self.attributes['_lang'] = lang
  1094. self.attributes['_xml:lang'] = lang
  1095. doctype = self['doctype']
  1096. if doctype:
  1097. if doctype == 'strict':
  1098. doctype = self.strict
  1099. elif doctype == 'transitional':
  1100. doctype = self.transitional
  1101. elif doctype == 'frameset':
  1102. doctype = self.frameset
  1103. else:
  1104. doctype = '%s\n' % doctype
  1105. else:
  1106. doctype = self.transitional
  1107. (fa, co) = self._xml()
  1108. return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
  1109. class HEAD(DIV):
  1110. tag = 'head'
  1111. class TITLE(DIV):
  1112. tag = 'title'
  1113. class META(DIV):
  1114. tag = 'meta/'
  1115. class LINK(DIV):
  1116. tag = 'link/'
  1117. class SCRIPT(DIV):
  1118. tag = 'script'
  1119. def xml(self):
  1120. (fa, co) = self._xml()
  1121. # no escaping of subcomponents
  1122. co = '\n'.join([str(component) for component in
  1123. self.components])
  1124. if co:
  1125. # <script [attributes]><!--//--><![CDATA[//><!--
  1126. # script body
  1127. # //--><!]]></script>
  1128. # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
  1129. return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
  1130. else:
  1131. return DIV.xml(self)
  1132. class STYLE(DIV):
  1133. tag = 'style'
  1134. def xml(self):
  1135. (fa, co) = self._xml()
  1136. # no escaping of subcomponents
  1137. co = '\n'.join([str(component) for component in
  1138. self.components])
  1139. if co:
  1140. # <style [attributes]><!--/*--><![CDATA[/*><!--*/
  1141. # style body
  1142. # /*]]>*/--></style>
  1143. return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
  1144. else:
  1145. return DIV.xml(self)
  1146. class IMG(DIV):
  1147. tag = 'img/'
  1148. class SPAN(DIV):
  1149. tag = 'span'
  1150. class BODY(DIV):
  1151. tag = 'body'
  1152. class H1(DIV):
  1153. tag = 'h1'
  1154. class H2(DIV):
  1155. tag = 'h2'
  1156. class H3(DIV):
  1157. tag = 'h3'
  1158. class H4(DIV):
  1159. tag = 'h4'
  1160. class H5(DIV):
  1161. tag = 'h5'
  1162. class H6(DIV):
  1163. tag = 'h6'
  1164. class P(DIV):
  1165. """
  1166. Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided.
  1167. see also :class:`DIV`
  1168. """
  1169. tag = 'p'
  1170. def xml(self):
  1171. text = DIV.xml(self)
  1172. if self['cr2br']:
  1173. text = text.replace('\n', '<br />')
  1174. return text
  1175. class STRONG(DIV):
  1176. tag = 'strong'
  1177. class B(DIV):
  1178. tag = 'b'
  1179. class BR(DIV):
  1180. tag = 'br/'
  1181. class HR(DIV):
  1182. tag = 'hr/'
  1183. class A(DIV):
  1184. tag = 'a'
  1185. def xml(self):
  1186. if not self.components and self['_href']:
  1187. self.append(self['_href'])
  1188. if not self['_disable_with']:
  1189. self['_data-w2p_disable_with'] = 'default'
  1190. if self['callback'] and not self['_id']:
  1191. self['_id'] = web2py_uuid()
  1192. if self['delete']:
  1193. self['_data-w2p_remove'] = self['delete']
  1194. if self['target']:
  1195. if self['target'] == '<self>':
  1196. self['target'] = self['_id']
  1197. self['_data-w2p_target'] = self['target']
  1198. if self['component']:
  1199. self['_data-w2p_method'] = 'GET'
  1200. self['_href'] = self['component']
  1201. elif self['callback']:
  1202. self['_data-w2p_method'] = 'POST'
  1203. self['_href'] = self['callback']
  1204. if self['delete'] and not self['noconfirm']:
  1205. if not self['confirm']:
  1206. self['_data-w2p_confirm'] = 'default'
  1207. else:
  1208. self['_data-w2p_confirm'] = self['confirm']
  1209. elif self['cid']:
  1210. self['_data-w2p_method'] = 'GET'
  1211. self['_data-w2p_target'] = self['cid']
  1212. if self['pre_call']:
  1213. self['_data-w2p_pre_call'] = self['pre_call']
  1214. return DIV.xml(self)
  1215. class BUTTON(DIV):
  1216. tag = 'button'
  1217. class EM(DIV):
  1218. tag = 'em'
  1219. class EMBED(DIV):
  1220. tag = 'embed/'
  1221. class TT(DIV):
  1222. tag = 'tt'
  1223. class PRE(DIV):
  1224. tag = 'pre'
  1225. class CENTER(DIV):
  1226. tag = 'center'
  1227. class CODE(DIV):
  1228. """
  1229. displays code in HTML with syntax highlighting.
  1230. :param attributes: optional attributes:
  1231. - language: indicates the language, otherwise PYTHON is assumed
  1232. - link: can provide a link
  1233. - styles: for styles
  1234. Example::
  1235. {{=CODE(\"print 'hello world'\", language='python', link=None,
  1236. counter=1, styles={}, highlight_line=None)}}
  1237. supported languages are \"python\", \"html_plain\", \"c\", \"cpp\",
  1238. \"web2py\", \"html\".
  1239. The \"html\" language interprets {{ and }} tags as \"web2py\" code,
  1240. \"html_plain\" doesn't.
  1241. if a link='/examples/global/vars/' is provided web2py keywords are linked to
  1242. the online docs.
  1243. the counter is used for line numbering, counter can be None or a prompt
  1244. string.
  1245. """
  1246. def xml(self):
  1247. language = self['language'] or 'PYTHON'
  1248. link = self['link']
  1249. counter = self.attributes.get('counter', 1)
  1250. highlight_line = self.attributes.get('highlight_line', None)
  1251. context_lines = self.attributes.get('context_lines', None)
  1252. styles = self['styles'] or {}
  1253. return highlight(
  1254. join(self.components),
  1255. language=language,
  1256. link=link,
  1257. counter=counter,
  1258. styles=styles,
  1259. attributes=self.attributes,
  1260. highlight_line=highlight_line,
  1261. context_lines=context_lines,
  1262. )
  1263. class LABEL(DIV):
  1264. tag = 'label'
  1265. class LI(DIV):
  1266. tag = 'li'
  1267. class UL(DIV):
  1268. """
  1269. UL Component.
  1270. If subcomponents are not LI-components they will be wrapped in a LI
  1271. see also :class:`DIV`
  1272. """
  1273. tag = 'ul'
  1274. def _fixup(self):
  1275. self._wrap_components(LI, LI)
  1276. class OL(UL):
  1277. tag = 'ol'
  1278. class TD(DIV):
  1279. tag = 'td'
  1280. class TH(DIV):
  1281. tag = 'th'
  1282. class TR(DIV):
  1283. """
  1284. TR Component.
  1285. If subcomponents are not TD/TH-components they will be wrapped in a TD
  1286. see also :class:`DIV`
  1287. """
  1288. tag = 'tr'
  1289. def _fixup(self):
  1290. self._wrap_components((TD, TH), TD)
  1291. class __TRHEAD__(DIV):
  1292. """
  1293. __TRHEAD__ Component, internal only
  1294. If subcomponents are not TD/TH-components they will be wrapped in a TH
  1295. see also :class:`DIV`
  1296. """
  1297. tag = 'tr'
  1298. def _fixup(self):
  1299. self._wrap_components((TD, TH), TH)
  1300. class THEAD(DIV):
  1301. tag = 'thead'
  1302. def _fixup(self):
  1303. self._wrap_components((__TRHEAD__, TR), __TRHEAD__)
  1304. class TBODY(DIV):
  1305. tag = 'tbody'
  1306. def _fixup(self):
  1307. self._wrap_components(TR, TR)
  1308. class TFOOT(DIV):
  1309. tag = 'tfoot'
  1310. def _fixup(self):
  1311. self._wrap_components(TR, TR)
  1312. class COL(DIV):
  1313. tag = 'col'
  1314. class COLGROUP(DIV):
  1315. tag = 'colgroup'
  1316. class TABLE(DIV):
  1317. """
  1318. TABLE Component.
  1319. If subcomponents are not TR/TBODY/THEAD/TFOOT-components
  1320. they will be wrapped in a TR
  1321. see also :class:`DIV`
  1322. """
  1323. tag = 'table'
  1324. def _fixup(self):
  1325. self._wrap_components((TR, TBODY, THEAD, TFOOT, COL, COLGROUP), TR)
  1326. class I(DIV):
  1327. tag = 'i'
  1328. class IFRAME(DIV):
  1329. tag = 'iframe'
  1330. class INPUT(DIV):
  1331. """
  1332. INPUT Component
  1333. examples::
  1334. >>> INPUT(_type='text', _name='name', value='Max').xml()
  1335. '<input name=\"name\" type=\"text\" value=\"Max\" />'
  1336. >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml()
  1337. '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />'
  1338. >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml()
  1339. '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />'
  1340. >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml()
  1341. '<input name=\"radio\" type=\"radio\" value=\"no\" />'
  1342. the input helper takes two special attributes value= and requires=.
  1343. :param value: used to pass the initial value for the input field.
  1344. value differs from _value because it works for checkboxes, radio,
  1345. textarea and select/option too.
  1346. - for a checkbox value should be '' or 'on'.
  1347. - for a radio or select/option value should be the _value
  1348. of the checked/selected item.
  1349. :param requires: should be None, or a validator or a list of validators
  1350. for the value of the field.
  1351. """
  1352. tag = 'input/'
  1353. def _validate(self):
  1354. # # this only changes value, not _value
  1355. name = self['_name']
  1356. if name is None or name == '':
  1357. return True
  1358. name = str(name)
  1359. request_vars_get = self.request_vars.get
  1360. if self['_type'] != 'checkbox':
  1361. self['old_value'] = self['value'] or self['_value'] or ''
  1362. value = request_vars_get(name, '')
  1363. self['value'] = value if not hasattr(value,'file') else None
  1364. else:
  1365. self['old_value'] = self['value'] or False
  1366. value = request_vars_get(name)
  1367. if isinstance(value, (tuple, list)):
  1368. self['value'] = self['_value'] in value
  1369. else:
  1370. self['value'] = self['_value'] == value
  1371. requires = self['requires']
  1372. if requires:
  1373. if not isinstance(requires, (list, tuple)):
  1374. requires = [requires]
  1375. for validator in requires:
  1376. (value, errors) = validator(value)
  1377. if not errors is None:
  1378. self.vars[name] = value
  1379. self.errors[name] = errors
  1380. break
  1381. if not name in self.errors:
  1382. self.vars[name] = value
  1383. return True
  1384. return False
  1385. def _postprocessing(self):
  1386. t = self['_type']
  1387. if not t:
  1388. t = self['_type'] = 'text'
  1389. t = t.lower()
  1390. value = self['value']
  1391. if self['_value'] is None or isinstance(self['_value'],cgi.FieldStorage):
  1392. _value = None
  1393. else:
  1394. _value = str(self['_value'])
  1395. if '_checked' in self.attributes and not 'value' in self.attributes:
  1396. pass
  1397. elif t == 'checkbox':
  1398. if not _value:
  1399. _value = self['_value'] = 'on'
  1400. if not value:
  1401. value = []
  1402. elif value is True:
  1403. value = [_value]
  1404. elif not isinstance(value, (list, tuple)):
  1405. value = str(value).split('|')
  1406. self['_checked'] = _value in value and 'checked' or None
  1407. elif t == 'radio':
  1408. if str(value) == str(_value):
  1409. self['_checked'] = 'checked'
  1410. else:
  1411. self['_checked'] = None
  1412. elif not t == 'submit':
  1413. if value is None:
  1414. self['value'] = _value
  1415. elif not isinstance(value, list):
  1416. self['_value'] = value
  1417. def xml(self):
  1418. name = self.attributes.get('_name', None)
  1419. if name and hasattr(self, 'errors') \
  1420. and self.errors.get(name, None) \
  1421. and self['hideerror'] != True:
  1422. self['_class'] = (self['_class'] and self['_class']
  1423. + ' ' or '') + 'invalidinput'
  1424. return DIV.xml(self) + DIV(
  1425. DIV(
  1426. self.errors[name], _class='error',
  1427. errors=None, _id='%s__error' % name),
  1428. _class='error_wrapper').xml()
  1429. else:
  1430. if self['_class'] and self['_class'].endswith('invalidinput'):
  1431. self['_class'] = self['_class'][:-12]
  1432. if self['_class'] == '':
  1433. self['_class'] = None
  1434. return DIV.xml(self)
  1435. class TEXTAREA(INPUT):
  1436. """
  1437. example::
  1438. TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
  1439. 'blah blah blah ...' will be the content of the textarea field.
  1440. """
  1441. tag = 'textarea'
  1442. def _postprocessing(self):
  1443. if not '_rows' in self.attributes:
  1444. self['_rows'] = 10
  1445. if not '_cols' in self.attributes:
  1446. self['_cols'] = 40
  1447. if not self['value'] is None:
  1448. self.components = [self['value']]
  1449. elif self.components:
  1450. self['value'] = self.components[0]
  1451. class OPTION(DIV):
  1452. tag = 'option'
  1453. def _fixup(self):
  1454. if not '_value' in self.attributes:
  1455. self.attributes['_value'] = str(self.components[0])
  1456. class OBJECT(DIV):
  1457. tag = 'object'
  1458. class OPTGROUP(DIV):
  1459. tag = 'optgroup'
  1460. def _fixup(self):
  1461. components = []
  1462. for c in self.components:
  1463. if isinstance(c, OPTION):
  1464. components.append(c)
  1465. else:
  1466. components.append(OPTION(c, _value=str(c)))
  1467. self.components = components
  1468. class SELECT(INPUT):
  1469. """
  1470. example::
  1471. >>> from validators import IS_IN_SET
  1472. >>> SELECT('yes', 'no', _name='selector', value='yes',
  1473. ... requires=IS_IN_SET(['yes', 'no'])).xml()
  1474. '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>'
  1475. """
  1476. tag = 'select'
  1477. def _fixup(self):
  1478. components = []
  1479. for c in self.components:
  1480. if isinstance(c, (OPTION, OPTGROUP)):
  1481. components.append(c)
  1482. else:
  1483. components.append(OPTION(c, _value=str(c)))
  1484. self.components = components
  1485. def _postprocessing(self):
  1486. component_list = []
  1487. for c in self.components:
  1488. if isinstance(c, OPTGROUP):
  1489. component_list.append(c.components)
  1490. else:
  1491. component_list.append([c])
  1492. options = itertools.chain(*component_list)
  1493. value = self['value']
  1494. if not value is None:
  1495. if not self['_multiple']:
  1496. for c in options: # my patch
  1497. if ((value is not None) and
  1498. (str(c['_value']) == str(value))):
  1499. c['_selected'] = 'selected'
  1500. else:
  1501. c['_selected'] = None
  1502. else:
  1503. if isinstance(value, (list, tuple)):
  1504. values = [str(item) for item in value]
  1505. else:
  1506. values = [str(value)]
  1507. for c in options: # my patch
  1508. if ((value is not None) and
  1509. (str(c['_value']) in values)):
  1510. c['_selected'] = 'selected'
  1511. else:
  1512. c['_selected'] = None
  1513. class FIELDSET(DIV):
  1514. tag = 'fieldset'
  1515. class LEGEND(DIV):
  1516. tag = 'legend'
  1517. class FORM(DIV):
  1518. """
  1519. example::
  1520. >>> from validators import IS_NOT_EMPTY
  1521. >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
  1522. >>> form.xml()
  1523. '<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>'
  1524. a FORM is container for INPUT, TEXTAREA, SELECT and other helpers
  1525. form has one important method::
  1526. form.accepts(request.vars, session)
  1527. if form is accepted (and all validators pass) form.vars contains the
  1528. accepted vars, otherwise form.errors contains the errors.
  1529. in case of errors the form is modified to present the errors to the user.
  1530. """
  1531. tag = 'form'
  1532. def __init__(self, *components, **attributes):
  1533. DIV.__init__(self, *components, **attributes)
  1534. self.vars = Storage()
  1535. self.errors = Storage()
  1536. self.latest = Storage()
  1537. self.accepted = None # none for not submitted
  1538. def assert_status(self, status, request_vars):
  1539. return status
  1540. def accepts(
  1541. self,
  1542. request_vars,
  1543. session=None,
  1544. formname='default',
  1545. keepvalues=False,
  1546. onvalidation=None,
  1547. hideerror=False,
  1548. **kwargs
  1549. ):
  1550. """
  1551. kwargs is not used but allows to specify the same interface for FORM and SQLFORM
  1552. """
  1553. if request_vars.__class__.__name__ == 'Request':
  1554. request_vars = request_vars.post_vars
  1555. self.errors.clear()
  1556. self.request_vars = Storage()
  1557. self.request_vars.update(request_vars)
  1558. self.session = session
  1559. self.formname = formname
  1560. self.keepvalues = keepvalues
  1561. # if this tag is a form and we are in accepting mode (status=True)
  1562. # check formname and formkey
  1563. status = True
  1564. changed = False
  1565. request_vars = self.request_vars
  1566. if session is not None:
  1567. formkey = session.get('_formkey[%s]' % formname, None)
  1568. # check if user tampering with form and void CSRF
  1569. if not formkey or formkey != request_vars._formkey:
  1570. status = False
  1571. if formname != request_vars._formname:
  1572. status = False
  1573. if status and session:
  1574. # check if editing a record that has been modified by the server
  1575. if hasattr(self, 'record_hash') and self.record_hash != formkey:
  1576. status = False
  1577. self.record_changed = changed = True
  1578. status = self._traverse(status, hideerror)
  1579. status = self.assert_status(status, request_vars)
  1580. if onvalidation:
  1581. if isinstance(onvalidation, dict):
  1582. onsuccess = onvalidation.get('onsuccess', None)
  1583. onfailure = onvalidation.get('onfailure', None)
  1584. onchange = onvalidation.get('onchange', None)
  1585. if [k for k in onvalidation if not k in (
  1586. 'onsuccess','onfailure','onchange')]:
  1587. raise RuntimeError('Invalid key in onvalidate dict')
  1588. if onsuccess and status:
  1589. call_as_list(onsuccess,self)
  1590. if onfailure and request_vars and not status:
  1591. call_as_list(onfailure,self)
  1592. status = len(self.errors) == 0
  1593. if changed:
  1594. if onchange and self.record_changed and \
  1595. self.detect_record_change:
  1596. call_as_list(onchange,self)
  1597. elif status:
  1598. call_as_list(onvalidation, self)
  1599. if self.errors:
  1600. status = False
  1601. if not session is None:
  1602. if hasattr(self, 'record_hash'):
  1603. formkey = self.record_hash
  1604. else:
  1605. formkey = web2py_uuid()
  1606. self.formkey = session['_formkey[%s]' % formname] = formkey
  1607. if status and not keepvalues:
  1608. self._traverse(False, hideerror)
  1609. self.accepted = status
  1610. return status
  1611. def _postprocessing(self):
  1612. if not '_action' in self.attributes:
  1613. self['_action'] = '#'
  1614. if not '_method' in self.attributes:
  1615. self['_method'] = 'post'
  1616. if not '_enctype' in self.attributes:
  1617. self['_enctype'] = 'multipart/form-data'
  1618. def hidden_fields(self):
  1619. c = []
  1620. attr = self.attributes.get('hidden', {})
  1621. if 'hidden' in self.attributes:
  1622. c = [INPUT(_type='hidden', _name=key, _value=value)
  1623. for (key, value) in attr.iteritems()]
  1624. if hasattr(self, 'formkey') and self.formkey:
  1625. c.append(INPUT(_type='hidden', _name='_formkey',
  1626. _value=self.formkey))
  1627. if hasattr(self, 'formname') and self.formname:
  1628. c.append(INPUT(_type='hidden', _name='_formname',
  1629. _value=self.formname))
  1630. return DIV(c, _style="display:none;")
  1631. def xml(self):
  1632. newform = FORM(*self.components, **self.attributes)
  1633. hidden_fields = self.hidden_fields()
  1634. if hidden_fields.components:
  1635. newform.append(hidden_fields)
  1636. return DIV.xml(newform)
  1637. def validate(self, **kwargs):
  1638. """
  1639. This function validates the form,
  1640. you can use it instead of directly form.accepts.
  1641. Usage:
  1642. In controller
  1643. def action():
  1644. form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
  1645. form.validate() #you can pass some args here - see below
  1646. return dict(form=form)
  1647. This can receive a bunch of arguments
  1648. onsuccess = 'flash' - will show message_onsuccess in response.flash
  1649. None - will do nothing
  1650. can be a function (lambda form: pass)
  1651. onfailure = 'flash' - will show message_onfailure in response.flash
  1652. None - will do nothing
  1653. can be a function (lambda form: pass)
  1654. onchange = 'flash' - will show message_onchange in response.flash
  1655. None - will do nothing
  1656. can be a function (lambda form: pass)
  1657. message_onsuccess
  1658. message_onfailure
  1659. message_onchange
  1660. next = where to redirect in case of success
  1661. any other kwargs will be passed for form.accepts(...)
  1662. """
  1663. from gluon import current, redirect
  1664. kwargs['request_vars'] = kwargs.get(
  1665. 'request_vars', current.request.post_vars)
  1666. kwargs['session'] = kwargs.get('session', current.session)
  1667. kwargs['dbio'] = kwargs.get('dbio', False)
  1668. # necessary for SQLHTML forms
  1669. onsuccess = kwargs.get('onsuccess', 'flash')
  1670. onfailure = kwargs.get('onfailure', 'flash')
  1671. onchange = kwargs.get('onchange', 'flash')
  1672. message_onsuccess = kwargs.get('message_onsuccess',
  1673. current.T("Success!"))
  1674. message_onfailure = kwargs.get('message_onfailure',
  1675. current.T("Errors in form, please check it out."))
  1676. message_onchange = kwargs.get('message_onchange',
  1677. current.T("Form consecutive submissions not allowed. " +
  1678. "Try re-submitting or refreshing the form page."))
  1679. next = kwargs.get('next', None)
  1680. for key in ('message_onsuccess', 'message_onfailure', 'onsuccess',
  1681. 'onfailure', 'next', 'message_onchange', 'onchange'):
  1682. if key in kwargs:
  1683. del kwargs[key]
  1684. if self.accepts(**kwargs):
  1685. if onsuccess == 'flash':
  1686. if next:
  1687. current.session.flash = message_onsuccess
  1688. else:
  1689. current.response.flash = message_onsuccess
  1690. elif callable(onsuccess):
  1691. onsuccess(self)
  1692. if next:
  1693. if self.vars:
  1694. for key, value in self.vars.iteritems():
  1695. next = next.replace('[%s]' % key,
  1696. urllib.quote(str(value)))
  1697. if not next.startswith('/'):
  1698. next = URL(next)
  1699. redirect(next)
  1700. return True
  1701. elif self.errors:
  1702. if onfailure == 'flash':
  1703. current.response.flash = message_onfailure
  1704. elif callable(onfailure):
  1705. onfailure(self)
  1706. return False
  1707. elif hasattr(self, "record_changed"):
  1708. if self.record_changed and self.detect_record_change:
  1709. if onchange == 'flash':
  1710. current.response.flash = message_onchange
  1711. elif callable(onchange):
  1712. onchange(self)
  1713. return False
  1714. def process(self, **kwargs):
  1715. """
  1716. Perform the .validate() method but returns the form
  1717. Usage in controllers:
  1718. # directly on return
  1719. def action():
  1720. #some code here
  1721. return dict(form=FORM(...).process(...))
  1722. You can use it with FORM, SQLFORM or FORM based plugins
  1723. Examples:
  1724. #response.flash messages
  1725. def action():
  1726. form = SQLFORM(db.table).process(message_onsuccess='Sucess!')
  1727. retutn dict(form=form)
  1728. # callback function
  1729. # callback receives True or False as first arg, and a list of args.
  1730. def my_callback(status, msg):
  1731. response.flash = "Success! "+msg if status else "Errors occured"
  1732. # after argument can be 'flash' to response.flash messages
  1733. # or a function name to use as callback or None to do nothing.
  1734. def action():
  1735. return dict(form=SQLFORM(db.table).process(onsuccess=my_callback)
  1736. """
  1737. kwargs['dbio'] = kwargs.get('dbio', True)
  1738. # necessary for SQLHTML forms
  1739. self.validate(**kwargs)
  1740. return self
  1741. REDIRECT_JS = "window.location='%s';return false"
  1742. def add_button(self, value, url, _class=None):
  1743. submit = self.element('input[type=submit]')
  1744. submit.parent.append(
  1745. INPUT(_type="button", _value=value, _class=_class,
  1746. _onclick=self.REDIRECT_JS % url))
  1747. @staticmethod
  1748. def confirm(text='OK', buttons=None, hidden=None):
  1749. if not buttons:
  1750. buttons = {}
  1751. if not hidden:
  1752. hidden = {}
  1753. inputs = [INPUT(_type='button',
  1754. _value=name,
  1755. _onclick=FORM.REDIRECT_JS % link)
  1756. for name, link in buttons.iteritems()]
  1757. inputs += [INPUT(_type='hidden',
  1758. _name=name,
  1759. _value=value)
  1760. for name, value in hidden.iteritems()]
  1761. form = FORM(INPUT(_type='submit', _value=text), *inputs)
  1762. form.process()
  1763. return form
  1764. def as_dict(self, flat=False, sanitize=True):
  1765. """EXPERIMENTAL
  1766. Sanitize is naive. It should catch any unsafe value
  1767. for client retrieval.
  1768. """
  1769. SERIALIZABLE = (int, float, bool, basestring, long,
  1770. set, list, dict, tuple, Storage, type(None))
  1771. UNSAFE = ("PASSWORD", "CRYPT")
  1772. d = self.__dict__
  1773. def sanitizer(obj):
  1774. if isinstance(obj, dict):
  1775. for k in obj.keys():
  1776. if any([unsafe in str(k).upper() for
  1777. unsafe in UNSAFE]):
  1778. # erease unsafe pair
  1779. obj.pop(k)
  1780. else:
  1781. # not implemented
  1782. pass
  1783. return obj
  1784. def flatten(obj):
  1785. if isinstance(obj, (dict, Storage)):
  1786. newobj = obj.copy()
  1787. else:
  1788. newobj = obj
  1789. if sanitize:
  1790. newobj = sanitizer(newobj)
  1791. if flat:
  1792. if type(obj) in SERIALIZABLE:
  1793. if isinstance(newobj, (dict, Storage)):
  1794. for k in newobj:
  1795. newk = flatten(k)
  1796. newobj[newk] = flatten(newobj[k])
  1797. if k != newk:
  1798. newobj.pop(k)
  1799. return newobj
  1800. elif isinstance(newobj, (list, tuple, set)):
  1801. return [flatten(item) for item in newobj]
  1802. else:
  1803. return newobj
  1804. else: return str(newobj)
  1805. else: return newobj
  1806. return flatten(d)
  1807. def as_json(self, sanitize=True):
  1808. d = self.as_dict(flat=True, sanitize=sanitize)
  1809. from serializers import json
  1810. return json(d)
  1811. def as_yaml(self, sanitize=True):
  1812. d = self.as_dict(flat=True, sanitize=sanitize)
  1813. from serializers import yaml
  1814. return yaml(d)
  1815. def as_xml(self, sanitize=True):
  1816. d = self.as_dict(flat=True, sanitize=sanitize)
  1817. from serializers import xml
  1818. return xml(d)
  1819. class BEAUTIFY(DIV):
  1820. """
  1821. example::
  1822. >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
  1823. '<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>'
  1824. turns any list, dictionary, etc into decent looking html.
  1825. Two special attributes are
  1826. :sorted: a function that takes the dict and returned sorted keys
  1827. :keyfilter: a funciton that takes a key and returns its representation
  1828. or None if the key is to be skipped. By default key[:1]=='_' is skipped.
  1829. """
  1830. tag = 'div'
  1831. @staticmethod
  1832. def no_underscore(key):
  1833. if key[:1] == '_':
  1834. return None
  1835. return key
  1836. def __init__(self, component, **attributes):
  1837. self.components = [component]
  1838. self.attributes = attributes
  1839. sorter = attributes.get('sorted', sorted)
  1840. keyfilter = attributes.get('keyfilter', BEAUTIFY.no_underscore)
  1841. components = []
  1842. attributes = copy.copy(self.attributes)
  1843. level = attributes['level'] = attributes.get('level', 6) - 1
  1844. if '_class' in attributes:
  1845. attributes['_class'] += 'i'
  1846. if level == 0:
  1847. return
  1848. for c in self.components:
  1849. if hasattr(c, 'value') and not callable(c.value):
  1850. if c.value:
  1851. components.append(c.value)
  1852. if hasattr(c, 'xml') and callable(c.xml):
  1853. components.append(c)
  1854. continue
  1855. elif hasattr(c, 'keys') and callable(c.keys):
  1856. rows = []
  1857. try:
  1858. keys = (sorter and sorter(c)) or c
  1859. for key in keys:
  1860. if isinstance(key, (str, unicode)) and keyfilter:
  1861. filtered_key = keyfilter(key)
  1862. else:
  1863. filtered_key = str(key)
  1864. if filtered_key is None:
  1865. continue
  1866. value = c[key]
  1867. if isinstance(value, types.LambdaType):
  1868. continue
  1869. rows.append(
  1870. TR(
  1871. TD(filtered_key, _style='font-weight:bold;vertical-align:top;'),
  1872. TD(':', _style='vertical-align:top;'),
  1873. TD(BEAUTIFY(value, **attributes))))
  1874. components.append(TABLE(*rows, **attributes))
  1875. continue
  1876. except:
  1877. pass
  1878. if isinstance(c, str):
  1879. components.append(str(c))
  1880. elif isinstance(c, unicode):
  1881. components.append(c.encode('utf8'))
  1882. elif isinstance(c, (list, tuple)):
  1883. items = [TR(TD(BEAUTIFY(item, **attributes)))
  1884. for item in c]
  1885. components.append(TABLE(*items, **attributes))
  1886. elif isinstance(c, cgi.FieldStorage):
  1887. components.append('FieldStorage object')
  1888. else:
  1889. components.append(repr(c))
  1890. self.components = components
  1891. class MENU(DIV):
  1892. """
  1893. Used to build menus
  1894. Optional arguments
  1895. _class: defaults to 'web2py-menu web2py-menu-vertical'
  1896. ul_class: defaults to 'web2py-menu-vertical'
  1897. li_class: defaults to 'web2py-menu-expand'
  1898. li_first: defaults to 'web2py-menu-first'
  1899. li_last: defaults to 'web2py-menu-last'
  1900. Example:
  1901. menu = MENU([['name', False, URL(...), [submenu]], ...])
  1902. {{=menu}}
  1903. """
  1904. tag = 'ul'
  1905. def __init__(self, data, **args):
  1906. self.data = data
  1907. self.attributes = args
  1908. self.components = []
  1909. if not '_class' in self.attributes:
  1910. self['_class'] = 'web2py-menu web2py-menu-vertical'
  1911. if not 'ul_class' in self.attributes:
  1912. self['ul_class'] = 'web2py-menu-vertical'
  1913. if not 'li_class' in self.attributes:
  1914. self['li_class'] = 'web2py-menu-expand'
  1915. if not 'li_first' in self.attributes:
  1916. self['li_first'] = 'web2py-menu-first'
  1917. if not 'li_last' in self.attributes:
  1918. self['li_last'] = 'web2py-menu-last'
  1919. if not 'li_active' in self.attributes:
  1920. self['li_active'] = 'web2py-menu-active'
  1921. if not 'mobile' in self.attributes:
  1922. self['mobile'] = False
  1923. def serialize(self, data, level=0):
  1924. if level == 0:
  1925. ul = UL(**self.attributes)
  1926. else:
  1927. ul = UL(_class=self['ul_class'])
  1928. for item in data:
  1929. if isinstance(item,LI):
  1930. ul.append(item)
  1931. else:
  1932. (name, active, link) = item[:3]
  1933. if isinstance(link, DIV):
  1934. li = LI(link)
  1935. elif 'no_link_url' in self.attributes and self['no_link_url'] == link:
  1936. li = LI(DIV(name))
  1937. elif isinstance(link,dict):
  1938. li = LI(A(name, **link))
  1939. elif link:
  1940. li = LI(A(name, _href=link))
  1941. elif not link and isinstance(name, A):
  1942. li = LI(name)
  1943. else:
  1944. li = LI(A(name, _href='#',
  1945. _onclick='javascript:void(0);return false;'))
  1946. if level == 0 and item == data[0]:
  1947. li['_class'] = self['li_first']
  1948. elif level == 0 and item == data[-1]:
  1949. li['_class'] = self['li_last']
  1950. if len(item) > 3 and item[3]:
  1951. li['_class'] = self['li_class']
  1952. li.append(self.serialize(item[3], level + 1))
  1953. if active or ('active_url' in self.attributes and self['active_url'] == link):
  1954. if li['_class']:
  1955. li['_class'] = li['_class'] + ' ' + self['li_active']
  1956. else:
  1957. li['_class'] = self['li_active']
  1958. if len(item) <= 4 or item[4] == True:
  1959. ul.append(li)
  1960. return ul
  1961. def serialize_mobile(self, data, select=None, prefix=''):
  1962. if not select:
  1963. select = SELECT(**self.attributes)
  1964. for item in data:
  1965. if len(item) <= 4 or item[4] == True:
  1966. select.append(OPTION(CAT(prefix, item[0]),
  1967. _value=item[2], _selected=item[1]))
  1968. if len(item) > 3 and len(item[3]):
  1969. self.serialize_mobile(
  1970. item[3], select, prefix=CAT(prefix, item[0], '/'))
  1971. select['_onchange'] = 'window.location=this.value'
  1972. return select
  1973. def xml(self):
  1974. if self['mobile']:
  1975. return self.serialize_mobile(self.data, 0).xml()
  1976. else:
  1977. return self.serialize(self.data, 0).xml()
  1978. def embed64(
  1979. filename=None,
  1980. file=None,
  1981. data=None,
  1982. extension='image/gif',
  1983. ):
  1984. """
  1985. helper to encode the provided (binary) data into base64.
  1986. :param filename: if provided, opens and reads this file in 'rb' mode
  1987. :param file: if provided, reads this file
  1988. :param data: if provided, uses the provided data
  1989. """
  1990. if filename and os.path.exists(file):
  1991. fp = open(filename, 'rb')
  1992. data = fp.read()
  1993. fp.close()
  1994. data = base64.b64encode(data)
  1995. return 'data:%s;base64,%s' % (extension, data)
  1996. def test():
  1997. """
  1998. Example:
  1999. >>> from validators import *
  2000. >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
  2001. <div><a data-w2p_disable_with="default" href="/a/b/c">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
  2002. >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
  2003. <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
  2004. >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
  2005. <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
  2006. >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
  2007. <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>
  2008. >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
  2009. >>> print form.xml()
  2010. <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
  2011. >>> print form.accepts({'myvar':'34'}, formname=None)
  2012. False
  2013. >>> print form.xml()
  2014. <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>
  2015. >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
  2016. True
  2017. >>> print form.xml()
  2018. <form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
  2019. >>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
  2020. >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
  2021. True
  2022. >>> print form.xml()
  2023. <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>
  2024. >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
  2025. >>> print form.accepts({'myvar':'as df'}, formname=None)
  2026. False
  2027. >>> print form.xml()
  2028. <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>
  2029. >>> session={}
  2030. >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
  2031. >>> isinstance(form.as_dict(), dict)
  2032. True
  2033. >>> form.as_dict(flat=True).has_key("vars")
  2034. True
  2035. >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
  2036. True
  2037. >>> if form.accepts({}, session,formname=None): print 'passed'
  2038. >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
  2039. """
  2040. pass
  2041. class web2pyHTMLParser(HTMLParser):
  2042. """
  2043. obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
  2044. obj.tree contains the root of the tree, and tree can be manipulated
  2045. >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree)
  2046. 'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz'
  2047. >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
  2048. '<div>a<span>b</span></div>c'
  2049. >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
  2050. >>> tree.element(_a='b')['_c']=5
  2051. >>> str(tree)
  2052. 'hello<div a="b" c="5">world</div>'
  2053. """
  2054. def __init__(self, text, closed=('input', 'link')):
  2055. HTMLParser.__init__(self)
  2056. self.tree = self.parent = TAG['']()
  2057. self.closed = closed
  2058. self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
  2059. self.last = None
  2060. self.feed(text)
  2061. def handle_starttag(self, tagname, attrs):
  2062. if tagname.upper() in self.tags:
  2063. tag = eval(tagname.upper())
  2064. else:
  2065. if tagname in self.closed:
  2066. tagname += '/'
  2067. tag = TAG[tagname]()
  2068. for key, value in attrs:
  2069. tag['_' + key] = value
  2070. tag.parent = self.parent
  2071. self.parent.append(tag)
  2072. if not tag.tag.endswith('/'):
  2073. self.parent = tag
  2074. else:
  2075. self.last = tag.tag[:-1]
  2076. def handle_data(self, data):
  2077. if not isinstance(data, unicode):
  2078. try:
  2079. data = data.decode('utf8')
  2080. except:
  2081. data = data.decode('latin1')
  2082. self.parent.append(data.encode('utf8', 'xmlcharref'))
  2083. def handle_charref(self, name):
  2084. if name.startswith('x'):
  2085. self.parent.append(unichr(int(name[1:], 16)).encode('utf8'))
  2086. else:
  2087. self.parent.append(unichr(int(name)).encode('utf8'))
  2088. def handle_entityref(self, name):
  2089. self.parent.append(entitydefs[name])
  2090. def handle_endtag(self, tagname):
  2091. # this deals with unbalanced tags
  2092. if tagname == self.last:
  2093. return
  2094. while True:
  2095. try:
  2096. parent_tagname = self.parent.tag
  2097. self.parent = self.parent.parent
  2098. except:
  2099. raise RuntimeError("unable to balance tag %s" % tagname)
  2100. if parent_tagname[:len(tagname)] == tagname: break
  2101. def markdown_serializer(text, tag=None, attr=None):
  2102. attr = attr or {}
  2103. if tag is None:
  2104. return re.sub('\s+', ' ', text)
  2105. if tag == 'br':
  2106. return '\n\n'
  2107. if tag == 'h1':
  2108. return '#' + text + '\n\n'
  2109. if tag == 'h2':
  2110. return '#' * 2 + text + '\n\n'
  2111. if tag == 'h3':
  2112. return '#' * 3 + text + '\n\n'
  2113. if tag == 'h4':
  2114. return '#' * 4 + text + '\n\n'
  2115. if tag == 'p':
  2116. return text + '\n\n'
  2117. if tag == 'b' or tag == 'strong':
  2118. return '**%s**' % text
  2119. if tag == 'em' or tag == 'i':
  2120. return '*%s*' % text
  2121. if tag == 'tt' or tag == 'code':
  2122. return '`%s`' % text
  2123. if tag == 'a':
  2124. return '[%s](%s)' % (text, attr.get('_href', ''))
  2125. if tag == 'img':
  2126. return '![%s](%s)' % (attr.get('_alt', ''), attr.get('_src', ''))
  2127. return text
  2128. def markmin_serializer(text, tag=None, attr=None):
  2129. attr = attr or {}
  2130. # if tag is None: return re.sub('\s+',' ',text)
  2131. if tag == 'br':
  2132. return '\n\n'
  2133. if tag == 'h1':
  2134. return '# ' + text + '\n\n'
  2135. if tag == 'h2':
  2136. return '#' * 2 + ' ' + text + '\n\n'
  2137. if tag == 'h3':
  2138. return '#' * 3 + ' ' + text + '\n\n'
  2139. if tag == 'h4':
  2140. return '#' * 4 + ' ' + text + '\n\n'
  2141. if tag == 'p':
  2142. return text + '\n\n'
  2143. if tag == 'li':
  2144. return '\n- ' + text.replace('\n', ' ')
  2145. if tag == 'tr':
  2146. return text[3:].replace('\n', ' ') + '\n'
  2147. if tag in ['table', 'blockquote']:
  2148. return '\n-----\n' + text + '\n------\n'
  2149. if tag in ['td', 'th']:
  2150. return ' | ' + text
  2151. if tag in ['b', 'strong', 'label']:
  2152. return '**%s**' % text
  2153. if tag in ['em', 'i']:
  2154. return "''%s''" % text
  2155. if tag in ['tt']:
  2156. return '``%s``' % text.strip()
  2157. if tag in ['code']:
  2158. return '``\n%s``' % text
  2159. if tag == 'a':
  2160. return '[[%s %s]]' % (text, attr.get('_href', ''))
  2161. if tag == 'img':
  2162. return '[[%s %s left]]' % (attr.get('_alt', 'no title'), attr.get('_src', ''))
  2163. return text
  2164. class MARKMIN(XmlComponent):
  2165. """
  2166. For documentation: http://web2py.com/examples/static/markmin.html
  2167. """
  2168. def __init__(self, text, extra=None, allowed=None, sep='p',
  2169. url=None, environment=None, latex='google',
  2170. autolinks='default',
  2171. protolinks='default',
  2172. class_prefix='',
  2173. id_prefix='markmin_'):
  2174. self.text = text
  2175. self.extra = extra or {}
  2176. self.allowed = allowed or {}
  2177. self.sep = sep
  2178. self.url = URL if url == True else url
  2179. self.environment = environment
  2180. self.latex = latex
  2181. self.autolinks = autolinks
  2182. self.protolinks = protolinks
  2183. self.class_prefix = class_prefix
  2184. self.id_prefix = id_prefix
  2185. def xml(self):
  2186. """
  2187. calls the gluon.contrib.markmin render function to convert the wiki syntax
  2188. """
  2189. from contrib.markmin.markmin2html import render
  2190. return render(self.text, extra=self.extra,
  2191. allowed=self.allowed, sep=self.sep, latex=self.latex,
  2192. URL=self.url, environment=self.environment,
  2193. autolinks=self.autolinks, protolinks=self.protolinks,
  2194. class_prefix=self.class_prefix, id_prefix=self.id_prefix)
  2195. def __str__(self):
  2196. return self.xml()
  2197. def flatten(self, render=None):
  2198. """
  2199. return the text stored by the MARKMIN object rendered by the render function
  2200. """
  2201. return self.text
  2202. def elements(self, *args, **kargs):
  2203. """
  2204. to be considered experimental since the behavior of this method is questionable
  2205. another options could be TAG(self.text).elements(*args,**kargs)
  2206. """
  2207. return [self.text]
  2208. if __name__ == '__main__':
  2209. import doctest
  2210. doctest.testmod()