0
|
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
|