comparison templates/html_report.py @ 0:7db7ecc78ad6 draft

Uploaded
author damion
date Mon, 02 Mar 2015 20:46:00 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:7db7ecc78ad6
1 import os.path
2 import time
3 import csv
4 import collections
5 import re
6
7 HTML_REPORT_HEADER_FILE = 'html_report_header.html'
8
9 class HTMLReport(object):
10
11 """ This receives and sets up the general meta-data fields available to the html rendering engine
12
13 """
14 def __init__(self, tagGroup, options, query_stats = []):
15
16 self.columns = tagGroup.columns
17 self.display_columns = [field for field in self.columns if field['group']=='column']
18 self.row_limit = options.row_limit
19 self.section_bins = {}
20 self.todo = collections.deque([]) # stack of things to do
21 self.query_stats = query_stats
22 self.empty_queries = [query['id'] for query in query_stats if query['filtered_rows'] == 0]
23 self.initialized = False
24 self.errorNotice = ''
25
26 # These items are available for display in html generation via dictionary string replacement: [string ... %(filters)s ...] % self.lookup
27 self.lookup = {
28 'depth': 0,
29 'filters': 'Filters: ' + options.filters_HTML if len(options.filters_HTML) else '',
30 'timestamp': time.strftime('%Y/%m/%d'),
31 'visible_limit': 20,
32 'column_count': str(len([field for field in self.columns if field['group']=='column'])),
33 'table_rows':0,
34 'select_row':0,
35 'row_limit': self.row_limit,
36 'label':'',
37 'value':'',
38 'link':'',
39 'cssClass':'',
40 'table_header': self._tableHeader(),
41 'section_bins':'',
42 'section_counter':1
43 }
44
45 self.initialized = True
46
47 #else:
48 # add error notice for non-initialized template:
49 # self.errorNotice = '<div style="width:400px;margin:auto"><h3>This HTML Report could not be initialized ...</h3></div>'
50
51
52 """
53 _processTagStack()
54 In the production of html, start tags and template bits are added to the outgoing html when designated section columns of data change value. the self.todo stack keeps track of all the tag closings that have to occur when a section or table section comes to an end (and new one begins or end of document occurs).
55
56 This dynamically executes any functions listed in stack that are greater than given tag depth.
57 Generally the functions return html closing content.
58
59 @param depth integer >= 0
60 @uses self.todo stack of [depth, function_name] items
61 """
62 def _processTagStack(self, depth = 0):
63 html = ''
64 while len(self.todo) and self.todo[0][0] >= depth:
65 html += getattr(self, self.todo.popleft()[1] )()
66 return html
67
68
69
70 ############################### HTML REPORT RENDERING ##############################
71 """ render() produces the html. Takes in tabular data + metainformation about that file, and iterates through rows. This approach depends on detecting changes in stated report section columns and table section columns, and triggers appropriate section start and end, and table / table section start and end tags.
72
73 @param in_file string Full file path
74 @param out_html_file string Full output html data file path to write to.
75 """
76 def render (self, in_file, out_html_file):
77
78 try:
79
80 fp_in = open(in_file, "rb")
81 fp_out = open(out_html_file, 'w')
82
83 fp_out.write( self._header(HTML_REPORT_HEADER_FILE) )
84
85 if self.initialized:
86
87 fp_out.write( self._bodyStart() )
88 self.todo.appendleft([0,'_bodyEnd'])
89
90
91 reader = csv.reader(fp_in, delimiter="\t")
92
93 for row in reader:
94
95 html = ''
96 self.rowdata = []
97 row_bins = []
98 section_reset = False
99
100 for (idx, field) in enumerate(self.columns):
101
102 value = field['value'] = row[idx]
103 depth = idx + 1
104
105 # If a bin is mentioned on this row, its put into self.selection_bins.
106 if field['type'] == 'bin' and value != '':
107 row_bins.append(value)
108 if not value in self.section_bins:
109 self.section_bins[value] = field['label']
110
111 grouping = field['group']
112 # Section or table grouping here:
113 if grouping == 'section' or grouping == 'table':
114
115 # Check to see if a new section or table section is triggered by change in field's value:
116 if section_reset or (not 'valueOld' in field) or value != field['valueOld']:
117
118 self.lookup['value'] = value
119 self.lookup['label'] = field['label']
120
121 html += self._processTagStack(depth)
122
123 if grouping == 'section':
124 section_reset = True
125 self.lookup['section_depth'] = depth
126 self.lookup['section_counter'] += 1
127 self.lookup['table_rows'] = 0
128 self.section_bins = {}
129
130 html += self._sectionStart()
131
132 html += self._sectionFormStart()
133 self.todo.appendleft([depth,'_sectionFormEnd'])
134
135 self.todo.appendleft([depth,'_sectionEnd'])
136
137
138 elif grouping == 'table':
139
140 lastToDo = self.todo[0]
141 if lastToDo[1] == '_sectionEnd':
142 html += self._tableStart()
143 self.todo.appendleft([lastToDo[0]+1,'_tableEnd'])
144
145 html += self._tbodyHeader() + self._tbodyStart()
146 self.todo.appendleft([lastToDo[0]+2,'_tbodyEnd'])
147
148 field['valueOld'] = value
149
150 else:
151
152 if grouping == 'column': self.rowdata.append(row[idx])
153
154 lastToDo = self.todo[0]
155 # No table level, instead going right from section to column field:
156 if lastToDo[1] == '_sectionEnd':
157 html += self._tableStart() + self._tbodyStart()
158 self.todo.appendleft([lastToDo[0]+1,'_tableEnd'])
159 self.todo.appendleft([lastToDo[0]+2,'_tbodyEnd'])
160
161 self.lookup['row_bins'] = ",".join(row_bins)
162 fp_out.write(html)
163 self.lookup['table_rows'] += 1
164 # Now output table row of data:
165 fp_out.write( self._tableRow() )
166
167
168 #Not initialized here, so just write created error notice.
169 else:
170
171 fp_out.write('<body><h3>' + self.errorNotice + '</h3></body></html>')
172
173 fp_out.write( self._processTagStack() )
174
175 except IOError as e:
176 print 'Operation failed: %s' % e.strerror
177
178 fp_in.close()
179 fp_out.close()
180
181
182 ############################### HTML REPORT PART TEMPLATES ##############################
183 def _header(self, filename):
184
185 with open(os.path.join(os.path.dirname(__file__), filename), "r") as fphtml:
186 data = fphtml.read()
187
188 return data
189
190
191 def _bodyStart(self):
192 # The form enables the creation of a dataset from selected entries. It passes selections (based on a certain column's value) to the "Select tabular rows" tool, which then creates a dataset from the selected rows.
193 html = """
194 """
195 if len(self.empty_queries):
196 qnames = ''
197 for name in self.empty_queries: qnames += '<li>' + name + '</li>\n'
198 html += """
199 <div class="headerMessage">The following queries yielded 0 results (check filters):
200 <ul>
201 %s
202 </ul>
203 </div>""" % qnames
204
205 return html % self.lookup
206
207 # Repeated for each grouped section table display
208 def _sectionStart(self):
209 self.lookup['select_row'] +=1
210 return """
211 <div class="section section_depth%(section_depth)s">
212 <div class="section_title">%(label)s: %(value)s</div>
213 """ % self.lookup
214
215
216
217 def _sectionFormStart (self):
218 # This sets up the selection form #../../../tool_runner/index
219 return ""
220
221 def _tableStart (self):
222
223 return """
224 <table class="report">
225 %(table_header)s
226 """ % self.lookup
227
228
229 def _tableHeader(self):
230
231 colTags = '' # Style numeric fields
232 thTags = ''
233
234 for field in self.columns:
235 if field['group'] == 'column':
236 colTags += ('<col />' if field['type'] == 'text' else '<col class="numeric" />')
237 thTags += '<th>' + field['label'] + '</th>'
238
239 return """
240 <colgroup>
241 %s
242 </colgroup>
243 <thead class="top">
244 <tr>%s</tr>
245 </thead>""" % (colTags, thTags)
246
247 def _tbodyHeader (self):
248 if self.lookup['value'] == '': self.lookup['value'] = '(no match)'
249 return """
250 <thead class="inside">
251 <tr>
252 <th colspan="%(column_count)s">%(label)s: %(value)s</th>
253 </tr>
254 </thead>""" % self.lookup
255
256 def _tbodyStart (self):
257 return """
258 <tbody>""" % self.lookup
259
260
261 def _tableRow(self):
262 self.lookup['select_row'] +=1
263
264 tdTags = ''
265 for (col, field) in enumerate(self.display_columns):
266 value = self.rowdata[col]
267 self.lookup['value'] = value
268 self.lookup['cssClass'] = ' class="numeric"' if field['type'] == 'numeric' else ''
269 accessionID = re.search(r'[a-z]+[0-9]+(.[0-9]+)*' ,value, re.I)
270 if (accessionID) :
271 self.lookup['link'] = '<a href="https://google.ca/#q=%s+gene" target="search">%s</a>' % (accessionID.group(), value)
272 else:
273 self.lookup['link'] = value
274 # First column optionally gets bin indicator as well as row checkbox selector
275 if (col == 0):
276 tdTags += '<td%(cssClass)s>%(link)s<span class="super">%(row_bins)s</span></td>' % self.lookup
277 else:
278 tdTags += '<td%(cssClass)s>%(value)s</td>' % self.lookup
279
280 return """\n\t\t\t<tr>%s</tr>""" % tdTags
281
282 def _tbodyEnd (self):
283 return """
284 </tbody>"""
285
286 def _tableEnd (self):
287 if len(self.section_bins):
288 bins = []
289 for key in sorted(self.section_bins):
290 bins.append( '<span class="super">(%s)</span>%s' % (key, self.section_bins[key]) )
291 self.lookup['section_bins'] = 'Bins: ' + ', '.join(bins)
292 else:
293 self.lookup['section_bins'] = ''
294
295 return """
296 <tfoot>
297 <tr>
298 <td colspan="%(column_count)s">
299 <div class="footerCenter">
300 %(filters)s.
301 </div>
302 <div class="footerLeft">
303 <span class="rowViewer0"></span> %(table_rows)s results.
304 <span class="rowViewer1 nonprintable"></span>
305 %(section_bins)s
306 </div>
307 <div class="footerRight">
308 Report produced on %(timestamp)s
309 </div>
310
311 </td>
312 </tr>
313 </tfoot>
314 </table>""" % self.lookup
315
316 def _sectionFormEnd (self):
317 return """
318
319 """
320
321 def _sectionEnd (self):
322 return """
323 </div>"""
324
325
326 def _bodyEnd (self):
327
328 return """\n\t</body>\n</html>"""
329