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 &lt; 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                #     &lt;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 '&lt;' in output:
127                    output = output.replace('&lt;', '<')
128
129                if '&gt;' in output:
130                    output = output.replace('&gt;', '>')
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)