# Export as HTML.py # Copyright 2004, 2005 by Brian C. Christensen # This file is part of GanttPV. # # GanttPV is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # GanttPV is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GanttPV; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Change log: # 040623 - started work on this script # 040625 - first working version of this script # 040715 - (Blake) different color for completed and partially completed projects # 040718 - simplify report heading # 041109 - convert value to string (to handle new float column type) # 041113 - added WriteValue to create a common routine to handle character output conversions needed; added export time message # 041120 - changes to convert some unicode characters; added wrapcells flag # 041203 - added support for weekly gantt charts and measurements # 041204 - fix weekly bars # 050423 - use Data.GetPeriodInfo to calculate period start & hours (to support Month & Quarter); used Data.GetCellValue for non time scale columns; Month & Quarter headings fixed # 050627 - allow override of Plan Bar Color # 050909 - Alexander - replaced tabs and line feeds # 060902 - Alex - indent subtask names # 060914 - Alex - use time-scale headers from Data (takes advantage of the better-looking headers in v0.7) # Note: The fonts used in this script are larger than in GanttPV. To reduce font size to more closely match GanttPV, change the "font-size" lines in the header strings. import os import wx import datetime import re # wrapcells = True wrapcells = False # tell export not to break lines within table cells # convert = True # convert some unicode characters convert = False # no conversion indent = " " * 3 xchar = { } if convert: cset = "windows-1257" xchar = { u'\u0105': '\xe0', u'\u010C': '\xd0', u'\u010D': '\xe8', u'\u0117': '\xeb', u'\u012F': '\xe1', u'\u0160': '\xd0', u'\u0161': '\xf0', u'\u016B': '\xfb', u'\u0173': '\xf8', u'\u017E': '\xfe', } else: cset = "iso-8859-1" if wrapcells: wrap = "" else: wrap = " nowrap " def MakeString(value): """ Use this to convert values taken from database. May need to convert fields to XML format. - Convert 'None' to an empty string - Try to convert value to string - Adjust some chars for XML """ global datetime if value == None or value == "": result = ' ' elif isinstance(value, int) or isinstance(value, float): result = str(value) else: result = "?" try: result = str(value) except UnicodeEncodeError: # fix european conversion errors result = '' for ch in value: if ord(ch) < 128: ch = chr(ord(ch)) elif ch in '><"&': ch = "&#" + chr(ord(ch)) + ";" elif xchar.has_key(ch): ch = xchar[ch] #if len(xchar[ch]) == 1: # xchar[ch] # # ch = "&#" + chr(ord(xchar[ch])) + ";" #else: # ch = xchar[ch] elif ord(ch) >= 128 and ord(ch) < 255: ch = "&#" + chr(ord(ch)) + ";" else: ch = "?" result += ch return result def WriteHTML(filename): fp = file(filename, 'w') # Create file header1 = """ """ header2 = """
  """ header3 = """  
  """ footer1 = """
    """ footer2 = """  
""" # self = the current frame rid = self.ReportID report = Data.Report[rid] rc = Data.ReportColumn rct = Data.ColumnType pid = report.get('ProjectID') # DateConv = {} # usage: index = DateConv['2004-01-01'] # DateIndex = [] # usage: date = DateIndex[1] # DateInfo = [] # dayhours, cumhours, dow = DateInfo[1] fp.write(header1) fp.write(MakeString(Data.Project[pid].get('Name'))) fp.write(' / ') fp.write(MakeString(report.get('Name') )) fp.write(header2) fp.write('

') fp.write(MakeString(report.get('Name') )) if pid != 1: fp.write(' (' + MakeString(Data.Project[pid].get('Name')) + ')') fp.write('

\r') fp.write('\r') # Extract info to display column headings # this is calculated in GanttReport.py, but is not exported. (export in next release?) columns = [] ctypes = [] coloffset = [] cwidth = [] cname1 = [] # first line of column name or (Month/Year or "") cname2 = [] # second line of (column name or "") or Day cnreps = [] # used to create column span for Month/Year month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] clist = Data.GetColumnList(self.ReportID) # list of ids of Report Column records for c in clist: # get info to decide whether this is a "timescale" column ct = rc[c].get('ColumnTypeID') if ct == None: # columns w/o types are invalid, ignore them continue type = rct[ct].get('AccessType') if (type == 's'): ctperiod, ctfield = rct[ct].get('Name').split("/") if ctfield == "Gantt": width = 24 else: width = rc[c].get('Width') or 40 cn = MakeString(rc[c].get("Label") or rct[ct].get("Label") or ctfield or "--") # if cn.count("\n"): cn = cn.replace("\n", " ") # di = Data.GetColumnDate(c, 0) repi = len(cnreps) reptop = "" for i in range(rc[c].get('Periods') or 1): header = Data.GetColumnHeader(c, i).splitlines() top, bot = header[0], header[-1] # (y, m, d) = Data.DateIndex[di].split("-") # convert index to date # gantt columns are narrower, so headers are shorter # if ctperiod in ("Day", "Week"): # if ctfield == 'Gantt': # top = month[int(m) - 1] + " " + str(y) # bot = d # else: # top = month[int(m) - 1] + " " + y[-2:] + " " + ctfield[0:3] # bot = d # elif ctperiod == "Month": # if ctfield == 'Gantt': # top = y # bot = m # else: # top = y # bot = month[int(m) - 1] # elif ctperiod == "Quarter": # q = (int(m) + 2) / 3 # if ctfield == 'Gantt': # top = y # bot = "Q" + str(q) # else: # top = y # bot = "Q" + str(q) # else: # top = "-" # bot = "-" cname2.append( bot ) cnreps.append(1) if top == reptop: # increment counter cname1.append( "" ) cnreps[repi] += 1 else: cname1.append( top ) # reptop = top # save this heading to check next one is dup # in v0.7, duplicate headers are left blank repi = len(cnreps) - 1 # remember this location columns.append( c ) ctypes.append( ct ) coloffset.append( i ) cwidth.append(width) # what is the width? # if ctperiod == "Week": # di += 7 # elif ctperiod == "Month": # di = Data.AddMonths(di, 1) # elif ctperiod == "Quarter": # di = Data.AddMonths(di, 3) # else: # default to days # di += 1 else: # create list of column headers cn = MakeString(rc[c].get("Label") or rct[ct].get("Label") or rct[ct].get("Name") or "--") # print cn cns = cn.splitlines() # split multi-line headers cns.append(" ") cname1.append( cns[0] ) cname2.append( cns[1] ) cnreps.append(1) # create lists of column pointers columns.append( c ) ctypes.append( ct ) coloffset.append( -1 ) cwidth.append((rc[c].get("Width") or 40) + 2) # what is the width? # print column headings fp.write("") for i, c in enumerate(cname1): if c == "": continue cspan = cnreps[i] if coloffset[i] == -1: rspan = ' rowspan="2" ' else: rspan = "" fp.write('") fp.write("") fp.write("") for i, c in enumerate(cname2): if coloffset[i] == -1: continue fp.write('") fp.write("\n") # extract info to build rows rtid = report.get('ReportTypeID') show = report.get('ShowHidden', False) try: rlist, rlevels = Data.GetRowLevels(self.ReportID) except AttributeError: rlist, rlevels = Data.GetRowList(self.ReportID), None for ri, rid in enumerate(rlist): rr = Data.ReportRow[rid] rtable = rr.get('TableName') tid = rr['TableID'] # was 'TaskID' -> changed to generic ID if not show: hidden = rr.get('Hidden', False) active = rtable and tid and (Data.Database[rtable][tid].get('zzStatus', 'active') == 'active') if hidden or not active: continue fp.write("") for ci, col in enumerate(columns): of = coloffset[ci] ct = Data.ColumnType[ctypes[ci]] t = ct.get('T', 'X') # either "A" or "B" ctable = Data.ReportType[rtid].get('Table' + t) at = ct.get('AccessType') if ri == 0: wid = ' width="' + str(cwidth[ci]) + '" ' else: wid = '' if of == -1: value = Data.GetCellValue(rid, col, of) #if rtable != ctable: # value = '' #elif at == 'd': # direct - value is in row # column = ct.get('Name') # value = Data.Database[rtable][tid].get(column, "") #elif at == 'i': # indirect - value in other table # it, ic = ct.get('Name').split('/') # indirect table & column # iid = Data.Database[rtable][tid].get(it+'ID') # value = Data.Database[it][iid].get(ic, "") fp.write('") else: # bar chart - decide on color of empty cell ctperiod, ctfield = ct.get('Name').split("/") if ctfield == "Gantt": # don't display a value if rtable == "Task": task = Data.Database['Task'][tid] column = Data.Database['ReportColumn'][col] es = task.get('hES', 0) # if not found don't display gantt chart ef = task.get('hEF', 0) ix = Data.DateConv[ column.get('FirstDate') or Data.GetToday() ] dh, cumh, dow = Data.GetPeriodInfo(ctperiod, ix, of) if dh == 0: cl = "off" elif es < (dh + cumh) and ef > cumh: # cl = "task" if task.get("ActualEndDate"): cl = "complete" elif task.get("PercentComplete"): # is this cell end past current position in task # if task.get("PercentComplete") >= (( cumh + dh - es )*100 / (ef-es)): # old formula pc = task.get("PercentComplete") position = es + (pc * (ef-es) / 100) # current position of task if pc == 100 or position >= (cumh + dh): cl = "complete" else: cl = "task" else: cl = "task" # if es <= cumh: xof = 0 # else: xof = int( rect.width * (es - cumh)/dh) # if ef >= (cumh + dh): wof = 0 # else: wof = int( rect.width * (cumh + dh - ef)/dh) else: cl = "non" else: cl = "non" ccolor = "" if cl == "task": override = rr.get('PlanBarColor') # use color override if (override and re.match('^[0-9A-Fa-f]{6}$', override)): cl = 'custom' ccolor = 'bgcolor="#%s" "color="#%s"' % (override, override) fp.write('") else: # table name, field name, time period, and record id value = Data.GetCellValue(rid, col, of) if value == None: value = '' cl = "non" fp.write('") fp.write("\r") fp.write(footer1) fp.write('
') # fp.write('


') fp.write('GanttPV ') # fp.write('Exported on ') dToday = datetime.datetime.today() # returns datetime object for now fp.write(dToday.strftime("%y-%m-%d %H:%M")) # convert to display format fp.write('
') fp.write(footer2) fp.close() def DoExport(): report = Data.Report[self.ReportID] deffile = (report.get('Name') or "Exported Report") + ".html" if deffile.count("/"): deffile = deffile.replace("/", "_") wildcardx = "HTML (*.html)|*.html|" "All files (*.*)|*.*" dlg = wx.FileDialog( self, message="Save file as ...", defaultFile=deffile, wildcard=wildcardx, style = wx.SAVE | wx.OVERWRITE_PROMPT ) dlg.SetFilterIndex(0) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() WriteHTML(path) dlg.Destroy() DoExport()
') if coloffset[i] > -1: ct = ctypes[i] ctperiod, ctfield = rct[ct].get('Name').split("/") if ctfield == "Gantt": if cspan < 3: c = c[:4] elif cspan < 2: c = c[:2] elif cspan < 1: c = " " else: c = c + '
' + cname2[i] fp.write(c) fp.write("
') fp.write(c) fp.write("
') if ct.get('Name') == 'Name' and rlevels: fp.write(indent * rlevels[ri]) fp.write(MakeString(value)) fp.write("' % (cl, ccolor, wid + wrap)) if cl in ("task", "complete", "custom"): fp.write("X") else: fp.write(" ") fp.write("') if value == "": fp.write(" ") else: fp.write(MakeString(value)) fp.write("