PageRenderTime 85ms CodeModel.GetById 26ms 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

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file