1#!/usr/bin/env python3
2
3"""HTML canned text.
4
5Special operations:
6
7<import>`filename`</import>: Includes the contents of `filename` into the report.
8<query><db>TMREP</db><sql>select count(*) from commands where mnemonic='LOX003' and exec_time
9 between :sensing_time and :sensing_stop</sql></query>
10
11"""
12
13import os
14import re
15
16from lxml import etree
17
18from django.template import Template
19from django.template import Context
20
21from chart.reports.widget import Widget
22from chart.project import settings
23from chart.common.exceptions import ConfigError
24from chart.common.xml import parsechildstr
25from chart.common.xml import parse_xml
26
27
28class HTMLWidget(Widget):
29 """Allow raw HTML code to be placed in a report template.
30
31 Special tags:
32
33 * <import>/tcc1/home/myusername/myfilename.html</import>
34 * <query><db>tchist</db><sql>select 0 from dual</sql></query>
35
36 """
37
38 name = 'html'
39
40 image = 'widgets/html.png'
41 thumbnail = 'widgets/html_sm.png'
42
43 raw_options = True
44
45 url = 'http://tctrac/projects/chart/wiki/HTMLWidget'
46
47 document_options = {}
48
49 def html(self, document):
50 """Render ourselves."""
51 html = document.html
52 dc = document.config
53
54 context = Context({'scid': document.config.get('scid'),
55 'sensing_start': document.config.get('sensing_start'),
56 'sensing_stop': document.config.get('sensing_stop')})
57
58 raw_text = etree.tostring(self.elem).decode()
59
60 if 'start_orbit' in raw_text:
61 context['start_orbit'] = dc['sid'].orbit.find_orbit(dc['sensing_start'])
62
63 if 'stop_orbit' in raw_text:
64 context['stop_orbit'] = dc['sid'].orbit.find_orbit(dc['sensing_stop'])
65
66 # take the parsed XML and convert it back to a single text string for
67 # Django template parsing
68 preloads = ('{%load link%}',
69 '{%load modulo%}')
70 proc_text = Template(''.join(preloads) + raw_text).render(context)
71 # convert the Django template output back to XML for rendering
72 elem = parse_xml(proc_text)
73
74 # lxml likes to insert namespace declarations when exporting elements
75 strip_namespace = re.compile(r' xmlns:xsi="[^"]*"')
76
77 # expansions = {'scid': dc.get('scid')}
78
79 # command = re.compile(r'{(?P<command>\w+):(?P<params>[^}]*}')
80
81 for query_elem in elem.findall('.//query'):
82 # for every <query> element, perform the lookup replace the original query
83 # with a <span> containing the result of the query.
84 result = self.query(query_elem, dc=dc)
85 query_elem.clear()
86 query_elem.tag = 'span'
87 query_elem.text = result
88
89 for child in elem:
90 # print('Proc elem ' + child.tag)
91 if child.tag == 'class':
92 # dont write <class>HTML</class> to document
93 continue
94
95 if child.tag == 'cond':
96 # dont write <cond><scid>M0X</scid></cond> to document
97 continue
98
99 elif child.tag == 'import':
100 html.write(open(os.path.expanduser(child.text.strip()), 'r').read())
101
102 else:
103 # this is documented but doesn't work
104 # etree.strip_namespaces(child, "http://www.w3.org/2001/XMLSchema-instance")
105 # print('HTML text ', child.tag, child.text)
106 # the "method='text'" clause means that glyphs like < in the report template
107 # XML get expanded to < in the HTML allowing mis-matched tags to be inserted
108 # in HTML widgets
109 # output = strip_namespace.sub('', etree.tostring(child)).format(**expansions)
110 temp = etree.tostring(child).decode()
111 # temp = etree.tostring(child, method='text')
112 output = strip_namespace.sub('', temp) # .format(**expansions)
113 # child.text = child.text.format(**expansions)
114
115 # These are special hacks to allow things like:
116
117 # <widget>
118 # <class>HTML</class>
119 # <a href="#" class="show_hide">Show/hide</a>
120 # <div class="slidingDiv">
121 # </widget>
122
123 # to work. There may be a better way to do it like a special string.encode(x)
124 # option but I'm confused enough already.
125
126 if '<' in output:
127 output = output.replace('<', '<')
128
129 if '>' in output:
130 output = output.replace('>', '>')
131
132 html.write(output)
133 # print('WRITING ' + etree.tostring(child,method='text') + ' DONE')
134
135 def query(self, elem, dc):
136 """Query an AUX database returning string to be inserted into report.
137 The query must return a single row.
138 """
139
140 from chart.db.aux import mysql_query
141 source = parsechildstr(elem, 'db').lower()
142
143 if source not in ('tmrep', 'tchist', 'events'):
144 raise ConfigError('Unknown <db>: {source}'.format(source=source))
145
146 source += '_' + dc['scid'].lower()
147
148 # original full SQL statement
149 sql = parsechildstr(elem, 'sql')
150
151 # search for bind variables referenced in the SQL
152 var_finder = re.compile(r':(\w+)')
153
154 # build a list of required bind variables
155 bindvars = []
156
157 # search the user SQL for references to bind variables (:var)
158 for var in var_finder.finditer(sql):
159 var = var.groups(0)[0]
160 # test for the bind variables we can handle
161 if var == 'sensing_start':
162 bindvars.append(dc['sensing_start'])
163
164 elif var == 'sensing_stop':
165 bindvars.append(dc['sensing_stop'])
166
167 else:
168 raise ConfigError('Unknown bind variable :{var}'.format(var=var))
169
170 sql = re.sub(r':([a-zA-Z0-9_]+)', r'%s', sql)
171
172 row = mysql_query(source, sql, *bindvars).fetchone()
173
174 if row is None:
175 return '<none>'
176
177 else:
178 return ', '.join(str(i) for i in row)