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

Uploaded
author damion
date Mon, 02 Mar 2015 20:46:00 -0500
parents
children 812de0e282bd
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 import common
7
8 HTML_REPORT_HEADER_FILE = 'html_report_header.html'
9
10 class HTMLReport(object):
11
12 """ This receives and sets up the general meta-data fields available to the html rendering engine
13
14 """
15 def __init__(self, tagGroup, options, query_stats = []):
16
17 self.columns = tagGroup.columns
18 self.display_columns = [field for field in self.columns if field['group']=='column']
19 self.row_limit = options.row_limit
20 self.section_bins = {}
21 self.todo = collections.deque([]) # stack of things to do
22 self.query_stats = query_stats
23 self.empty_queries = [query['id'] for query in query_stats if query['filtered_rows'] == 0]
24 self.initialized = False
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 if hasattr(options,'dataset_selection_id'):
46 self.lookup['dataset_selection_id'] = options.dataset_selection_id
47 self.initialized = True
48
49 else:
50 self.lookup['dataset_selection_id'] = 0
51 self.errorNotice = '<div style="width:400px;margin:auto"><h3>This Selectable HTML Report needs the "Basic Report Field Output" to include the qseq and sseq sequence fields (aligned part of query sequence and subject sequence). Add these fields or try a selection that has more than 12 columns.</h3></div>'
52
53 """
54 _processTagStack()
55 In the production of html, start tags and template bits are added to the outgoing html when
56 designated section columns of data change value. the self.todo stack keeps track of all the
57 tag closings that have to occur when a section or table section comes to an end (and new one
58 begins or end of document occurs).
59
60 This dynamically executes any functions listed in stack that are greater than given tag depth.
61 Generally the functions return html closing content.
62
63 @param depth integer >= 0
64 @uses self.todo stack of [depth, function_name] items
65 """
66 def _processTagStack(self, depth = 0):
67 html = ''
68 while len(self.todo) and self.todo[0][0] >= depth:
69 html += getattr(self, self.todo.popleft()[1] )()
70 return html
71
72
73
74 ############################### HTML REPORT RENDERING ##############################
75 """ render() produces the html. Takes in tabular data + metainformation about that file,
76 and iterates through rows. This approach depends on detecting changes in stated report
77 section columns and table section columns, and triggers appropriate section start and end,
78 and table / table section start and end tags.
79
80 @param in_file string Full file path
81 @param out_html_file string Full output html data file path to write to.
82 """
83 def render (self, in_file, out_html_file):
84
85 try:
86
87 fp_in = open(in_file, "rb")
88 fp_out = open(out_html_file, 'w')
89
90 fp_out.write( self._header(HTML_REPORT_HEADER_FILE) )
91
92 if self.initialized:
93
94 fp_out.write( self._bodyStart() )
95 self.todo.appendleft([0,'_bodyEnd'])
96
97 reader = csv.reader(fp_in, delimiter="\t")
98
99 for row in reader:
100
101 html = ''
102 self.rowdata = []
103 row_bins = []
104 section_reset = False
105
106 for (idx, field) in enumerate(self.columns):
107
108 value = field['value'] = row[idx]
109 depth = idx + 1
110
111 # If a bin is mentioned on this row, its put into self.selection_bins.
112 if field['type'] == 'bin' and value != '':
113 row_bins.append(value)
114 if not value in self.section_bins:
115 self.section_bins[value] = field['label']
116
117 grouping = field['group']
118 # Section or table grouping here:
119 if grouping == 'section' or grouping == 'table':
120
121 # Check to see if a new section or table section is triggered by change in field's value:
122 if section_reset or (not 'valueOld' in field) or value != field['valueOld']:
123
124 self.lookup['value'] = value
125 self.lookup['label'] = field['label']
126
127 html += self._processTagStack(depth)
128
129 if grouping == 'section':
130 section_reset = True
131 self.lookup['section_depth'] = depth
132 self.lookup['section_counter'] += 1
133 self.lookup['table_rows'] = 0
134 self.section_bins = {}
135
136 html += self._sectionStart()
137
138 html += self._sectionFormStart()
139 self.todo.appendleft([depth,'_sectionFormEnd'])
140
141 self.todo.appendleft([depth,'_sectionEnd'])
142
143 elif grouping == 'table':
144
145 lastToDo = self.todo[0]
146 if lastToDo[1] == '_sectionEnd': #Just started a section
147 html += self._tableStart()
148 self.todo.appendleft([lastToDo[0]+1,'_tableEnd'])
149
150 html += self._tbodyHeader() + self._tbodyStart()
151 self.todo.appendleft([lastToDo[0]+2,'_tbodyEnd'])
152
153 field['valueOld'] = value
154
155 else:
156
157 if grouping == 'column': self.rowdata.append(row[idx])
158
159 lastToDo = self.todo[0]
160 # No table level, instead going right from section to column field:
161 if lastToDo[1] == '_sectionEnd':
162 html += self._tableStart() + self._tbodyStart()
163 self.todo.appendleft([lastToDo[0]+1,'_tableEnd'])
164 self.todo.appendleft([lastToDo[0]+2,'_tbodyEnd'])
165
166 self.lookup['row_bins'] = ",".join(row_bins)
167
168 fp_out.write(html)
169
170 self.lookup['table_rows'] += 1
171
172 # Now output table row of data:
173 fp_out.write( self._tableRow() )
174
175
176 #Not initialized here, so just write created error notice.
177 else:
178
179 fp_out.write('<h3>' + self.errorNotice + '</body></html>')
180
181 fp_out.write( self._processTagStack() )
182
183 except IOError as e:
184 print 'Operation failed: %s' % e.strerror
185
186 fp_in.close()
187 fp_out.close()
188
189
190 ############################### HTML REPORT PART TEMPLATES ##############################
191 def _header(self, filename):
192
193 with open(os.path.join(os.path.dirname(__file__), filename), "r") as fphtml:
194 data = fphtml.read()
195
196 return data
197
198
199 def _bodyStart(self):
200 # 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.
201 with open(os.path.join(os.path.dirname(__file__), "html_selectable_report_tool_state.txt"), "r") as form_code_file:
202 self.lookup['form_state'] = form_code_file.readline().strip()
203
204 html = """
205 <script>
206 // Toggle checkbox inputs of given table according to state of checkbox
207 function toggleInputs(mycheckbox, table) {
208 var checkboxes = table.getElementsByTagName('INPUT');
209 // Loop skips first toggler checkbox
210 for (var ptr = 1; ptr < checkboxes.length; ptr ++ ) {
211 checkboxes[ptr].checked = mycheckbox.checked;
212 }
213 return true; // prevents click processing
214 }
215
216 //Drops hidden section id from form submission when no section items were selected
217 function formCheck() {
218 var tables = document.getElementsByTagName('TABLE');
219 for (var ptr = 0; ptr < tables.length; ptr ++) {
220 var inputs = tables[ptr].getElementsByTagName('INPUT');
221 var selected = 0;
222 // 1st item in table is select-all toggle
223 for (var ptr2 = 1; ptr2 < inputs.length; ptr2 ++) {
224 if (inputs[ptr2].getAttribute('name') == 'select' && inputs[ptr2].checked) selected ++;
225 }
226 if (selected==0) {
227 id = tables[ptr].getAttribute("id");
228 secInput = document.getElementById(id + '_start');
229 secInput.disabled = true;
230 secInput.setAttribute('disabled','disabled');
231 secInput.value='';
232 }
233
234 }
235 return true;
236 }
237 </script>
238
239 <form id="tool_form" name="tool_form" action="../../../tool_runner" target="galaxy_main" method="post" enctype="application/x-www-form-urlencoded">
240 <input type="hidden" name="refresh" value="refresh"/>
241 <input type="hidden" name="tool_id" value="bccdcSelectSubsets"/>
242 <input type="hidden" name="tool_state" value="%(form_state)s">
243 <input type="hidden" name="input" value="%(dataset_selection_id)s"/>
244 <input type="hidden" name="incl_excl" value="1"/>
245 """ % self.lookup
246
247 if len(self.empty_queries):
248 qnames = ''
249 for name in self.empty_queries: qnames += '<li>' + name + '</li>\n'
250 html += """
251 <div class="headerMessage">The following %s queries yielded 0 results (check filters):
252 <ul>
253 %s
254 </ul>
255 </div>""" % (len(self.empty_queries), qnames)
256
257 return html % self.lookup
258
259 # Repeated for each grouped section table display
260 def _sectionStart(self):
261 self.lookup['select_row'] +=1
262 return """
263 <div class="section section_depth%(section_depth)s">
264 <div class="section_title">%(label)s: %(value)s</div>
265 """ % self.lookup
266
267
268
269 def _sectionFormStart (self):
270 # This sets up the selection form with the minimal necessary parameters #
271 return """
272 <input type="checkbox" name="select" value="%(select_row)s" id="secId_%(select_row)s_start" checked="checked" style="display:none">
273 <input type="submit" class="btn btn-primary nonprintable" name="runtool_btn" value="Submit" onclick="formCheck()">
274 """ % self.lookup
275
276
277 def _tableStart (self):
278
279 return """
280 <div class="checkUncheckAllPlaceholder"></div>
281 <table class="report" id="secId_%(select_row)s">
282 %(table_header)s
283 """ % self.lookup
284
285
286 def _tableHeader(self):
287
288 colTags = ''
289 thTags = ''
290 firstColumnFlag = True
291 # Style numeric fields
292 for field in self.columns:
293 if field['group'] == 'column':
294 colTags += ('<col />' if field['type'] == 'text' else '<col class="numeric" />')
295 if firstColumnFlag == True:
296 thTags += '''<th>
297 <input type="checkbox" class="sectionCheckbox nonprintable" value="1"/>
298 ''' + field['label'] + '</th>'
299 firstColumnFlag = False
300 else:
301 thTags += '<th>' + field['label'] + '</th>'
302
303 return """
304 <colgroup>
305 %s
306 </colgroup>
307 <thead class="top">
308 <tr>%s</tr>
309 </thead>""" % (colTags, thTags)
310
311 def _tbodyHeader (self):
312 if self.lookup['value'] == '': self.lookup['value'] = '(no match)'
313 return """
314 <thead class="inside">
315 <tr>
316 <th colspan="%(column_count)s">%(label)s: %(value)s</th>
317 </tr>
318 </thead>""" % self.lookup
319
320 def _tbodyStart (self):
321 return """
322 <tbody>""" % self.lookup
323
324
325 def _tableRow(self):
326 self.lookup['select_row'] +=1
327
328 tdTags = ''
329 for (col, field) in enumerate(self.display_columns):
330 value = self.rowdata[col]
331 self.lookup['value'] = value
332 self.lookup['cssClass'] = ' class="numeric"' if field['type'] == 'numeric' else ''
333 #See http://www.ncbi.nlm.nih.gov/books/NBK21091/table/ch18.T.refseq_accession_numbers_and_mole/?report=objectonly
334 #See http://www.ncbi.nlm.nih.gov/Sequin/acc.html
335 accessionID = re.search(r'[a-z]+[_]?[0-9]+(.[0-9]+)*' ,value, re.I)
336 if (accessionID) :
337 self.lookup['link'] = '<a href="https://google.com/#q=%s+gene" target="search">%s</a>' % (accessionID.group(), value)
338 else:
339 self.lookup['link'] = value
340 # First column optionally gets bin indicator as well as row checkbox selector
341 if (col == 0):
342 tdTags += '<td%(cssClass)s><input type="checkbox" name="select" value="%(select_row)s" />%(link)s<span class="super">%(row_bins)s</span></td>' % self.lookup
343 else:
344 tdTags += '<td%(cssClass)s>%(value)s</td>' % self.lookup
345
346 return """\n\t\t\t<tr>%s</tr>""" % tdTags
347
348
349 def _tbodyEnd (self):
350 return """
351 </tbody>"""
352
353 def _tableEnd (self):
354 if len(self.section_bins):
355 bins = []
356 for key in sorted(self.section_bins):
357 bins.append( '<span class="super">(%s)</span>%s' % (key, self.section_bins[key]) )
358 self.lookup['section_bins'] = 'Bins: ' + ', '.join(bins)
359 else:
360 self.lookup['section_bins'] = ''
361
362 return """
363 <tfoot>
364 <tr>
365 <td colspan="%(column_count)s">
366 <div class="footerCenter">
367 %(filters)s.
368 </div>
369 <div class="footerLeft">
370 <span class="rowViewer0"></span> %(table_rows)s results.
371 <span class="rowViewer1 nonprintable"></span>
372 %(section_bins)s
373 </div>
374 <div class="footerRight">
375 Report produced on %(timestamp)s
376 </div>
377
378 </td>
379 </tr>
380 </tfoot>
381 </table>""" % self.lookup
382
383 def _sectionFormEnd (self):
384 return """
385
386 """
387
388 def _sectionEnd (self):
389 return """
390
391 </div>"""
392
393
394 def _bodyEnd (self):
395
396 return """
397 </form>\n\t</body>\n</html>"""
398
399