Source code for odml_xls_table

# -*- coding: utf-8 -*-
"""

"""

__package__='odmltables'

import datetime
import xlwt
import numpy as np

# Workaround Python 2 and 3 unicode handling.
try:
    unicode = unicode
except NameError:
    unicode = str

from .odml_table import OdmlTable
from .xls_style import XlsStyle


[docs]class OdmlXlsTable(OdmlTable): """ Class to create a csv-file from an odml-file :param sheetname: name of the excel sheet; default is 'sheet1' :param header_style: style used for the header of the table :param first_style: default style used for the rows :param second_style: used to switch styles of the rows if changing_point is not None :param first_marked_style: default style used in marked columns :param second_marked_style: used to switch styles of the rows in marked columns if changing_point is not None :param pattern: can be 'alternating' or 'checkerboard' :param changing_point: select the point for changing styles. this can be when a new section, property or value starts ('sections', 'properties', 'values' or None) :type sheetname: string :type header_style: XlsStyle :type first_style: XlsStyle :type second_style: XlsStyle :type first_marked_style: XlsStyle :type second_marked_style: XlsStyle :type pattern: string :type changing_point: string """ def __init__(self, load_from=None): super(OdmlXlsTable, self).__init__(load_from=load_from) self.sheetname = "sheet1" self._marked_cols = ["Value"] self.document_info_style = XlsStyle(backcolor='white', fontcolor='gray80', fontstyle='bold 1') self.header_style = XlsStyle(backcolor='gray80', fontcolor='white', fontstyle='bold 1') self.first_style = XlsStyle(backcolor='dark_blue', fontcolor='white', fontstyle='') self.second_style = XlsStyle(backcolor='green', fontcolor='white', fontstyle='') self.first_marked_style = XlsStyle(backcolor='light_blue', fontcolor='black', fontstyle='') self.second_marked_style = XlsStyle(backcolor='lime', fontcolor='black', fontstyle='') self.highlight_style = XlsStyle(backcolor='red', fontcolor='black', fontstyle='') self._highlight_defaults = False self._pattern = 'alternating' self._changing_point = 'sections' # TODO: python properties?? @property def changing_point(self): return self._changing_point @changing_point.setter def changing_point(self, point): if point in ["sections", "properties", "values", None]: self._changing_point = point else: raise Exception("Your changing point must be 'sections', " + "'properties', 'values' or None") # TODO: exceptions @property def pattern(self): return self._pattern @pattern.setter def pattern(self, pat): if pat in ['alternating', 'checkerboard']: self._pattern = pat else: raise Exception("This pattern does not exist") @property def highlight_defaults(self): return self._highlight_defaults @highlight_defaults.setter def highlight_defaults(self, mode): if mode in [True, False]: self._highlight_defaults = mode else: try: self._highlight_defaults = bool(mode) except: raise TypeError(f'Mode "{mode}" can not be converted to boolean.')
[docs] def mark_columns(self, *args): """ choose the columns of the table you want to highlight by giving them another style (for example a different color). Possible Arguments are: - 'Path' - 'SectionName' - 'SectionType' - 'SectionDefinition' - 'PropertyName' - 'PropertyDefinition' - 'Value' - 'DataUnit' - 'DataUncertainty' - 'odmlDatatype'. """ cols = [] for arg in args: if arg in list(self._header_titles): cols.append(arg) else: raise Exception("wrong argument") # TODO: exception... self._marked_cols = cols
[docs] def write2file(self, save_to): """ writes the data from the odml-file to a xls-file :param save_to: name of the xls-file :type save_to: string """ self.consistency_check() valid_changing_points = ['sections', 'properties', 'values', None] if not self._changing_point in valid_changing_points: raise Exception(f"Invalid argument for changing_point: Your changing_point must be one " f"of {valid_changing_points}") styles = {"document_info": xlwt.easyxf( self.document_info_style.get_style_string()), "header": xlwt.easyxf(self.header_style.get_style_string()), "row0col0": xlwt.easyxf(self.first_style.get_style_string()), "row1col0": xlwt.easyxf(self.second_style.get_style_string()), "row0col1": xlwt.easyxf(self.first_marked_style.get_style_string()), "row1col1": xlwt.easyxf(self.second_marked_style.get_style_string()), "highlight": xlwt.easyxf(self.highlight_style.get_style_string())} def write_row(row_id, row_content, stylestrings): assert len(row_content) == len(stylestrings) xls_styles = [styles[rs] for rs in stylestrings] for col_id, cell_content in enumerate(row_content): style = xls_styles[col_id] if cell_content == None: cell_content = '' if isinstance(cell_content, datetime.datetime): style.num_format_str = "DD-MM-YYYY HH:MM:SS" elif isinstance(cell_content, datetime.date): style.num_format_str = "DD-MM-YYYY" elif isinstance(cell_content, datetime.time): style.num_format_str = "HH:MM:SS" else: style.num_format_str = "" sheet.write(row_id, col_id, cell_content, style) # finding longest string in the column if len(unicode(cell_content)) > max_col_len[col_id]: max_col_len[col_id] = len(unicode(cell_content)) workbook = xlwt.Workbook() sheet = workbook.add_sheet(self.sheetname) oldpath = "" row_id = 0 doclen = len(self._docdict) if self._docdict else 0 max_col_len = [1] * max(len(self._header), 2 * doclen + 1) for i, h in enumerate(self._header): if h != None: max_col_len[i] = len(self._header_titles[h]) if self._docdict: row_content = ['Document Information'] for k, v in sorted(self._docdict.items()): row_content.extend([k,v]) row_styles = ['document_info'] * len(row_content) write_row(0, row_content, row_styles) row_id += 1 # write the header for col_id, h in enumerate(self._header): sheet.write(row_id, col_id, self._header_titles[h] if h in self._header_titles else "", styles['header']) row_id += 1 # set default styles as bool values for simplicity if self._pattern is "checkerboard": row_style_default = np.array([0, 1] * (len(self._header)), dtype=bool) row_style_default = row_style_default[:len(self._header)] elif self._pattern is "alternating": row_style_default = np.array([0] * len(self._header), dtype=bool) else: raise Exception(f"{self._pattern} is not a valid pattern") column_style_default = np.array([1 if h in self._marked_cols else 0 for h in self._header], dtype=bool) self.row_style = row_style_default self.column_style = column_style_default def _switch_row_style(): self.row_style = np.invert(self.row_style) if self._odmldict != None: # write the rest of the rows for dic in self._odmldict: # make a copy of the actual dic row_dic = dic.copy() # inflate row_dic row_dic['Path'], row_dic['PropertyName'] = row_dic['Path'].split(':') row_dic['SectionName'] = row_dic['Path'].split('/')[-1] row_dic_complete = row_dic.copy() # removing unnecessary entries if row_dic["Path"] == oldpath: if not self.show_all_sections: for h in self._SECTION_INF + ['SectionName']: row_dic[h] = "" row_dic['Path'] = "" # if dic["Path"].split(':')[-1] == oldprop: # if not self.show_all_properties: # for h in self._PROPERTY_INF: # row_dic[h] = "" # handling row styles if self._changing_point is 'properties': _switch_row_style() elif self._changing_point is 'sections' and (row_dic["Path"] != oldpath): _switch_row_style() # row_content: only those elements of row_dic, that will be visible in the table row_content = [row_dic[h] if h != None else '' for h in self._header] # generating row even when no value entry is present if not row_dic['Value']: row_dic['Value'] = [''] for v in row_dic['Value']: stylestring = [f"row{r:d}col{c:d}" for r, c in zip(self.row_style, self.column_style)] # highlight empty values if self._highlight_defaults and (row_dic['Value'] == [] or row_dic['Value'] == ['']): stylestring[self._header.index('Value')] = 'highlight' # update value entry and write line if 'Value' in self._header: # explicitely replacing 0-1 representation by string representation if isinstance(v, bool): v = 'True' if v else 'False' row_content[self._header.index('Value')] = v write_row(row_id, row_content, stylestring) row_id += 1 # continue with next property if values are not exported if 'Value' not in self._header: break if self._changing_point is 'values': _switch_row_style() # adjust section and property entries for next value for h in self._header: if (not self.show_all_properties and h in self._PROPERTY_INF + ['PropertyName']): row_content[self._header.index(h)] = '' elif (not self.show_all_sections and h in self._SECTION_INF + ['SectionName', 'Path']): row_content[self._header.index(h)] = '' oldpath = row_dic_complete["Path"] # adjust the size of the columns due to the max length of the content, # but no more than max_allowed_col_len characters max_allowed_col_len = 80 for i, l in enumerate(max_col_len): sheet.col(i).width = 256 * (min(l, max_allowed_col_len) + 1) workbook.save(save_to)