utils.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449
  1. #Copyright ReportLab Europe Ltd. 2000-2019
  2. #see license.txt for license details
  3. # $URI:$
  4. __version__='3.5.34'
  5. __doc__='''Gazillions of miscellaneous internal utility functions'''
  6. import os, sys, time, types, datetime, ast, importlib
  7. from functools import reduce as functools_reduce
  8. literal_eval = ast.literal_eval
  9. try:
  10. from base64 import decodebytes as base64_decodebytes, encodebytes as base64_encodebytes
  11. except ImportError:
  12. from base64 import decodestring as base64_decodebytes, encodestring as base64_encodebytes
  13. from reportlab.lib.logger import warnOnce
  14. from reportlab.lib.rltempfile import get_rl_tempfile, get_rl_tempdir, _rl_getuid
  15. from . rl_safe_eval import rl_safe_exec, rl_safe_eval, safer_globals
  16. class __UNSET__(object):
  17. @staticmethod
  18. def __bool__():
  19. return False
  20. @staticmethod
  21. def __str__():
  22. return '__UNSET__'
  23. __repr__ = __str__
  24. __UNSET__ = __UNSET__()
  25. try:
  26. import cPickle as pickle
  27. except ImportError:
  28. import pickle
  29. try:
  30. from hashlib import md5
  31. except ImportError:
  32. import md5
  33. try:
  34. import platform
  35. isPyPy = platform.python_implementation()=='PyPy'
  36. except:
  37. isPyPy = False
  38. def isFunction(v):
  39. return type(v) == type(isFunction)
  40. class c:
  41. def m(self): pass
  42. def isMethod(v,mt=type(c.m)):
  43. return type(v) == mt
  44. del c
  45. def isModule(v):
  46. return type(v) == type(sys)
  47. def isSeq(v,_st=(tuple,list)):
  48. return isinstance(v,_st)
  49. def isNative(v):
  50. return isinstance(v, str)
  51. #isStr is supposed to be for arbitrary stringType
  52. #isBytes for bytes strings only
  53. #isUnicode for proper unicode
  54. _rl_NoneType=type(None)
  55. bytesT = bytes
  56. unicodeT = str
  57. strTypes = (str,bytes)
  58. def _digester(s):
  59. return md5(s if isBytes(s) else s.encode('utf8')).hexdigest()
  60. def asBytes(v,enc='utf8'):
  61. if isinstance(v,bytes): return v
  62. try:
  63. return v.encode(enc)
  64. except:
  65. annotateException('asBytes(%s,enc=%s) error: ' % (ascii(v),ascii(enc)))
  66. def asUnicode(v,enc='utf8'):
  67. if isinstance(v,str): return v
  68. try:
  69. return v.decode(enc)
  70. except:
  71. annotateException('asUnicode(%s,enc=%s) error: ' % (ascii(v),ascii(enc)))
  72. def asUnicodeEx(v,enc='utf8'):
  73. if isinstance(v,str): return v
  74. try:
  75. return v.decode(enc) if isinstance(v,bytes) else str(v)
  76. except:
  77. annotateException('asUnicodeEx(%s,enc=%s) error: ' % (ascii(v),ascii(enc)))
  78. def asNative(v,enc='utf8'):
  79. return asUnicode(v,enc=enc)
  80. uniChr = chr
  81. def int2Byte(i):
  82. return bytes([i])
  83. def isStr(v):
  84. return isinstance(v, (str,bytes))
  85. def isBytes(v):
  86. return isinstance(v, bytes)
  87. def isUnicode(v):
  88. return isinstance(v, str)
  89. def isClass(v):
  90. return isinstance(v, type)
  91. def isNonPrimitiveInstance(x):
  92. return not isinstance(x,(float,int,type,tuple,list,dict,str,bytes,complex,bool,slice,_rl_NoneType,
  93. types.FunctionType,types.LambdaType,types.CodeType,
  94. types.MappingProxyType,types.SimpleNamespace,
  95. types.GeneratorType,types.MethodType,types.BuiltinFunctionType,
  96. types.BuiltinMethodType,types.ModuleType,types.TracebackType,
  97. types.FrameType,types.GetSetDescriptorType,types.MemberDescriptorType))
  98. def instantiated(v):
  99. return not isinstance(v,type)
  100. from string import ascii_letters, ascii_uppercase, ascii_lowercase
  101. from io import BytesIO, StringIO
  102. def getBytesIO(buf=None):
  103. '''unified StringIO instance interface'''
  104. if buf:
  105. return BytesIO(buf)
  106. return BytesIO()
  107. _bytesIOType = BytesIO
  108. def getStringIO(buf=None):
  109. '''unified StringIO instance interface'''
  110. if buf:
  111. return StringIO(buf)
  112. return StringIO()
  113. def bytestr(x,enc='utf8'):
  114. if isinstance(x,str):
  115. return x.encode(enc)
  116. elif isinstance(x,bytes):
  117. return x
  118. else:
  119. return str(x).encode(enc)
  120. def encode_label(args):
  121. return base64_encodebytes(pickle.dumps(args)).strip().decode('latin1')
  122. def decode_label(label):
  123. return pickle.loads(base64_decodebytes(label.encode('latin1')))
  124. def rawUnicode(s):
  125. '''converts first 256 unicodes 1-1'''
  126. return s.decode('latin1') if not isinstance(s,str) else s
  127. def rawBytes(s):
  128. '''converts first 256 unicodes 1-1'''
  129. return s.encode('latin1') if isinstance(s,str) else s
  130. import builtins
  131. rl_exec = getattr(builtins,'exec')
  132. del builtins
  133. def char2int(s):
  134. return s if isinstance(s,int) else ord(s if isinstance(s,str) else s.decode('latin1'))
  135. def rl_reraise(t, v, b=None):
  136. if v.__traceback__ is not b:
  137. raise v.with_traceback(b)
  138. raise v
  139. def rl_add_builtins(**kwd):
  140. import builtins
  141. for k,v in kwd.items():
  142. setattr(builtins,k,v)
  143. def zipImported(ldr=None):
  144. try:
  145. if not ldr:
  146. ldr = sys._getframe(1).f_globals['__loader__']
  147. from zipimport import zipimporter
  148. return ldr if isinstance(ldr,zipimporter) and len(ldr._files) else None
  149. except:
  150. return None
  151. def _findFiles(dirList,ext='.ttf'):
  152. from os.path import isfile, isdir, join as path_join
  153. from os import listdir
  154. ext = ext.lower()
  155. R = []
  156. A = R.append
  157. for D in dirList:
  158. if not isdir(D): continue
  159. for fn in listdir(D):
  160. fn = path_join(D,fn)
  161. if isfile(fn) and (not ext or fn.lower().endswith(ext)): A(fn)
  162. return R
  163. class CIDict(dict):
  164. def __init__(self,*args,**kwds):
  165. for a in args: self.update(a)
  166. self.update(kwds)
  167. def update(self,D):
  168. for k,v in D.items(): self[k] = v
  169. def __setitem__(self,k,v):
  170. try:
  171. k = k.lower()
  172. except:
  173. pass
  174. dict.__setitem__(self,k,v)
  175. def __getitem__(self,k):
  176. try:
  177. k = k.lower()
  178. except:
  179. pass
  180. return dict.__getitem__(self,k)
  181. def __delitem__(self,k):
  182. try:
  183. k = k.lower()
  184. except:
  185. pass
  186. return dict.__delitem__(self,k)
  187. def get(self,k,dv=None):
  188. try:
  189. return self[k]
  190. except KeyError:
  191. return dv
  192. def __contains__(self,k):
  193. try:
  194. self[k]
  195. return True
  196. except:
  197. return False
  198. def pop(self,k,*a):
  199. try:
  200. k = k.lower()
  201. except:
  202. pass
  203. return dict.pop(*((self,k)+a))
  204. def setdefault(self,k,*a):
  205. try:
  206. k = k.lower()
  207. except:
  208. pass
  209. return dict.setdefault(*((self,k)+a))
  210. if os.name == 'mac':
  211. #with the Mac, we need to tag the file in a special
  212. #way so the system knows it is a PDF file.
  213. #This supplied by Joe Strout
  214. import macfs, macostools
  215. _KNOWN_MAC_EXT = {
  216. 'BMP' : ('ogle','BMP '),
  217. 'EPS' : ('ogle','EPSF'),
  218. 'EPSF': ('ogle','EPSF'),
  219. 'GIF' : ('ogle','GIFf'),
  220. 'JPG' : ('ogle','JPEG'),
  221. 'JPEG': ('ogle','JPEG'),
  222. 'PCT' : ('ttxt','PICT'),
  223. 'PICT': ('ttxt','PICT'),
  224. 'PNG' : ('ogle','PNGf'),
  225. 'PPM' : ('ogle','.PPM'),
  226. 'TIF' : ('ogle','TIFF'),
  227. 'TIFF': ('ogle','TIFF'),
  228. 'PDF' : ('CARO','PDF '),
  229. 'HTML': ('MSIE','TEXT'),
  230. }
  231. def markfilename(filename,creatorcode=None,filetype=None,ext='PDF'):
  232. try:
  233. if creatorcode is None or filetype is None and ext is not None:
  234. try:
  235. creatorcode, filetype = _KNOWN_MAC_EXT[ext.upper()]
  236. except:
  237. return
  238. macfs.FSSpec(filename).SetCreatorType(creatorcode,filetype)
  239. macostools.touched(filename)
  240. except:
  241. pass
  242. else:
  243. def markfilename(filename,creatorcode=None,filetype=None):
  244. pass
  245. import reportlab
  246. __RL_DIR=os.path.dirname(reportlab.__file__) #possibly relative
  247. _RL_DIR=os.path.isabs(__RL_DIR) and __RL_DIR or os.path.abspath(__RL_DIR)
  248. del reportlab
  249. #Attempt to detect if this copy of reportlab is running in a
  250. #file system (as opposed to mostly running in a zip or McMillan
  251. #archive or Jar file). This is used by test cases, so that
  252. #we can write test cases that don't get activated in frozen form.
  253. try:
  254. __file__
  255. except:
  256. __file__ = sys.argv[0]
  257. import glob, fnmatch
  258. try:
  259. __rl_loader__ = __loader__
  260. _isFSD = not __rl_loader__
  261. if not zipImported(ldr=__rl_loader__):
  262. raise NotImplementedError("can't handle compact distro type %r" % __rl_loader__)
  263. _archive = os.path.normcase(os.path.normpath(__rl_loader__.archive))
  264. _archivepfx = _archive + os.sep
  265. _archivedir = os.path.dirname(_archive)
  266. _archivedirpfx = _archivedir + os.sep
  267. _archivepfxlen = len(_archivepfx)
  268. _archivedirpfxlen = len(_archivedirpfx)
  269. def __startswith_rl(fn,
  270. _archivepfx=_archivepfx,
  271. _archivedirpfx=_archivedirpfx,
  272. _archive=_archive,
  273. _archivedir=_archivedir,
  274. os_path_normpath=os.path.normpath,
  275. os_path_normcase=os.path.normcase,
  276. os_getcwd=os.getcwd,
  277. os_sep=os.sep,
  278. os_sep_len = len(os.sep)):
  279. '''if the name starts with a known prefix strip it off'''
  280. fn = os_path_normpath(fn.replace('/',os_sep))
  281. nfn = os_path_normcase(fn)
  282. if nfn in (_archivedir,_archive): return 1,''
  283. if nfn.startswith(_archivepfx): return 1,fn[_archivepfxlen:]
  284. if nfn.startswith(_archivedirpfx): return 1,fn[_archivedirpfxlen:]
  285. cwd = os_path_normcase(os_getcwd())
  286. n = len(cwd)
  287. if nfn.startswith(cwd):
  288. if fn[n:].startswith(os_sep): return 1, fn[n+os_sep_len:]
  289. if n==len(fn): return 1,''
  290. return not os.path.isabs(fn),fn
  291. def _startswith_rl(fn):
  292. return __startswith_rl(fn)[1]
  293. def rl_glob(pattern,glob=glob.glob,fnmatch=fnmatch.fnmatch, _RL_DIR=_RL_DIR,pjoin=os.path.join):
  294. c, pfn = __startswith_rl(pattern)
  295. r = glob(pfn)
  296. if c or r==[]:
  297. r += list(map(lambda x,D=_archivepfx,pjoin=pjoin: pjoin(_archivepfx,x),list(filter(lambda x,pfn=pfn,fnmatch=fnmatch: fnmatch(x,pfn),list(__rl_loader__._files.keys())))))
  298. return r
  299. except:
  300. _isFSD = os.path.isfile(__file__) #slight risk of wrong path
  301. __rl_loader__ = None
  302. def _startswith_rl(fn):
  303. return fn
  304. def rl_glob(pattern,glob=glob.glob):
  305. return glob(pattern)
  306. del glob, fnmatch
  307. _isFSSD = _isFSD and os.path.isfile(os.path.splitext(__file__)[0] +'.py')
  308. def isFileSystemDistro():
  309. '''return truth if a file system distribution'''
  310. return _isFSD
  311. def isCompactDistro():
  312. '''return truth if not a file system distribution'''
  313. return not _isFSD
  314. def isSourceDistro():
  315. '''return truth if a source file system distribution'''
  316. return _isFSSD
  317. def normalize_path(p):
  318. return os.path.normcase(os.path.abspath(os.path.normpath(p)))
  319. _importlib_invalidate_caches = getattr(importlib,'invalidate_caches',lambda :None)
  320. def recursiveImport(modulename, baseDir=None, noCWD=0, debug=0):
  321. """Dynamically imports possible packagized module, or raises ImportError"""
  322. path = [normalize_path(p) for p in sys.path]
  323. if baseDir:
  324. for p in baseDir if isinstance(baseDir,(list,tuple)) else (baseDir,):
  325. if p:
  326. p = normalize_path(p)
  327. if p not in path: path.insert(0,p)
  328. if noCWD:
  329. for p in ('','.',normalize_path('.')):
  330. while p in path:
  331. if debug: print('removed "%s" from path' % p)
  332. path.remove(p)
  333. else:
  334. p = os.getcwd()
  335. if p not in path:
  336. path.insert(0,p)
  337. #make import errors a bit more informative
  338. opath = sys.path
  339. try:
  340. sys.path = path
  341. _importlib_invalidate_caches()
  342. if debug:
  343. print()
  344. print(20*'+')
  345. print('+++++ modulename=%s' % ascii(modulename))
  346. print('+++++ cwd=%s' % ascii(os.getcwd()))
  347. print('+++++ sys.path=%s' % ascii(sys.path))
  348. print('+++++ os.paths.isfile(%s)=%s' % (ascii('./%s.py'%modulename), ascii(os.path.isfile('./%s.py'%modulename))))
  349. print('+++++ opath=%s' % ascii(opath))
  350. print(20*'-')
  351. return importlib.import_module(modulename)
  352. except ImportError:
  353. annotateException("Could not import %r\nusing sys.path %r in cwd=%r" % (
  354. modulename,sys.path,os.getcwd())
  355. )
  356. except:
  357. annotateException("Exception %s while importing %r\nusing sys.path %r in cwd=%r" % (
  358. str(sys.exc_info()[1]), modulename,sys.path,os.getcwd()))
  359. finally:
  360. sys.path = opath
  361. _importlib_invalidate_caches()
  362. if debug:
  363. print('===== restore sys.path=%s' % repr(opath))
  364. def import_zlib():
  365. try:
  366. import zlib
  367. except ImportError:
  368. zlib = None
  369. from reportlab.rl_config import ZLIB_WARNINGS
  370. if ZLIB_WARNINGS: warnOnce('zlib not available')
  371. return zlib
  372. # Image Capability Detection. Set a flag haveImages
  373. # to tell us if either PIL or Java imaging libraries present.
  374. # define PIL_Image as either None, or an alias for the PIL.Image
  375. # module, as there are 2 ways to import it
  376. if sys.platform[0:4] == 'java':
  377. try:
  378. import javax.imageio
  379. import java.awt.image
  380. haveImages = 1
  381. except:
  382. haveImages = 0
  383. else:
  384. try:
  385. from PIL import Image
  386. except ImportError:
  387. try:
  388. import Image
  389. except ImportError:
  390. Image = None
  391. haveImages = Image is not None
  392. class ArgvDictValue:
  393. '''A type to allow clients of getArgvDict to specify a conversion function'''
  394. def __init__(self,value,func):
  395. self.value = value
  396. self.func = func
  397. def getArgvDict(**kw):
  398. ''' Builds a dictionary from its keyword arguments with overrides from sys.argv.
  399. Attempts to be smart about conversions, but the value can be an instance
  400. of ArgDictValue to allow specifying a conversion function.
  401. '''
  402. def handleValue(v,av,func):
  403. if func:
  404. v = func(av)
  405. else:
  406. if isStr(v):
  407. v = av
  408. elif isinstance(v,float):
  409. v = float(av)
  410. elif isinstance(v,int):
  411. v = int(av)
  412. elif isinstance(v,list):
  413. v = list(literal_eval(av),{})
  414. elif isinstance(v,tuple):
  415. v = tuple(literal_eval(av),{})
  416. else:
  417. raise TypeError("Can't convert string %r to %s" % (av,type(v)))
  418. return v
  419. A = sys.argv[1:]
  420. R = {}
  421. for k, v in kw.items():
  422. if isinstance(v,ArgvDictValue):
  423. v, func = v.value, v.func
  424. else:
  425. func = None
  426. handled = 0
  427. ke = k+'='
  428. for a in A:
  429. if a.startswith(ke):
  430. av = a[len(ke):]
  431. A.remove(a)
  432. R[k] = handleValue(v,av,func)
  433. handled = 1
  434. break
  435. if not handled: R[k] = handleValue(v,v,func)
  436. return R
  437. def getHyphenater(hDict=None):
  438. try:
  439. from reportlab.lib.pyHnj import Hyphen
  440. if hDict is None: hDict=os.path.join(os.path.dirname(__file__),'hyphen.mashed')
  441. return Hyphen(hDict)
  442. except ImportError as errMsg:
  443. if str(errMsg)!='No module named pyHnj': raise
  444. return None
  445. def _className(self):
  446. '''Return a shortened class name'''
  447. try:
  448. name = self.__class__.__name__
  449. i=name.rfind('.')
  450. if i>=0: return name[i+1:]
  451. return name
  452. except AttributeError:
  453. return str(self)
  454. def open_for_read_by_name(name,mode='b'):
  455. if 'r' not in mode: mode = 'r'+mode
  456. try:
  457. return open(name,mode)
  458. except IOError:
  459. if _isFSD or __rl_loader__ is None: raise
  460. #we have a __rl_loader__, perhaps the filename starts with
  461. #the dirname(reportlab.__file__) or is relative
  462. name = _startswith_rl(name)
  463. s = __rl_loader__.get_data(name)
  464. if 'b' not in mode and os.linesep!='\n': s = s.replace(os.linesep,'\n')
  465. return getBytesIO(s)
  466. from urllib.parse import unquote, urlparse
  467. from urllib.request import urlopen
  468. def rlUrlRead(name):
  469. return urlopen(name).read()
  470. def open_for_read(name,mode='b'):
  471. #auto initialized function`
  472. #copied here from urllib.URLopener.open_data because
  473. # 1) they want to remove it
  474. # 2) the existing one is borken
  475. def datareader(url, unquote=unquote):
  476. """Use "data" URL."""
  477. # ignore POSTed data
  478. #
  479. # syntax of data URLs:
  480. # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
  481. # mediatype := [ type "/" subtype ] *( ";" parameter )
  482. # data := *urlchar
  483. # parameter := attribute "=" value
  484. try:
  485. typ, data = url.split(',', 1)
  486. except ValueError:
  487. raise IOError('data error', 'bad data URL')
  488. if not typ:
  489. typ = 'text/plain;charset=US-ASCII'
  490. semi = typ.rfind(';')
  491. if semi >= 0 and '=' not in typ[semi:]:
  492. encoding = typ[semi+1:]
  493. typ = typ[:semi]
  494. else:
  495. encoding = ''
  496. if encoding == 'base64':
  497. # XXX is this encoding/decoding ok?
  498. data = base64_decodebytes(data.encode('ascii'))
  499. else:
  500. data = unquote(data).encode('latin-1')
  501. return data
  502. from reportlab.rl_config import trustedHosts, trustedSchemes
  503. if trustedHosts:
  504. import re, fnmatch
  505. def xre(s):
  506. s = fnmatch.translate(s)
  507. return s[4:-3] if s.startswith('(?s:') else s[:-7]
  508. trustedHosts = re.compile(''.join(('^(?:',
  509. '|'.join(map(xre,trustedHosts)),
  510. ')\\Z')))
  511. def open_for_read(name,mode='b'):
  512. '''attempt to open a file or URL for reading'''
  513. if hasattr(name,'read'): return name
  514. try:
  515. return open_for_read_by_name(name,mode)
  516. except:
  517. try:
  518. if trustedHosts is not None:
  519. purl = urlparse(name)
  520. if purl[0] and not ((purl[0] in ('data','file') or trustedHosts.match(purl[1])) and (purl[0] in trustedSchemes)):
  521. raise ValueError('Attempted untrusted host access')
  522. return getBytesIO(datareader(name) if name[:5].lower()=='data:' else rlUrlRead(name))
  523. except:
  524. raise IOError('Cannot open resource "%s"' % name)
  525. globals()['open_for_read'] = open_for_read
  526. return open_for_read(name,mode)
  527. def open_and_read(name,mode='b'):
  528. f = open_for_read(name,mode)
  529. if name is not f and hasattr(f,'__exit__'):
  530. with f:
  531. return f.read()
  532. else:
  533. return f.read()
  534. def open_and_readlines(name,mode='t'):
  535. return open_and_read(name,mode).split('\n')
  536. def rl_isfile(fn,os_path_isfile=os.path.isfile):
  537. if hasattr(fn,'read'): return True
  538. if os_path_isfile(fn): return True
  539. if _isFSD or __rl_loader__ is None: return False
  540. fn = _startswith_rl(fn)
  541. return fn in list(__rl_loader__._files.keys())
  542. def rl_isdir(pn,os_path_isdir=os.path.isdir,os_path_normpath=os.path.normpath):
  543. if os_path_isdir(pn): return True
  544. if _isFSD or __rl_loader__ is None: return False
  545. pn = _startswith_rl(os_path_normpath(pn))
  546. if not pn.endswith(os.sep): pn += os.sep
  547. return len(list(filter(lambda x,pn=pn: x.startswith(pn),list(__rl_loader__._files.keys()))))>0
  548. def rl_listdir(pn,os_path_isdir=os.path.isdir,os_path_normpath=os.path.normpath,os_listdir=os.listdir):
  549. if os_path_isdir(pn) or _isFSD or __rl_loader__ is None: return os_listdir(pn)
  550. pn = _startswith_rl(os_path_normpath(pn))
  551. if not pn.endswith(os.sep): pn += os.sep
  552. return [x[len(pn):] for x in __rl_loader__._files.keys() if x.startswith(pn)]
  553. def rl_getmtime(pn,os_path_isfile=os.path.isfile,os_path_normpath=os.path.normpath,os_path_getmtime=os.path.getmtime,time_mktime=time.mktime):
  554. if os_path_isfile(pn) or _isFSD or __rl_loader__ is None: return os_path_getmtime(pn)
  555. p = _startswith_rl(os_path_normpath(pn))
  556. try:
  557. e = __rl_loader__._files[p]
  558. except KeyError:
  559. return os_path_getmtime(pn)
  560. s = e[5]
  561. d = e[6]
  562. return time_mktime((((d>>9)&0x7f)+1980,(d>>5)&0xf,d&0x1f,(s>>11)&0x1f,(s>>5)&0x3f,(s&0x1f)<<1,0,0,0))
  563. from importlib import util as importlib_util
  564. def __rl_get_module__(name,dir):
  565. for ext in ('.py','.pyw','.pyo','.pyc','.pyd'):
  566. path = os.path.join(dir,name+ext)
  567. if os.path.isfile(path):
  568. spec = importlib_util.spec_from_file_location(name,path)
  569. return spec.loader.load_module()
  570. raise ImportError('no suitable file found')
  571. def rl_get_module(name,dir):
  572. if name in sys.modules:
  573. om = sys.modules[name]
  574. del sys.modules[name]
  575. else:
  576. om = None
  577. try:
  578. try:
  579. return __rl_get_module__(name,dir)
  580. except:
  581. if isCompactDistro():
  582. #attempt a load from inside the zip archive
  583. import zipimport
  584. dir = _startswith_rl(dir)
  585. dir = (dir=='.' or not dir) and _archive or os.path.join(_archive,dir.replace('/',os.sep))
  586. zi = zipimport.zipimporter(dir)
  587. return zi.load_module(name)
  588. raise ImportError('%s[%s]' % (name,dir))
  589. finally:
  590. if om: sys.modules[name] = om
  591. def _isPILImage(im):
  592. try:
  593. return isinstance(im,Image.Image)
  594. except AttributeError:
  595. return 0
  596. class ImageReader(object):
  597. "Wraps up either PIL or Java to get data from bitmaps"
  598. _cache={}
  599. _max_image_size = None
  600. def __init__(self, fileName,ident=None):
  601. if isinstance(fileName,ImageReader):
  602. self.__dict__ = fileName.__dict__ #borgize
  603. return
  604. self._ident = ident
  605. #start wih lots of null private fields, to be populated by
  606. #the relevant engine.
  607. self.fileName = fileName
  608. self._image = None
  609. self._width = None
  610. self._height = None
  611. self._transparent = None
  612. self._data = None
  613. if _isPILImage(fileName):
  614. self._image = fileName
  615. self.fp = getattr(fileName,'fp',None)
  616. try:
  617. self.fileName = self._image.fileName
  618. except AttributeError:
  619. self.fileName = 'PILIMAGE_%d' % id(self)
  620. else:
  621. try:
  622. from reportlab.rl_config import imageReaderFlags
  623. self.fp = open_for_read(fileName,'b')
  624. if isinstance(self.fp,_bytesIOType): imageReaderFlags=0 #avoid messing with already internal files
  625. if imageReaderFlags>0: #interning
  626. data = self.fp.read()
  627. if imageReaderFlags&2: #autoclose
  628. try:
  629. self.fp.close()
  630. except:
  631. pass
  632. if imageReaderFlags&4: #cache the data
  633. if not self._cache:
  634. from rl_config import register_reset
  635. register_reset(self._cache.clear)
  636. data=self._cache.setdefault(_digester(data),data)
  637. self.fp=getBytesIO(data)
  638. elif imageReaderFlags==-1 and isinstance(fileName,str):
  639. #try Ralf Schmitt's re-opening technique of avoiding too many open files
  640. self.fp.close()
  641. del self.fp #will become a property in the next statement
  642. self.__class__=LazyImageReader
  643. if haveImages:
  644. #detect which library we are using and open the image
  645. if not self._image:
  646. self._image = self._read_image(self.fp)
  647. self.check_pil_image_size(self._image)
  648. if getattr(self._image,'format',None)=='JPEG': self.jpeg_fh = self._jpeg_fh
  649. else:
  650. from reportlab.pdfbase.pdfutils import readJPEGInfo
  651. try:
  652. self._width,self._height, c, dpi = readJPEGInfo(self.fp)
  653. except:
  654. annotateException('\nImaging Library not available, unable to import bitmaps only jpegs\nfileName=%r identity=%s'%(fileName,self.identity()))
  655. size = self._width*self._height*c
  656. if self._max_image_size is not None and size>self._max+image_size:
  657. raise MemoryError('JPEG %s color %s x %s image would use %s > %s bytes'
  658. %(c,self._width,self._height,size,self._max_image_size))
  659. self.jpeg_fh = self._jpeg_fh
  660. self._data = self.fp.read()
  661. self._dataA=None
  662. self.fp.seek(0)
  663. except:
  664. annotateException('\nfileName=%r identity=%s'%(fileName,self.identity()))
  665. def identity(self):
  666. '''try to return information that will identify the instance'''
  667. fn = self.fileName
  668. if not isStr(fn):
  669. fn = getattr(getattr(self,'fp',None),'name',None)
  670. ident = self._ident
  671. return '[%s@%s%s%s]' % (self.__class__.__name__,hex(id(self)),ident and (' ident=%r' % ident) or '',fn and (' filename=%r' % fn) or '')
  672. def _read_image(self,fp):
  673. if sys.platform[0:4] == 'java':
  674. from javax.imageio import ImageIO
  675. return ImageIO.read(fp)
  676. else:
  677. return Image.open(fp)
  678. @classmethod
  679. def check_pil_image_size(cls, im):
  680. max_image_size = cls._max_image_size
  681. if max_image_size is None: return
  682. w, h = im.size
  683. m = im.mode
  684. size = max(1,((1 if m=='1' else 8*len(m))*w*h)>>3)
  685. if size>max_image_size:
  686. raise MemoryError('PIL %s %s x %s image would use %s > %s bytes'
  687. %(m,w,h,size,max_image_size))
  688. @classmethod
  689. def set_max_image_size(cls,max_image_size=None):
  690. cls._max_image_size = max_image_size
  691. if max_image_size is not None:
  692. from reportlab.rl_config import register_reset
  693. register_reset(cls.set_max_image_size)
  694. def _jpeg_fh(self):
  695. fp = self.fp
  696. fp.seek(0)
  697. return fp
  698. def jpeg_fh(self):
  699. return None
  700. def getSize(self):
  701. if (self._width is None or self._height is None):
  702. if sys.platform[0:4] == 'java':
  703. self._width = self._image.getWidth()
  704. self._height = self._image.getHeight()
  705. else:
  706. self._width, self._height = self._image.size
  707. return (self._width, self._height)
  708. def getRGBData(self):
  709. "Return byte array of RGB data as string"
  710. try:
  711. if self._data is None:
  712. self._dataA = None
  713. if sys.platform[0:4] == 'java':
  714. import jarray
  715. from java.awt.image import PixelGrabber
  716. width, height = self.getSize()
  717. buffer = jarray.zeros(width*height, 'i')
  718. pg = PixelGrabber(self._image, 0,0,width,height,buffer,0,width)
  719. pg.grabPixels()
  720. # there must be a way to do this with a cast not a byte-level loop,
  721. # I just haven't found it yet...
  722. pixels = []
  723. a = pixels.append
  724. for i in range(len(buffer)):
  725. rgb = buffer[i]
  726. a(chr((rgb>>16)&0xff))
  727. a(chr((rgb>>8)&0xff))
  728. a(chr(rgb&0xff))
  729. self._data = ''.join(pixels)
  730. self.mode = 'RGB'
  731. else:
  732. im = self._image
  733. mode = self.mode = im.mode
  734. if mode in ('LA','RGBA'):
  735. if getattr(Image,'VERSION','').startswith('1.1.7'): im.load()
  736. self._dataA = ImageReader(im.split()[3 if mode=='RGBA' else 1])
  737. nm = mode[:-1]
  738. im = im.convert(nm)
  739. self.mode = nm
  740. elif mode not in ('L','RGB','CMYK'):
  741. if im.format=='PNG' and im.mode=='P' and 'transparency' in im.info:
  742. im = im.convert('RGBA')
  743. self._dataA = ImageReader(im.split()[3])
  744. im = im.convert('RGB')
  745. else:
  746. im = im.convert('RGB')
  747. self.mode = 'RGB'
  748. self._data = (im.tobytes if hasattr(im, 'tobytes') else im.tostring)() #make pillow and PIL both happy, for now
  749. return self._data
  750. except:
  751. annotateException('\nidentity=%s'%self.identity())
  752. def getImageData(self):
  753. width, height = self.getSize()
  754. return width, height, self.getRGBData()
  755. def getTransparent(self):
  756. if sys.platform[0:4] == 'java':
  757. return None
  758. else:
  759. if "transparency" in self._image.info:
  760. transparency = self._image.info["transparency"] * 3
  761. palette = self._image.palette
  762. try:
  763. palette = palette.palette
  764. except:
  765. try:
  766. palette = palette.data
  767. except:
  768. return None
  769. return palette[transparency:transparency+3]
  770. else:
  771. return None
  772. class LazyImageReader(ImageReader):
  773. def fp(self):
  774. return open_for_read(self.fileName, 'b')
  775. fp=property(fp)
  776. def _image(self):
  777. return self._read_image(self.fp)
  778. _image=property(_image)
  779. def getImageData(imageFileName):
  780. "Get width, height and RGB pixels from image file. Wraps Java/PIL"
  781. try:
  782. return imageFileName.getImageData()
  783. except AttributeError:
  784. return ImageReader(imageFileName).getImageData()
  785. class DebugMemo:
  786. '''Intended as a simple report back encapsulator
  787. Typical usages:
  788. 1. To record error data::
  789. dbg = DebugMemo(fn='dbgmemo.dbg',myVar=value)
  790. dbg.add(anotherPayload='aaaa',andagain='bbb')
  791. dbg.dump()
  792. 2. To show the recorded info::
  793. dbg = DebugMemo(fn='dbgmemo.dbg',mode='r')
  794. dbg.load()
  795. dbg.show()
  796. 3. To re-use recorded information::
  797. dbg = DebugMemo(fn='dbgmemo.dbg',mode='r')
  798. dbg.load()
  799. myTestFunc(dbg.payload('myVar'),dbg.payload('andagain'))
  800. In addition to the payload variables the dump records many useful bits
  801. of information which are also printed in the show() method.
  802. '''
  803. def __init__(self,fn='rl_dbgmemo.dbg',mode='w',getScript=1,modules=(),capture_traceback=1, stdout=None, **kw):
  804. import time, socket
  805. self.fn = fn
  806. if not stdout:
  807. self.stdout = sys.stdout
  808. else:
  809. if hasattr(stdout,'write'):
  810. self.stdout = stdout
  811. else:
  812. self.stdout = open(stdout,'w')
  813. if mode!='w': return
  814. self.store = store = {}
  815. if capture_traceback and sys.exc_info() != (None,None,None):
  816. import traceback
  817. s = getBytesIO()
  818. traceback.print_exc(None,s)
  819. store['__traceback'] = s.getvalue()
  820. cwd=os.getcwd()
  821. lcwd = os.listdir(cwd)
  822. pcwd = os.path.dirname(cwd)
  823. lpcwd = pcwd and os.listdir(pcwd) or '???'
  824. exed = os.path.abspath(os.path.dirname(sys.argv[0]))
  825. project_version='???'
  826. md=None
  827. try:
  828. import marshal
  829. md=marshal.loads(__rl_loader__.get_data('meta_data.mar'))
  830. project_version=md['project_version']
  831. except:
  832. pass
  833. env = os.environ
  834. K=list(env.keys())
  835. K.sort()
  836. store.update({ 'gmt': time.asctime(time.gmtime(time.time())),
  837. 'platform': sys.platform,
  838. 'version': sys.version,
  839. 'hexversion': hex(sys.hexversion),
  840. 'executable': sys.executable,
  841. 'exec_prefix': sys.exec_prefix,
  842. 'prefix': sys.prefix,
  843. 'path': sys.path,
  844. 'argv': sys.argv,
  845. 'cwd': cwd,
  846. 'hostname': socket.gethostname(),
  847. 'lcwd': lcwd,
  848. 'lpcwd': lpcwd,
  849. 'byteorder': sys.byteorder,
  850. 'maxint': getattr(sys,'maxunicode','????'),
  851. 'api_version': getattr(sys,'api_version','????'),
  852. 'version_info': getattr(sys,'version_info','????'),
  853. 'winver': getattr(sys,'winver','????'),
  854. 'environment': '\n\t\t\t'.join(['']+['%s=%r' % (k,env[k]) for k in K]),
  855. '__rl_loader__': repr(__rl_loader__),
  856. 'project_meta_data': md,
  857. 'project_version': project_version,
  858. })
  859. for M,A in (
  860. (sys,('getwindowsversion','getfilesystemencoding')),
  861. (os,('uname', 'ctermid', 'getgid', 'getuid', 'getegid',
  862. 'geteuid', 'getlogin', 'getgroups', 'getpgrp', 'getpid', 'getppid',
  863. )),
  864. ):
  865. for a in A:
  866. if hasattr(M,a):
  867. try:
  868. store[a] = getattr(M,a)()
  869. except:
  870. pass
  871. if exed!=cwd:
  872. try:
  873. store.update({'exed': exed, 'lexed': os.listdir(exed),})
  874. except:
  875. pass
  876. if getScript:
  877. fn = os.path.abspath(sys.argv[0])
  878. if os.path.isfile(fn):
  879. try:
  880. store['__script'] = (fn,open(fn,'r').read())
  881. except:
  882. pass
  883. module_versions = {}
  884. for n,m in sys.modules.items():
  885. if n=='reportlab' or n=='rlextra' or n[:10]=='reportlab.' or n[:8]=='rlextra.':
  886. v = [getattr(m,x,None) for x in ('__version__','__path__','__file__')]
  887. if [_f for _f in v if _f]:
  888. v = [v[0]] + [_f for _f in v[1:] if _f]
  889. module_versions[n] = tuple(v)
  890. store['__module_versions'] = module_versions
  891. self.store['__payload'] = {}
  892. self._add(kw)
  893. def _add(self,D):
  894. payload = self.store['__payload']
  895. for k, v in D.items():
  896. payload[k] = v
  897. def add(self,**kw):
  898. self._add(kw)
  899. def _dump(self,f):
  900. try:
  901. pos=f.tell()
  902. pickle.dump(self.store,f)
  903. except:
  904. S=self.store.copy()
  905. ff=getBytesIO()
  906. for k,v in S.items():
  907. try:
  908. pickle.dump({k:v},ff)
  909. except:
  910. S[k] = '<unpicklable object %r>' % v
  911. f.seek(pos,0)
  912. pickle.dump(S,f)
  913. def dump(self):
  914. f = open(self.fn,'wb')
  915. try:
  916. self._dump(f)
  917. finally:
  918. f.close()
  919. def dumps(self):
  920. f = getBytesIO()
  921. self._dump(f)
  922. return f.getvalue()
  923. def _load(self,f):
  924. self.store = pickle.load(f)
  925. def load(self):
  926. f = open(self.fn,'rb')
  927. try:
  928. self._load(f)
  929. finally:
  930. f.close()
  931. def loads(self,s):
  932. self._load(getBytesIO(s))
  933. def _show_module_versions(self,k,v):
  934. self._writeln(k[2:])
  935. K = list(v.keys())
  936. K.sort()
  937. for k in K:
  938. vk = vk0 = v[k]
  939. if isinstance(vk,tuple): vk0 = vk[0]
  940. try:
  941. __import__(k)
  942. m = sys.modules[k]
  943. d = getattr(m,'__version__',None)==vk0 and 'SAME' or 'DIFFERENT'
  944. except:
  945. m = None
  946. d = '??????unknown??????'
  947. self._writeln(' %s = %s (%s)' % (k,vk,d))
  948. def _banner(self,k,what):
  949. self._writeln('###################%s %s##################' % (what,k[2:]))
  950. def _start(self,k):
  951. self._banner(k,'Start ')
  952. def _finish(self,k):
  953. self._banner(k,'Finish ')
  954. def _show_lines(self,k,v):
  955. self._start(k)
  956. self._writeln(v)
  957. self._finish(k)
  958. def _show_file(self,k,v):
  959. k = '%s %s' % (k,os.path.basename(v[0]))
  960. self._show_lines(k,v[1])
  961. def _show_payload(self,k,v):
  962. if v:
  963. import pprint
  964. self._start(k)
  965. pprint.pprint(v,self.stdout)
  966. self._finish(k)
  967. def _show_extensions(self):
  968. for mn in ('_rl_accel','_renderPM','sgmlop','pyRXP','pyRXPU','_imaging','Image'):
  969. try:
  970. A = [mn].append
  971. __import__(mn)
  972. m = sys.modules[mn]
  973. A(m.__file__)
  974. for vn in ('__version__','VERSION','_version','version'):
  975. if hasattr(m,vn):
  976. A('%s=%r' % (vn,getattr(m,vn)))
  977. except:
  978. A('not found')
  979. self._writeln(' '+' '.join(A.__self__))
  980. specials = {'__module_versions': _show_module_versions,
  981. '__payload': _show_payload,
  982. '__traceback': _show_lines,
  983. '__script': _show_file,
  984. }
  985. def show(self):
  986. K = list(self.store.keys())
  987. K.sort()
  988. for k in K:
  989. if k not in list(self.specials.keys()): self._writeln('%-15s = %s' % (k,self.store[k]))
  990. for k in K:
  991. if k in list(self.specials.keys()): self.specials[k](self,k,self.store[k])
  992. self._show_extensions()
  993. def payload(self,name):
  994. return self.store['__payload'][name]
  995. def __setitem__(self,name,value):
  996. self.store['__payload'][name] = value
  997. def __getitem__(self,name):
  998. return self.store['__payload'][name]
  999. def _writeln(self,msg):
  1000. self.stdout.write(msg+'\n')
  1001. def _flatten(L,a):
  1002. for x in L:
  1003. if isSeq(x): _flatten(x,a)
  1004. else: a(x)
  1005. def flatten(L):
  1006. '''recursively flatten the list or tuple L'''
  1007. R = []
  1008. _flatten(L,R.append)
  1009. return R
  1010. def find_locals(func,depth=0):
  1011. '''apply func to the locals at each stack frame till func returns a non false value'''
  1012. while 1:
  1013. _ = func(sys._getframe(depth).f_locals)
  1014. if _: return _
  1015. depth += 1
  1016. class _FmtSelfDict:
  1017. def __init__(self,obj,overrideArgs):
  1018. self.obj = obj
  1019. self._overrideArgs = overrideArgs
  1020. def __getitem__(self,k):
  1021. try:
  1022. return self._overrideArgs[k]
  1023. except KeyError:
  1024. try:
  1025. return self.obj.__dict__[k]
  1026. except KeyError:
  1027. return getattr(self.obj,k)
  1028. class FmtSelfDict:
  1029. '''mixin to provide the _fmt method'''
  1030. def _fmt(self,fmt,**overrideArgs):
  1031. D = _FmtSelfDict(self, overrideArgs)
  1032. return fmt % D
  1033. def _simpleSplit(txt,mW,SW):
  1034. L = []
  1035. ws = SW(' ')
  1036. O = []
  1037. w = -ws
  1038. for t in txt.split():
  1039. lt = SW(t)
  1040. if w+ws+lt<=mW or O==[]:
  1041. O.append(t)
  1042. w = w + ws + lt
  1043. else:
  1044. L.append(' '.join(O))
  1045. O = [t]
  1046. w = lt
  1047. if O!=[]: L.append(' '.join(O))
  1048. return L
  1049. def simpleSplit(text,fontName,fontSize,maxWidth):
  1050. from reportlab.pdfbase.pdfmetrics import stringWidth
  1051. lines = asUnicode(text).split(u'\n')
  1052. SW = lambda text, fN=fontName, fS=fontSize: stringWidth(text, fN, fS)
  1053. if maxWidth:
  1054. L = []
  1055. for l in lines:
  1056. L.extend(_simpleSplit(l,maxWidth,SW))
  1057. lines = L
  1058. return lines
  1059. def escapeTextOnce(text):
  1060. "Escapes once only"
  1061. from xml.sax.saxutils import escape
  1062. if text is None:
  1063. return text
  1064. if isBytes(text): s = text.decode('utf8')
  1065. text = escape(text)
  1066. text = text.replace(u'&amp;amp;',u'&amp;')
  1067. text = text.replace(u'&amp;gt;', u'&gt;')
  1068. text = text.replace(u'&amp;lt;', u'&lt;')
  1069. return text
  1070. def fileName2FSEnc(fn):
  1071. if isUnicode(fn):
  1072. return fn
  1073. else:
  1074. for enc in fsEncodings:
  1075. try:
  1076. return fn.decode(enc)
  1077. except:
  1078. pass
  1079. raise ValueError('cannot convert %r to filesystem encoding' % fn)
  1080. import itertools
  1081. def prev_this_next(items):
  1082. """
  1083. Loop over a collection with look-ahead and look-back.
  1084. From Thomas Guest,
  1085. http://wordaligned.org/articles/zippy-triples-served-with-python
  1086. Seriously useful looping tool (Google "zippy triples")
  1087. lets you loop a collection and see the previous and next items,
  1088. which get set to None at the ends.
  1089. To be used in layout algorithms where one wants a peek at the
  1090. next item coming down the pipe.
  1091. """
  1092. extend = itertools.chain([None], items, [None])
  1093. prev, this, next = itertools.tee(extend, 3)
  1094. try:
  1095. next(this)
  1096. next(next)
  1097. next(next)
  1098. except StopIteration:
  1099. pass
  1100. return zip(prev, this, next)
  1101. def commasplit(s):
  1102. '''
  1103. Splits the string s at every unescaped comma and returns the result as a list.
  1104. To escape a comma, double it. Individual items are stripped.
  1105. To avoid the ambiguity of 3 successive commas to denote a comma at the beginning
  1106. or end of an item, add a space between the item seperator and the escaped comma.
  1107. >>> commasplit(u'a,b,c') == [u'a', u'b', u'c']
  1108. True
  1109. >>> commasplit('a,, , b , c ') == [u'a,', u'b', u'c']
  1110. True
  1111. >>> commasplit(u'a, ,,b, c') == [u'a', u',b', u'c']
  1112. '''
  1113. if isBytes(s): s = s.decode('utf8')
  1114. n = len(s)-1
  1115. s += u' '
  1116. i = 0
  1117. r=[u'']
  1118. while i<=n:
  1119. if s[i]==u',':
  1120. if s[i+1]==u',':
  1121. r[-1]+=u','
  1122. i += 1
  1123. else:
  1124. r[-1] = r[-1].strip()
  1125. if i!=n: r.append(u'')
  1126. else:
  1127. r[-1] += s[i]
  1128. i+=1
  1129. r[-1] = r[-1].strip()
  1130. return r
  1131. def commajoin(l):
  1132. '''
  1133. Inverse of commasplit, except that whitespace around items is not conserved.
  1134. Adds more whitespace than needed for simplicity and performance.
  1135. >>> commasplit(commajoin(['a', 'b', 'c'])) == [u'a', u'b', u'c']
  1136. True
  1137. >>> commasplit((commajoin([u'a,', u' b ', u'c'])) == [u'a,', u'b', u'c']
  1138. True
  1139. >>> commasplit((commajoin([u'a ', u',b', u'c'])) == [u'a', u',b', u'c']
  1140. '''
  1141. return u','.join([ u' ' + asUnicode(i).replace(u',', u',,') + u' ' for i in l ])
  1142. def findInPaths(fn,paths,isfile=True,fail=False):
  1143. '''search for relative files in likely places'''
  1144. exists = isfile and os.path.isfile or os.path.isdir
  1145. if exists(fn): return fn
  1146. pjoin = os.path.join
  1147. if not os.path.isabs(fn):
  1148. for p in paths:
  1149. pfn = pjoin(p,fn)
  1150. if exists(pfn):
  1151. return pfn
  1152. if fail: raise ValueError('cannot locate %r with paths=%r' % (fn,paths))
  1153. return fn
  1154. def annotateException(msg,enc='utf8',postMsg='',sep=' '):
  1155. '''add msg to the args of an existing exception'''
  1156. t,v,b=sys.exc_info()
  1157. rl_reraise(t,t(sep.join((_ for _ in (msg,str(v),postMsg) if _))),b)
  1158. def escapeOnce(data):
  1159. """Ensure XML output is escaped just once, irrespective of input
  1160. >>> escapeOnce('A & B')
  1161. 'A &amp; B'
  1162. >>> escapeOnce('C &amp; D')
  1163. 'C &amp; D'
  1164. >>> escapeOnce('E &amp;amp; F')
  1165. 'E &amp; F'
  1166. """
  1167. data = data.replace("&", "&amp;")
  1168. #...but if it was already escaped, make sure it
  1169. # is not done twice....this will turn any tags
  1170. # back to how they were at the start.
  1171. data = data.replace("&amp;amp;", "&amp;")
  1172. data = data.replace("&amp;gt;", "&gt;")
  1173. data = data.replace("&amp;lt;", "&lt;")
  1174. data = data.replace("&amp;#", "&#")
  1175. #..and just in case someone had double-escaped it, do it again
  1176. data = data.replace("&amp;amp;", "&amp;")
  1177. data = data.replace("&amp;gt;", "&gt;")
  1178. data = data.replace("&amp;lt;", "&lt;")
  1179. return data
  1180. class IdentStr(str):
  1181. '''useful for identifying things that get split'''
  1182. def __new__(cls,value):
  1183. if isinstance(value,IdentStr):
  1184. inc = value.__inc
  1185. value = value[:-(2+len(str(inc)))]
  1186. inc += 1
  1187. else:
  1188. inc = 0
  1189. value += '[%d]' % inc
  1190. self = str.__new__(cls,value)
  1191. self.__inc = inc
  1192. return self
  1193. class RLString(str):
  1194. '''allows specification of extra properties of a string using a dictionary of extra attributes
  1195. eg fontName = RLString('proxima-nova-bold',
  1196. svgAttrs=dict(family='"proxima-nova"',weight='bold'))
  1197. '''
  1198. def __new__(cls,v,**kwds):
  1199. self = str.__new__(cls,v)
  1200. for k,v in kwds.items():
  1201. setattr(self,k,v)
  1202. return self
  1203. def makeFileName(s):
  1204. '''force filename strings to unicode so python can handle encoding stuff'''
  1205. if not isUnicode(s):
  1206. s = s.decode('utf8')
  1207. return s
  1208. class FixedOffsetTZ(datetime.tzinfo):
  1209. """Fixed offset in minutes east from UTC."""
  1210. def __init__(self, h, m, name):
  1211. self.__offset = datetime.timedelta(hours=h, minutes = m)
  1212. self.__name = name
  1213. def utcoffset(self, dt):
  1214. return self.__offset
  1215. def tzname(self, dt):
  1216. return self.__name
  1217. def dst(self, dt):
  1218. return datetime.timedelta(0)
  1219. class TimeStamp(object):
  1220. def __init__(self,invariant=None):
  1221. if invariant is None:
  1222. from reportlab.rl_config import invariant
  1223. t = os.environ.get('SOURCE_DATE_EPOCH','').strip()
  1224. if invariant or t:
  1225. t = int(t) if t else 946684800.0
  1226. lt = time.gmtime(t)
  1227. dhh = dmm = 0
  1228. self.tzname = 'UTC'
  1229. else:
  1230. t = time.time()
  1231. lt = tuple(time.localtime(t))
  1232. dhh = int(time.timezone / (3600.0))
  1233. dmm = (time.timezone % 3600) % 60
  1234. self.tzname = ''
  1235. self.t = t
  1236. self.lt = lt
  1237. self.YMDhms = tuple(lt)[:6]
  1238. self.dhh = dhh
  1239. self.dmm = dmm
  1240. @property
  1241. def datetime(self):
  1242. if self.tzname:
  1243. return datetime.datetime.fromtimestamp(self.t,FixedOffsetTZ(self.dhh,self.dmm,self.tzname))
  1244. else:
  1245. return datetime.datetime.now()
  1246. @property
  1247. def asctime(self):
  1248. return time.asctime(self.lt)
  1249. def recursiveGetAttr(obj, name, g=None):
  1250. "Can call down into e.g. object1.object2[4].attr"
  1251. if not isStr(name): raise TypeError('invalid reursive acess using %r' % name)
  1252. name = asNative(name)
  1253. name = name.strip()
  1254. if not name: raise ValueError('empty recursive access')
  1255. dot = '.' if name and name[0] not in '[.(' else ''
  1256. return rl_safe_eval('obj%s%s'%(dot,name), g={}, l=dict(obj=obj))
  1257. def recursiveSetAttr(obj, name, value):
  1258. "Can call down into e.g. object1.object2[4].attr = value"
  1259. #get the thing above last.
  1260. tokens = name.split('.')
  1261. if len(tokens) == 1:
  1262. setattr(obj, name, value)
  1263. else:
  1264. most = '.'.join(tokens[:-1])
  1265. last = tokens[-1]
  1266. parent = recursiveGetAttr(obj, most)
  1267. setattr(parent, last, value)
  1268. def recursiveDelAttr(obj, name):
  1269. tokens = name.split('.')
  1270. if len(tokens) == 1:
  1271. delattr(obj, name)
  1272. else:
  1273. most = '.'.join(tokens[:-1])
  1274. last = tokens[-1]
  1275. parent = recursiveGetAttr(obj, most)
  1276. delattr(parent, last)
  1277. def yieldNoneSplits(L):
  1278. '''yield sublists of L separated by None; the Nones disappear'''
  1279. i = 0
  1280. n = len(L)
  1281. while i<n:
  1282. try:
  1283. j = L.index(None,i)
  1284. yield L[i:j]
  1285. i = j+1
  1286. if not L: break
  1287. except ValueError:
  1288. yield L[i:]
  1289. break