Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/MarkupPy/markup.py: 19%

289 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# This code is in the public domain, it comes 

2# with absolutely no warranty and you can do 

3# absolutely whatever you want with it. 

4 

5__date__ = '16 March 2015' 

6__version__ = '1.10' 

7__doc__= """ 

8This is markup.py - a Python module that attempts to 

9make it easier to generate HTML/XML from a Python program 

10in an intuitive, lightweight, customizable and pythonic way. 

11It works with both python 2 and 3. 

12 

13The code is in the public domain. 

14 

15Version: %s as of %s. 

16 

17Documentation and further info is at http://markup.sourceforge.net/ 

18 

19Please send bug reports, feature requests, enhancement 

20ideas or questions to nogradi at gmail dot com. 

21 

22Installation: drop markup.py somewhere into your Python path. 

23""" % ( __version__, __date__ ) 

24 

25try: 

26 basestring 

27 import string 

28except: 

29 # python 3 

30 basestring = str 

31 string = str 

32 long = int 

33 

34# tags which are reserved python keywords will be referred  

35# to by a leading underscore otherwise we end up with a syntax error 

36import keyword 

37 

38class element: 

39 """This class handles the addition of a new element.""" 

40 

41 def __init__( self, tag, case='lower', parent=None ): 

42 self.parent = parent 

43 

44 if case == 'upper': 

45 self.tag = tag.upper( ) 

46 elif case == 'lower': 

47 self.tag = tag.lower( ) 

48 elif case =='given': 

49 self.tag = tag 

50 else: 

51 self.tag = tag 

52 

53 def __call__( self, *args, **kwargs ): 

54 if len( args ) > 1: 

55 raise ArgumentError( self.tag ) 

56 

57 # if class_ was defined in parent it should be added to every element 

58 if self.parent is not None and self.parent.class_ is not None: 

59 if 'class_' not in kwargs: 

60 kwargs['class_'] = self.parent.class_ 

61 

62 if self.parent is None and len( args ) == 1: 

63 x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ] 

64 return '\n'.join( x ) 

65 elif self.parent is None and len( args ) == 0: 

66 x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ] 

67 return '\n'.join( x ) 

68 

69 if self.tag in self.parent.twotags: 

70 for myarg, mydict in _argsdicts( args, kwargs ): 

71 self.render( self.tag, False, myarg, mydict ) 

72 elif self.tag in self.parent.onetags: 

73 if len( args ) == 0: 

74 for myarg, mydict in _argsdicts( args, kwargs ): 

75 self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0 

76 else: 

77 raise ClosingError( self.tag ) 

78 elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags: 

79 raise DeprecationError( self.tag ) 

80 else: 

81 raise InvalidElementError( self.tag, self.parent.mode ) 

82 

83 def render( self, tag, single, between, kwargs ): 

84 """Append the actual tags to content.""" 

85 

86 out = "<%s" % tag 

87 for key, value in list( kwargs.items( ) ): 

88 if value is not None: # when value is None that means stuff like <... checked> 

89 key = key.strip('_') # strip this so class_ will mean class, etc. 

90 if key == 'http_equiv': # special cases, maybe change _ to - overall? 

91 key = 'http-equiv' 

92 elif key == 'accept_charset': 

93 key = 'accept-charset' 

94 out = "%s %s=\"%s\"" % ( out, key, escape( value ) ) 

95 else: 

96 out = "%s %s" % ( out, key ) 

97 if between is not None: 

98 out = "%s>%s</%s>" % ( out, between, tag ) 

99 else: 

100 if single: 

101 out = "%s />" % out 

102 else: 

103 out = "%s>" % out 

104 if self.parent is not None: 

105 self.parent.content.append( out ) 

106 else: 

107 return out 

108 

109 def close( self ): 

110 """Append a closing tag unless element has only opening tag.""" 

111 

112 if self.tag in self.parent.twotags: 

113 self.parent.content.append( "</%s>" % self.tag ) 

114 elif self.tag in self.parent.onetags: 

115 raise ClosingError( self.tag ) 

116 elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags: 

117 raise DeprecationError( self.tag ) 

118 

119 def open( self, **kwargs ): 

120 """Append an opening tag.""" 

121 

122 if self.tag in self.parent.twotags or self.tag in self.parent.onetags: 

123 self.render( self.tag, False, None, kwargs ) 

124 elif self.mode == 'strict_html' and self.tag in self.parent.deptags: 

125 raise DeprecationError( self.tag ) 

126 

127class page: 

128 """This is our main class representing a document. Elements are added 

129 as attributes of an instance of this class.""" 

130 

131 def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ): 

132 """Stuff that effects the whole document. 

133 

134 mode -- 'strict_html' for HTML 4.01 (default) 

135 'html' alias for 'strict_html' 

136 'loose_html' to allow some deprecated elements 

137 'xml' to allow arbitrary elements 

138 

139 case -- 'lower' element names will be printed in lower case (default) 

140 'upper' they will be printed in upper case 

141 'given' element names will be printed as they are given 

142 

143 onetags -- list or tuple of valid elements with opening tags only 

144 twotags -- list or tuple of valid elements with both opening and closing tags 

145 these two keyword arguments may be used to select 

146 the set of valid elements in 'xml' mode 

147 invalid elements will raise appropriate exceptions 

148  

149 separator -- string to place between added elements, defaults to newline 

150  

151 class_ -- a class that will be added to every element if defined""" 

152 

153 valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ] 

154 valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON", 

155 "CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET", 

156 "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS", 

157 "KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", 

158 "OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE", 

159 "SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR", 

160 "TT", "UL", "VAR" ] 

161 deprecated_onetags = [ "BASEFONT", "ISINDEX" ] 

162 deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ] 

163 

164 self.header = [ ] 

165 self.content = [ ] 

166 self.footer = [ ] 

167 self.case = case 

168 self.separator = separator 

169 

170 # init( ) sets it to True so we know that </body></html> has to be printed at the end 

171 self._full = False 

172 self.class_= class_ 

173 

174 if mode == 'strict_html' or mode == 'html': 

175 self.onetags = valid_onetags 

176 self.onetags += list( map( string.lower, self.onetags ) ) 

177 self.twotags = valid_twotags 

178 self.twotags += list( map( string.lower, self.twotags ) ) 

179 self.deptags = deprecated_onetags + deprecated_twotags 

180 self.deptags += list( map( string.lower, self.deptags ) ) 

181 self.mode = 'strict_html' 

182 elif mode == 'loose_html': 

183 self.onetags = valid_onetags + deprecated_onetags 

184 self.onetags += list( map( string.lower, self.onetags ) ) 

185 self.twotags = valid_twotags + deprecated_twotags 

186 self.twotags += list( map( string.lower, self.twotags ) ) 

187 self.mode = mode 

188 elif mode == 'xml': 

189 if onetags and twotags: 

190 self.onetags = onetags 

191 self.twotags = twotags 

192 elif ( onetags and not twotags ) or ( twotags and not onetags ): 

193 raise CustomizationError( ) 

194 else: 

195 self.onetags = russell( ) 

196 self.twotags = russell( ) 

197 self.mode = mode 

198 else: 

199 raise ModeError( mode ) 

200 

201 def __getattr__( self, attr ): 

202 

203 # tags should start with double underscore 

204 if attr.startswith("__") and attr.endswith("__"): 

205 raise AttributeError( attr ) 

206 # tag with single underscore should be a reserved keyword 

207 if attr.startswith( '_' ): 

208 attr = attr.lstrip( '_' ) 

209 if attr not in keyword.kwlist: 

210 raise AttributeError( attr ) 

211 

212 return element( attr, case=self.case, parent=self ) 

213 

214 def __str__( self ): 

215 

216 if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ): 

217 end = [ '</body>', '</html>' ] 

218 else: 

219 end = [ ] 

220 

221 return self.separator.join( self.header + self.content + self.footer + end ) 

222 

223 def __call__( self, escape=False ): 

224 """Return the document as a string. 

225 

226 escape -- False print normally 

227 True replace < and > by &lt; and &gt; 

228 the default escape sequences in most browsers""" 

229 

230 if escape: 

231 return _escape( self.__str__( ) ) 

232 else: 

233 return self.__str__( ) 

234 

235 def add( self, text ): 

236 """This is an alias to addcontent.""" 

237 self.addcontent( text ) 

238 

239 def addfooter( self, text ): 

240 """Add some text to the bottom of the document""" 

241 self.footer.append( text ) 

242 

243 def addheader( self, text ): 

244 """Add some text to the top of the document""" 

245 self.header.append( text ) 

246 

247 def addcontent( self, text ): 

248 """Add some text to the main part of the document""" 

249 self.content.append( text ) 

250 

251 

252 def init( self, lang='en', css=None, metainfo=None, title=None, header=None, 

253 footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None, base=None ): 

254 """This method is used for complete documents with appropriate 

255 doctype, encoding, title, etc information. For an HTML/XML snippet 

256 omit this method. 

257 

258 lang -- language, usually a two character string, will appear 

259 as <html lang='en'> in html mode (ignored in xml mode) 

260  

261 css -- Cascading Style Sheet filename as a string or a list of 

262 strings for multiple css files (ignored in xml mode) 

263 

264 metainfo -- a dictionary in the form { 'name':'content' } to be inserted 

265 into meta element(s) as <meta name='name' content='content'> 

266 (ignored in xml mode) 

267  

268 base -- set the <base href="..."> tag in <head> 

269  

270 bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added 

271 as attributes of the <body> element as <body key='value' ... > 

272 (ignored in xml mode) 

273 

274 script -- dictionary containing src:type pairs, <script type='text/type' src=src></script> 

275 or a list of [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for all 

276 

277 title -- the title of the document as a string to be inserted into 

278 a title element as <title>my title</title> (ignored in xml mode) 

279 

280 header -- some text to be inserted right after the <body> element 

281 (ignored in xml mode) 

282 

283 footer -- some text to be inserted right before the </body> element 

284 (ignored in xml mode) 

285 

286 charset -- a string defining the character set, will be inserted into a 

287 <meta http-equiv='Content-Type' content='text/html; charset=myset'> 

288 element (ignored in xml mode) 

289 

290 encoding -- a string defining the encoding, will be put into to first line of 

291 the document as <?xml version='1.0' encoding='myencoding' ?> in 

292 xml mode (ignored in html mode) 

293 

294 doctype -- the document type string, defaults to 

295 <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'> 

296 in html mode (ignored in xml mode)""" 

297 

298 self._full = True 

299 

300 if self.mode == 'strict_html' or self.mode == 'loose_html': 

301 if doctype is None: 

302 doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>" 

303 self.header.append( doctype ) 

304 self.html( lang=lang ) 

305 self.head( ) 

306 if charset is not None: 

307 self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset ) 

308 if metainfo is not None: 

309 self.metainfo( metainfo ) 

310 if css is not None: 

311 self.css( css ) 

312 if title is not None: 

313 self.title( title ) 

314 if script is not None: 

315 self.scripts( script ) 

316 if base is not None: 

317 self.base( href='%s' % base ) 

318 self.head.close() 

319 if bodyattrs is not None: 

320 self.body( **bodyattrs ) 

321 else: 

322 self.body( ) 

323 if header is not None: 

324 self.content.append( header ) 

325 if footer is not None: 

326 self.footer.append( footer ) 

327 

328 elif self.mode == 'xml': 

329 if doctype is None: 

330 if encoding is not None: 

331 doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding 

332 else: 

333 doctype = "<?xml version='1.0' ?>" 

334 self.header.append( doctype ) 

335 

336 def css( self, filelist ): 

337 """This convenience function is only useful for html. 

338 It adds css stylesheet(s) to the document via the <link> element.""" 

339 

340 if isinstance( filelist, basestring ): 

341 self.link( href=filelist, rel='stylesheet', type='text/css', media='all' ) 

342 else: 

343 for file in filelist: 

344 self.link( href=file, rel='stylesheet', type='text/css', media='all' ) 

345 

346 def metainfo( self, mydict ): 

347 """This convenience function is only useful for html. 

348 It adds meta information via the <meta> element, the argument is 

349 a dictionary of the form { 'name':'content' }.""" 

350 

351 if isinstance( mydict, dict ): 

352 for name, content in list( mydict.items( ) ): 

353 self.meta( name=name, content=content ) 

354 else: 

355 raise TypeError( "Metainfo should be called with a dictionary argument of name:content pairs." ) 

356 

357 def scripts( self, mydict ): 

358 """Only useful in html, mydict is dictionary of src:type pairs or a list 

359 of script sources [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for type. 

360 Will be rendered as <script type='text/type' src=src></script>""" 

361 

362 if isinstance( mydict, dict ): 

363 for src, type in list( mydict.items( ) ): 

364 self.script( '', src=src, type='text/%s' % type ) 

365 else: 

366 try: 

367 for src in mydict: 

368 self.script( '', src=src, type='text/javascript' ) 

369 except: 

370 raise TypeError( "Script should be given a dictionary of src:type pairs or a list of javascript src's." ) 

371 

372 

373class _oneliner: 

374 """An instance of oneliner returns a string corresponding to one element. 

375 This class can be used to write 'oneliners' that return a string 

376 immediately so there is no need to instantiate the page class.""" 

377 

378 def __init__( self, case='lower' ): 

379 self.case = case 

380 

381 def __getattr__( self, attr ): 

382 

383 # tags should start with double underscore 

384 if attr.startswith("__") and attr.endswith("__"): 

385 raise AttributeError( attr ) 

386 # tag with single underscore should be a reserved keyword 

387 if attr.startswith( '_' ): 

388 attr = attr.lstrip( '_' ) 

389 if attr not in keyword.kwlist: 

390 raise AttributeError( attr ) 

391 

392 return element( attr, case=self.case, parent=None ) 

393 

394oneliner = _oneliner( case='lower' ) 

395upper_oneliner = _oneliner( case='upper' ) 

396given_oneliner = _oneliner( case='given' ) 

397 

398def _argsdicts( args, mydict ): 

399 """A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1.""" 

400 

401 if len( args ) == 0: 

402 args = None, 

403 elif len( args ) == 1: 

404 args = _totuple( args[0] ) 

405 else: 

406 raise Exception( "We should have never gotten here." ) 

407 

408 mykeys = list( mydict.keys( ) ) 

409 myvalues = list( map( _totuple, list( mydict.values( ) ) ) ) 

410 

411 maxlength = max( list( map( len, [ args ] + myvalues ) ) ) 

412 

413 for i in range( maxlength ): 

414 thisdict = { } 

415 for key, value in zip( mykeys, myvalues ): 

416 try: 

417 thisdict[ key ] = value[i] 

418 except IndexError: 

419 thisdict[ key ] = value[-1] 

420 try: 

421 thisarg = args[i] 

422 except IndexError: 

423 thisarg = args[-1] 

424 

425 yield thisarg, thisdict 

426 

427def _totuple( x ): 

428 """Utility stuff to convert string, int, long, float, None or anything to a usable tuple.""" 

429 

430 if isinstance( x, basestring ): 

431 out = x, 

432 elif isinstance( x, ( int, long, float ) ): 

433 out = str( x ), 

434 elif x is None: 

435 out = None, 

436 else: 

437 out = tuple( x ) 

438 

439 return out 

440 

441def escape( text, newline=False ): 

442 """Escape special html characters.""" 

443 

444 if isinstance( text, basestring ): 

445 if '&' in text: 

446 text = text.replace( '&', '&amp;' ) 

447 if '>' in text: 

448 text = text.replace( '>', '&gt;' ) 

449 if '<' in text: 

450 text = text.replace( '<', '&lt;' ) 

451 if '\"' in text: 

452 text = text.replace( '\"', '&quot;' ) 

453 if '\'' in text: 

454 text = text.replace( '\'', '&quot;' ) 

455 if newline: 

456 if '\n' in text: 

457 text = text.replace( '\n', '<br>' ) 

458 

459 return text 

460 

461_escape = escape 

462 

463def unescape( text ): 

464 """Inverse of escape.""" 

465 

466 if isinstance( text, basestring ): 

467 if '&amp;' in text: 

468 text = text.replace( '&amp;', '&' ) 

469 if '&gt;' in text: 

470 text = text.replace( '&gt;', '>' ) 

471 if '&lt;' in text: 

472 text = text.replace( '&lt;', '<' ) 

473 if '&quot;' in text: 

474 text = text.replace( '&quot;', '\"' ) 

475 

476 return text 

477 

478class dummy: 

479 """A dummy class for attaching attributes.""" 

480 pass 

481 

482doctype = dummy( ) 

483doctype.frameset = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">""" 

484doctype.strict = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">""" 

485doctype.loose = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">""" 

486 

487class russell: 

488 """A dummy class that contains anything.""" 

489 

490 def __contains__( self, item ): 

491 return True 

492 

493 

494class MarkupError( Exception ): 

495 """All our exceptions subclass this.""" 

496 def __str__( self ): 

497 return self.message 

498 

499class ClosingError( MarkupError ): 

500 def __init__( self, tag ): 

501 self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag 

502 

503class OpeningError( MarkupError ): 

504 def __init__( self, tag ): 

505 self.message = "The element '%s' can not be opened." % tag 

506 

507class ArgumentError( MarkupError ): 

508 def __init__( self, tag ): 

509 self.message = "The element '%s' was called with more than one non-keyword argument." % tag 

510 

511class InvalidElementError( MarkupError ): 

512 def __init__( self, tag, mode ): 

513 self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode ) 

514 

515class DeprecationError( MarkupError ): 

516 def __init__( self, tag ): 

517 self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag 

518 

519class ModeError( MarkupError ): 

520 def __init__( self, mode ): 

521 self.message = "Mode '%s' is invalid, possible values: strict_html, html (alias for strict_html), loose_html, xml." % mode 

522 

523class CustomizationError( MarkupError ): 

524 def __init__( self ): 

525 self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'." 

526 

527if __name__ == '__main__': 527 ↛ 528line 527 didn't jump to line 528, because the condition on line 527 was never true

528 import sys 

529 sys.stdout.write( __doc__ )