| 1 |
""" Report renderer for Excel .xls format """ |
|---|
| 2 |
|
|---|
| 3 |
from trac.core import * |
|---|
| 4 |
from trac.ticket.report import ITicketReportRenderer |
|---|
| 5 |
from trac.ticket.web_ui import TicketModule |
|---|
| 6 |
from trac.ticket.query import QueryModule |
|---|
| 7 |
from pyExcelerator import * |
|---|
| 8 |
from datetime import datetime |
|---|
| 9 |
import copy |
|---|
| 10 |
import types |
|---|
| 11 |
|
|---|
| 12 |
class XlsDoc(CompoundDoc.XlsDoc): |
|---|
| 13 |
def get(self, stream): |
|---|
| 14 |
padding = '\x00' * (0x1000 - (len(stream) % 0x1000)) |
|---|
| 15 |
self.book_stream_len = len(stream) + len(padding) |
|---|
| 16 |
self.__build_directory() |
|---|
| 17 |
self.__build_sat() |
|---|
| 18 |
self.__build_header() |
|---|
| 19 |
return '%s%s%s%s%s%s%s' % ( |
|---|
| 20 |
self.header, |
|---|
| 21 |
self.packed_MSAT_1st, |
|---|
| 22 |
stream, |
|---|
| 23 |
padding, |
|---|
| 24 |
self.packed_MSAT_2nd, |
|---|
| 25 |
self.packed_SAT, |
|---|
| 26 |
self.dir_stream) |
|---|
| 27 |
|
|---|
| 28 |
class Workbook(Workbook): |
|---|
| 29 |
def get(self): |
|---|
| 30 |
doc = XlsDoc() |
|---|
| 31 |
return doc.get(self.get_biff_data()) |
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
class ReportToExcel(Component): |
|---|
| 35 |
implements (ITicketReportRenderer) |
|---|
| 36 |
|
|---|
| 37 |
# ITicketRerportRenderer |
|---|
| 38 |
def get_report_format(self): |
|---|
| 39 |
return 'xls' |
|---|
| 40 |
|
|---|
| 41 |
def get_report_mimetype(self): |
|---|
| 42 |
return 'application/vnd.ms-excel' |
|---|
| 43 |
|
|---|
| 44 |
def get_report_linkname(self): |
|---|
| 45 |
return 'Excel' |
|---|
| 46 |
|
|---|
| 47 |
def get_report_linkclass(self): |
|---|
| 48 |
return None #'xls' |
|---|
| 49 |
|
|---|
| 50 |
def render(self, req, cols, rows): |
|---|
| 51 |
|
|---|
| 52 |
req.send_response(200) |
|---|
| 53 |
req.send_header('Content-Type', self.get_report_mimetype()) |
|---|
| 54 |
req.send_header('Content-Disposition', |
|---|
| 55 |
'filename=Report%s.xls' % req.hdf['report.id']) |
|---|
| 56 |
req.end_headers() |
|---|
| 57 |
|
|---|
| 58 |
wb = Workbook() |
|---|
| 59 |
sheetname = "%s - %s" % (req.hdf['report.title'], |
|---|
| 60 |
req.hdf['project.name']) |
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
ws = wb.add_sheet(self.convertSheetName(sheetname)) |
|---|
| 64 |
|
|---|
| 65 |
ws.panes_frozen = True |
|---|
| 66 |
ro = 1 |
|---|
| 67 |
ws.horz_split_pos = ro |
|---|
| 68 |
|
|---|
| 69 |
|
|---|
| 70 |
font0 = Font() |
|---|
| 71 |
font0.charset = font0.CHARSET_SYS_DEFAULT |
|---|
| 72 |
font0.name = 'MS UI Gothic' |
|---|
| 73 |
font1 = copy.copy(font0) |
|---|
| 74 |
font1.bold = True |
|---|
| 75 |
font2 = copy.copy(font0) |
|---|
| 76 |
font2.height = 0x00A0 |
|---|
| 77 |
align0 = Alignment() |
|---|
| 78 |
align1 = copy.copy(align0) |
|---|
| 79 |
align1.vert = align1.VERT_TOP |
|---|
| 80 |
align2 = copy.copy(align1) |
|---|
| 81 |
align2.wrap = align2.WRAP_AT_RIGHT |
|---|
| 82 |
|
|---|
| 83 |
self.style0 = XFStyle() |
|---|
| 84 |
self.style0.font = font0 |
|---|
| 85 |
self.style0.alignment = align1 |
|---|
| 86 |
self.style0.num_format_str = 'general' |
|---|
| 87 |
self.style_colheader = copy.copy(self.style0) |
|---|
| 88 |
self.style_colheader.num_format_str = '@' |
|---|
| 89 |
self.style_colheader.font = font1 |
|---|
| 90 |
self.style_num = copy.copy(self.style0) |
|---|
| 91 |
self.style_str = copy.copy(self.style0) |
|---|
| 92 |
self.style_str.num_format_str = '@' |
|---|
| 93 |
self.style_wrap_str = copy.copy(self.style0) |
|---|
| 94 |
self.style_wrap_str.alignment = align2 |
|---|
| 95 |
self.style_wrap_str.font = font2 |
|---|
| 96 |
self.style_date = copy.copy(self.style0) |
|---|
| 97 |
self.style_date.num_format_str = 'yyyy/mm/dd' |
|---|
| 98 |
|
|---|
| 99 |
for col, cx in map(lambda x, y: [x, y], cols, range(len(cols))): |
|---|
| 100 |
|
|---|
| 101 |
name = str(col).replace('_','') |
|---|
| 102 |
ws.write(ro-1, cx, name.decode('utf-8'), self.style_colheader) |
|---|
| 103 |
|
|---|
| 104 |
conv = self.convertComments |
|---|
| 105 |
|
|---|
| 106 |
style = self.style_str |
|---|
| 107 |
|
|---|
| 108 |
if name in ['time', 'date','changetime', 'created', 'modified', |
|---|
| 109 |
'hora', 'fecha','cambio' ,'creado' ,'modificado']: |
|---|
| 110 |
ws.col(cx).width = 0xb00 |
|---|
| 111 |
conv = self.convertTimeStamp |
|---|
| 112 |
style = self.style_date |
|---|
| 113 |
|
|---|
| 114 |
elif name in ['summary','resumen']: |
|---|
| 115 |
ws.col(cx).width = 0x1a00 |
|---|
| 116 |
style = self.style_wrap_str |
|---|
| 117 |
|
|---|
| 118 |
elif name in ['description','descripcion']: |
|---|
| 119 |
ws.col(cx).width = 0x7000 |
|---|
| 120 |
style = self.style_wrap_str |
|---|
| 121 |
|
|---|
| 122 |
elif name in ['color', 'ticket', 'id']: |
|---|
| 123 |
if name in ['color']: |
|---|
| 124 |
ws.col(cx).hidden = 1 |
|---|
| 125 |
conv = self.convertInteger |
|---|
| 126 |
style = self.style_num |
|---|
| 127 |
elif name in ['style']: |
|---|
| 128 |
ws.col(cx).hidden = 1 |
|---|
| 129 |
elif name == "component": |
|---|
| 130 |
ws.col(cx).width = 0x1a00 |
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
for value, rx in map(lambda x, y: [conv(x[cx]), ro + y], \ |
|---|
| 134 |
rows, range(len(rows))): |
|---|
| 135 |
if type(value) is int: |
|---|
| 136 |
ws.write(rx, cx, value, self.style_num) |
|---|
| 137 |
elif type(value) is datetime: |
|---|
| 138 |
ws.write(rx, cx, value, self.style_date) |
|---|
| 139 |
elif type(value) is types.NoneType: |
|---|
| 140 |
ws.write(rx, cx, '-',self.style_str) |
|---|
| 141 |
else: |
|---|
| 142 |
ws.write(rx, cx, value, style) |
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
|
|---|
| 146 |
|
|---|
| 147 |
req.write(wb.get()) |
|---|
| 148 |
|
|---|
| 149 |
def convertComments(self, x): |
|---|
| 150 |
try: |
|---|
| 151 |
if type(x) is types.NoneType: |
|---|
| 152 |
return x |
|---|
| 153 |
else: |
|---|
| 154 |
return str(x).replace('\r','').rstrip('\r\n').decode('utf-8') |
|---|
| 155 |
except: |
|---|
| 156 |
return x.replace('\r','').rstrip('\r\n') |
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 |
def convertTimeStamp(self,timestamp): |
|---|
| 160 |
|
|---|
| 161 |
try: |
|---|
| 162 |
return datetime.fromtimestamp(float(timestamp)) |
|---|
| 163 |
except: |
|---|
| 164 |
return timestamp |
|---|
| 165 |
|
|---|
| 166 |
def convertInteger(self,value): |
|---|
| 167 |
try: |
|---|
| 168 |
return int(value) |
|---|
| 169 |
except: |
|---|
| 170 |
return value |
|---|
| 171 |
|
|---|
| 172 |
def convertSheetName(self, sheetname): |
|---|
| 173 |
|
|---|
| 174 |
sheetname = sheetname.replace('/','-') |
|---|
| 175 |
|
|---|
| 176 |
try: |
|---|
| 177 |
return sheetname.decode('utf-8') |
|---|
| 178 |
except: |
|---|
| 179 |
return sheetname |
|---|
| 180 |
|
|---|
| 181 |
|
|---|
| 182 |
|
|---|
| 183 |
|
|---|