Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Functions to dump emails 

5""" 

6import os 

7import re 

8import pprint 

9from jinja2.exceptions import UndefinedError 

10from pyquickhelper.loghelper import noLOG 

11from ..grabber.email_message import EmailMessage 

12from ..helpers.buffer_files_writing import BufferFilesWriting 

13from .email_message_style import template_email_html, template_email_css 

14from .renderer import Renderer 

15 

16 

17class EmailMessageRenderer(Renderer): 

18 """ 

19 defines way to render an email 

20 """ 

21 

22 def __init__(self, tmpl=None, css=None, 

23 style_table="dataframe100l", 

24 style_highlight="dataframe100l_hl", 

25 buffer_write=None, 

26 fLOG=noLOG): 

27 """ 

28 Defines a template based 

29 on `Jinja2 <http://jinja.pocoo.org/docs/dev/>`_. 

30 

31 @param tmpl template (string or file) 

32 @param css style 

33 @param style_table style for the table 

34 @param style_highlight style for highlighted cells 

35 @param buffer_write instance of class @see cl BufferFilesWriting 

36 @param fLOG logging function 

37 

38 @example(Template to render an email) 

39 

40 See file :mod:`email_message_style <pymmails.grabber.email_message_style>` 

41 

42 HTML template:: 

43 

44 <?xml version="1.0" encoding="utf-8"?> 

45 <body> 

46 <html> 

47 <head> 

48 <title>{{ message.get_field("subject") }}</title> 

49 <link rel="stylesheet" type="text/css" href="{{ css }}"> 

50 </head> 

51 <body> 

52 {{ '<a href="{0}">&lt;--</a>'.format(prev_mail) if prev_mail else '' }} 

53 {{ '<a href="{0}">--&gt;</a>'.format(next_mail) if next_mail else '' }} 

54 <h1>{{ message.get_date().strftime('%Y/%m/%d') }} - {{ message.get_field("subject") }}</h1> 

55 <h2>attributes</h2> 

56 {{ render.produce_table_html(message, toshow=EmailMessage.subset, location=location, avoid=EmailMessage.avoid) }} 

57 <h2>message</h2> 

58 {{ render.process_body_html(location, message.body_html, attachments) }} 

59 <h2>full list of attributes</h2> 

60 {{ render.produce_table_html(message, toshow=message.Fields, location=location, tohighlight=EmailMessage.subset) }} 

61 </body> 

62 </html> 

63 

64 CSS style:: 

65 

66 .dataframe100l { 

67 padding: 0; 

68 width=75% 

69 font-family: Calibri; 

70 font-size: 100%; 

71 cursor: pointer; 

72 } 

73 

74 .dataframe100l table { 

75 border-collapse: collapse; 

76 text-align: left; 

77 font-size: 11px; 

78 } 

79 

80 .dataframe100l table td, .dataframe100l table th { 

81 padding: 3px 6px; 

82 } 

83 

84 .dataframe100l table thead th { 

85 background-color:#AAAAAA; 

86 color:#ffffff; 

87 font-size: 11px; 

88 font-weight: bold; 

89 border-left: 1px solid #0070A8; 

90 } 

91 

92 .dataframe100l table tbody td { 

93 color: #00496B; 

94 border-left: 1px solid #E1EEF4; 

95 font-size: 11px; 

96 font-weight: normal; 

97 } 

98 

99 .dataframe100l table tbody .alt td { 

100 background: #E1EEF4; 

101 color: #00496B; 

102 } 

103 

104 .dataframe100l_hl td { 

105 background: #FFFF00; 

106 } 

107 

108 .dataframe100l_hl th { 

109 background: #FFFF00; 

110 } 

111 

112 .dataframe100l_hl tr { 

113 background: #FFFF00; 

114 } 

115 

116 .dataframe100l table tbody td:first-child { 

117 border-left: none; 

118 } 

119 

120 .dataframe100l table tbody tr:last-child td { 

121 border-bottom: none; 

122 } 

123 

124 .dataframe100l table tfoot td div { 

125 border-top: 1px solid #006699; 

126 background: #E1EEF4; 

127 } 

128 

129 .dataframe100l table tfoot td { 

130 padding: 0; 

131 font-size: 11px 

132 } 

133 

134 .dataframe100l table tfoot td div{ padding: 2px; } 

135 .dataframe100l table tfoot td ul { 

136 margin: 0; 

137 padding:0; 

138 list-style: none; 

139 text-align: right; 

140 } 

141 

142 .dataframe100l table tfoot li { display: inline; } 

143 .dataframe100l table tfoot li a { 

144 text-decoration: none; 

145 display: inline-block; 

146 padding: 2px 8px; 

147 margin: 1px; 

148 color: #FFFFFF; 

149 border: 1px solid #006699; 

150 border-radius: 3px; 

151 background-color:#006699; 

152 } 

153 

154 .dataframe100l table tfoot ul.active, .dataframe100l table tfoot ul a:hover { 

155 text-decoration: none; 

156 border-color: #006699; 

157 color: #FFFFFF; 

158 background: none; 

159 background-color:#00557F; 

160 } 

161 

162 @endexample 

163 

164 """ 

165 if tmpl is None: 

166 _template = template_email_html 

167 elif len(tmpl) < 5000 and os.path.exists(tmpl): 

168 with open(tmpl, "r", encoding="utf8") as f: 

169 _template = f.read() 

170 else: 

171 _template = tmpl 

172 

173 if css is None: 

174 _css = template_email_css 

175 elif len(css) < 5000 and os.path.exists(css): 

176 with open(css, "r", encoding="utf8") as f: 

177 _css = f.read() 

178 else: 

179 _css = css 

180 

181 if buffer_write is None: 

182 buffer_write = BufferFilesWriting(fLOG=fLOG) 

183 Renderer.__init__(self, tmpl=_template, css=_css, style_table=style_table, 

184 style_highlight=style_highlight, buffer_write=buffer_write, fLOG=fLOG) 

185 

186 def render(self, location, mail, attachments, # pylint: disable=W0221 

187 file_css="mail_style.css", prev_mail=None, next_mail=None, **addition): 

188 """ 

189 Renders a mail. 

190 

191 @paramp location location where this mail should be saved 

192 @param mail instance of @see cl EmailMessage 

193 @param file_css css file (where it is supposed to be stored) 

194 @param attachments attachments 

195 @param prev_mail previous mail (or None if there is none) 

196 @param next_mail next mail (or None if there is none) 

197 @param addition sent to *Jinja* 

198 @return html, css (content), attachments 

199 

200 The mail is stored in object ``message``, ``css`` means the style sheet, 

201 ``render`` means this object, ``location`` means *location*, 

202 ``EmailMessage`` is the class *EmailMessage*, ``attachments`` is *attachments*:: 

203 

204 {{ message.get_subject() }} 

205 

206 The value stored in *file_css* will be relative to *location*. 

207 

208 """ 

209 file_css = os.path.relpath(file_css, location) 

210 css = self._css.render(message=mail) 

211 try: 

212 html = self._template.render(message=mail, css=file_css, render=self, 

213 location=location, EmailMessage=EmailMessage, 

214 attachments=attachments, prev_mail=prev_mail, 

215 next_mail=next_mail, **addition) 

216 except UndefinedError as e: 

217 empty = [] 

218 if 'groups' in addition: 

219 for gr in addition['groups']: 

220 if 'emails' in gr and len(gr['emails']) == 0: 

221 empty.append(gr) 

222 tmpl = self._raw_template 

223 disp1 = pprint.pformat(empty) 

224 mes = "This usually happens when the project was sent with a mail not retained in a the final list." 

225 raise RuntimeError("Unable to apply pattern\n----\n{0}\n----\n{1}\n----\n{2}".format( 

226 mes, tmpl, disp1)) from e 

227 return html, css, attachments 

228 

229 def write(self, location, mail, filename, attachments=None, # pylint: disable=W0221 

230 overwrite=False, file_css="mail_style.css", encoding="utf8", 

231 prev_mail=None, next_mail=None, **addition): 

232 """ 

233 Writes a mail, the function assumes the attachments were already dumped. 

234 

235 @param location location 

236 @param mail instance of @see cl EmailMessage 

237 @param attachments list of attachments (see @see me dump_attachments) 

238 @param overwrite the function does not overwrite 

239 @param file_css css file (where it is supposed to be stored) 

240 @param encoding encoding 

241 @param addition additional parameter sent to Jinja2 

242 @param prev_mail previous mail (or None if there is none) 

243 @param next_mail next mail (or None if there is none) 

244 @return list of written local files, attachements 

245 """ 

246 full_css = os.path.join(location, file_css) 

247 full_mail = os.path.join(location, filename) 

248 if not overwrite and self.BufferWrite.exists(full_css) and \ 

249 self.BufferWrite.exists(full_mail): 

250 return [full_mail, full_css], attachments 

251 html, css, attachments = self.render( 

252 location, mail, attachments, file_css=full_css, 

253 prev_mail=prev_mail, next_mail=next_mail, **addition) 

254 if not self.BufferWrite.exists(full_css, local=not overwrite): 

255 f = self.BufferWrite.open(full_css, text=True, encoding=encoding) 

256 f.write(css) 

257 if not self.BufferWrite.exists(full_mail, local=not overwrite): 

258 f = self.BufferWrite.open(full_mail, text=True, encoding=encoding) 

259 f.write(html) 

260 return [full_mail, full_css], attachments 

261 

262 def produce_table_html(self, email, location, toshow, tohighlight=None, atts=None, avoid=None): 

263 """ 

264 produces a table with the values of some fields of the message 

265 

266 @param toshow list of fields to show, if None, it considers all fields 

267 @param tohighlight list of fields to highlights 

268 @param atts list of files to append at the end of the table, 

269 list of tuple *((filename,message_id,content_id))* 

270 @param location folder where this page will be saved (for attachment) 

271 @param avoid fields to avoid 

272 @return html string 

273 """ 

274 if atts is None: 

275 atts = [] 

276 if avoid is None: 

277 avoid = [] 

278 rows = [] 

279 rows.append('<div class="{0}">'.format(self._style_table)) 

280 rows.append('<table border="1">') 

281 rows.append("<thead><tr><th>key</th><th>value</th></tr></thead>") 

282 for tu in sorted(email.items()): 

283 if toshow is not None and tu[0] not in toshow: 

284 continue 

285 if tu[0] in avoid: 

286 continue 

287 

288 tu = (tu[0], email.decode_header(tu[0], tu[1])) 

289 

290 if tohighlight and tu[0] in tohighlight: 

291 format = '<tr class="%s"><th>{0}</th><td>{1}</td></tr>' % self._style_highlight 

292 else: 

293 format = "<tr><th>{0}</th><td>{1}</td></tr>" 

294 rows.append(format.format( 

295 tu[0].replace("<", "&lt;").replace(">", "&gt;"), 

296 tu[1].replace("<", "&lt;").replace(">", "&gt;"))) 

297 

298 for i, a in enumerate(atts): 

299 filename, _, cid = a 

300 rows.append('<tr><td>{0}</td><td><a href="{1}">{2}</a>{3}</td></tr>'.format( 

301 "attachment %d" % (i + 1), 

302 os.path.relpath( 

303 filename, location) if location else filename, 

304 os.path.split(filename)[-1], 

305 "id={0}".format(cid) if cid else "")) 

306 

307 rows.append("</table>") 

308 rows.append("<br /></div>") 

309 return "\n".join(rows) 

310 

311 def process_body_html(self, location, body, atts): 

312 """ 

313 replaces link to images included in the mail body:: 

314 

315 <img name="14a318e16161c62a_14a31789f7a34aae_null" 

316 title="pastedImage.png" 

317 src="cid:1146aa0a-244a-440e-8ea5-7b272c94f89a" 

318 height="153.02644466209597" 

319 width="560"> 

320 

321 @param location location where the HTML body will be saved 

322 @param body html body 

323 @param atts attachements (filename, message id, content id) 

324 @return modified body html 

325 """ 

326 if atts: 

327 for filename, _, cid in atts: 

328 if cid is None: 

329 continue 

330 pattern = 'src="cid:{0}"'.format(cid.strip("<>")) 

331 exp = re.compile('({0})'.format(pattern)) 

332 fall = exp.findall(body) 

333 if len(fall) > 0: 

334 relf = os.path.relpath(filename, location) 

335 link = 'src="{0}"'.format(relf) 

336 body = body.replace(pattern, link) 

337 return body