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
17class EmailMessageRenderer(Renderer):
18 """
19 defines way to render an email
20 """
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/>`_.
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
38 @example(Template to render an email)
40 See file :mod:`email_message_style <pymmails.grabber.email_message_style>`
42 HTML template::
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}"><--</a>'.format(prev_mail) if prev_mail else '' }}
53 {{ '<a href="{0}">--></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>
64 CSS style::
66 .dataframe100l {
67 padding: 0;
68 width=75%
69 font-family: Calibri;
70 font-size: 100%;
71 cursor: pointer;
72 }
74 .dataframe100l table {
75 border-collapse: collapse;
76 text-align: left;
77 font-size: 11px;
78 }
80 .dataframe100l table td, .dataframe100l table th {
81 padding: 3px 6px;
82 }
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 }
92 .dataframe100l table tbody td {
93 color: #00496B;
94 border-left: 1px solid #E1EEF4;
95 font-size: 11px;
96 font-weight: normal;
97 }
99 .dataframe100l table tbody .alt td {
100 background: #E1EEF4;
101 color: #00496B;
102 }
104 .dataframe100l_hl td {
105 background: #FFFF00;
106 }
108 .dataframe100l_hl th {
109 background: #FFFF00;
110 }
112 .dataframe100l_hl tr {
113 background: #FFFF00;
114 }
116 .dataframe100l table tbody td:first-child {
117 border-left: none;
118 }
120 .dataframe100l table tbody tr:last-child td {
121 border-bottom: none;
122 }
124 .dataframe100l table tfoot td div {
125 border-top: 1px solid #006699;
126 background: #E1EEF4;
127 }
129 .dataframe100l table tfoot td {
130 padding: 0;
131 font-size: 11px
132 }
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 }
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 }
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 }
162 @endexample
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
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
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)
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.
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
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*::
204 {{ message.get_subject() }}
206 The value stored in *file_css* will be relative to *location*.
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
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.
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
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
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
288 tu = (tu[0], email.decode_header(tu[0], tu[1]))
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("<", "<").replace(">", ">"),
296 tu[1].replace("<", "<").replace(">", ">")))
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 ""))
307 rows.append("</table>")
308 rows.append("<br /></div>")
309 return "\n".join(rows)
311 def process_body_html(self, location, body, atts):
312 """
313 replaces link to images included in the mail body::
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">
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