Source code for simple_report.xls.section

#coding:utf-8

from datetime import datetime, date, time
import decimal

import re
import xlrd
import xlwt
from xlwt.Style import default_style
from simple_report.interface import ISpreadsheetSection

from simple_report.core.exception import XLSReportWriteException
from simple_report.core.spreadsheet_section import (
    SpreadsheetSection, AbstractMerge
)
from simple_report.xls.cursor import CalculateNextCursorXLS
from simple_report.utils import FormulaWriteExcel

from xlwt import Formatting as xlwt_formatting

# for k, v in xlwt.ExcelMagic.all_funcs_by_name.items():
#     xlwt.ExcelMagic.all_funcs_by_name[k] = list(v)
#     xlwt.ExcelMagic.all_funcs_by_name[k][2] = 100

xlwt.ExcelMagic.all_funcs_by_name['SUM'] = (4, 1, 100, 'V', 'D+')

KEEP_TEXT_TYPE = False

FORMULA_XLS_TYPE = 'formula_xls'
EXCEL_IMAGE_TYPE = 'excel_image'
TEXT_CELL_FORMAT = '@'


def _get_out_cell(out_sheet, col_index, row_index):
    """ HACK: Extract the internal xlwt cell representation. """
    row = out_sheet._Worksheet__rows.get(row_index)
    if not row:
        return None

    cell = row._Row__cells.get(col_index)
    return cell


[docs]class Section(SpreadsheetSection, ISpreadsheetSection): """ Класс секции отчета в xls """ def __init__(self, sheet, name, begin, end, writer): super(Section, self).__init__(sheet, name, begin, end) self.sheet_data = sheet self.writer = writer
[docs] def flush(self, params, oriented=ISpreadsheetSection.LEFT_DOWN, used_formulas=None): """ Запись секции в отчет :param params: словарь с параметрами подстановки :param oriented: направление вывода секции :param used_formulas: используемые формулы - нужны для записи простых формул в отчет :result: None """ for k, v in params.items(): if v is None: params[k] = '' if used_formulas is None: used_formulas = {} begin_row, begin_column = self.begin end_row, end_column = self.end book = self.sheet_data.sheet.book current_col, current_row = self.calc_next_cursor(oriented=oriented) for rdrowx in range(begin_row, end_row + 1): # индекс строки независит от колонок wtrowx = current_row + rdrowx - begin_row for rdcolx in range(begin_column, end_column + 1): # Вычисляем координаты ячейки для записи. wtcolx = current_col + rdcolx - begin_column try: cell = self.writer.rdsheet.cell(rdrowx, rdcolx) except IndexError: continue val = cell.value # доставем формат ячейки xf_index = cell.xf_index xf = book.xf_list[xf_index] format_key = xf.format_key format_ = book.format_map[format_key] format_str = format_.format_str cty = cell.ctype f_id = None for key, value in params.items(): if unicode(cell.value).count(u''.join(['#', key, '#'])): if used_formulas: formula_id_list = used_formulas.get(key) if formula_id_list: for formula_id in formula_id_list: self.sheet_data.formula_id_dict.setdefault( formula_id, [] ).append( ''.join([xlrd.colname(wtcolx), str(wtrowx + 1)]) ) if isinstance(value, FormulaWriteExcel): # Если приходит формула, то заменяем на # ее значение с указанием списка ячеек formula = value.excel_function f_id = value.formula_id if formula is not None and f_id is not None: formula_cells = self.sheet_data.formula_id_dict.get( f_id ) if formula_cells: if value.ranged: val = '%s(%s)' % (formula, ':'.join( [formula_cells[0], formula_cells[-1]])) else: val = '%s(%s)' % (formula, ','.join( formula_cells)) self.sheet_data.formula_id_dict[f_id] = [] cty = FORMULA_XLS_TYPE else: val = '' cty = xlrd.XL_CELL_TEXT break elif isinstance(value, XLSImage): cty = EXCEL_IMAGE_TYPE val = value break # Тип ячейки cty = self.get_value_type(value=value, default_type=cell.ctype) value = unicode(value) val = val.replace(u'#%s#' % key, value) if isinstance(val, basestring): while u'#' in val: val = re.sub(u'#.*#', '', val) if len(val.split('#')) == 2: break # Копирование всяких свойств из шаблона в результирующий отчет. if (wtcolx not in self.writer.wtcols and rdcolx in self.writer.rdsheet.colinfo_map): rdcol = self.writer.rdsheet.colinfo_map[rdcolx] wtcol = self.writer.wtsheet.col(wtcolx) wtcol.width = rdcol.width wtcol.set_style(self.writer.style_list[rdcol.xf_index]) wtcol.hidden = rdcol.hidden wtcol.level = rdcol.outline_level wtcol.collapsed = rdcol.collapsed self.writer.wtcols.add(wtcolx) if cty == xlrd.XL_CELL_EMPTY: continue # XF - индексы if cell.xf_index is not None: style = self.writer.style_list[cell.xf_index] else: style = default_style rdcoords2d = rdrowx, rdcolx if rdcoords2d in self.writer.merged_cell_top_left_map: rlo, rhi, clo, chi = self.writer.merged_cell_top_left_map[ rdcoords2d ] assert (rlo, clo) == rdcoords2d if isinstance(val, XLSImage): self.writer.wtsheet.merge( wtrowx, wtrowx + rhi - rlo - 1, wtcolx, wtcolx + chi - clo - 1, style ) #TODO: вынести в метод записи self.writer.wtsheet.insert_bitmap( val.path, wtrowx, wtcolx ) continue self.writer.wtsheet.write_merge( wtrowx, wtrowx + rhi - rlo - 1, wtcolx, wtcolx + chi - clo - 1, val, style) continue if rdcoords2d in self.writer.merged_cell_already_set: continue # если поле текстовое и # стоит настройка "Сохранять текстовые поля" # то не преобразуем текст в число if KEEP_TEXT_TYPE and format_str == TEXT_CELL_FORMAT: pass else: try: val1 = val if isinstance(val1, float): val1 = str(val1) decimal.Decimal(val1) cty = xlrd.XL_CELL_NUMBER except (decimal.InvalidOperation, TypeError): pass runlist = self.writer.rdsheet.rich_text_runlist_map.get( (rdrowx, rdcolx) ) self.write_result((wtcolx, wtrowx), val, style, cty, (runlist, rdrowx, rdcolx)) # перетащим заодно и высоту текущей строки rdrow = self.writer.rdsheet.rowinfo_map.get(rdrowx) wtrow = self.writer.wtsheet.rows.get(wtrowx) if rdrow is not None and wtrow is not None: wtrow.height = rdrow.height # height_mismatch нужен для того, чтобы применилась высота wtrow.height_mismatch = rdrow.height_mismatch
[docs] def get_width(self): """ Получение ширины секции """ begin_row, begin_col = self.begin end_row, end_col = self.end return end_col - begin_col + 1
[docs] def calc_next_cursor(self, oriented=ISpreadsheetSection.LEFT_DOWN): """ Вычисляем следующее положение курсора. """ begin_row, begin_column = self.begin end_row, end_column = self.end current_col, current_row = CalculateNextCursorXLS().get_next_cursor( self.sheet_data.cursor, (begin_column, begin_row), (end_column, end_row), oriented, section=self) return current_col, current_row #TODO реализовать для поддержки интерфейса ISpreadsheetSection
[docs] def get_all_parameters(self): """ Получение всех параметров секции. :result: None """
[docs] def get_cell_final_type(self, value, cell_type): """ Окончательный тип значения ячейки. Нужна, для того, чтобы точно определить, является ли ячейка числовой """ cty = cell_type if KEEP_TEXT_TYPE and cell_type == xlrd.XL_CELL_TEXT: return cty try: long(value) cty = xlrd.XL_CELL_NUMBER except ValueError: pass return cty
[docs] def get_value_type(self, value, default_type=xlrd.XL_CELL_TEXT): """ Возвращаем тип значения для выходного элемента :param value: значение :param default_type: тип по умолчанию :result: тип ячейки """ if isinstance(value, basestring): cty = xlrd.XL_CELL_TEXT elif isinstance(value, (datetime, date, time)): cty = xlrd.XL_CELL_DATE elif isinstance(value, bool): cty = xlrd.XL_CELL_BOOLEAN elif value is None: cty = xlrd.XL_CELL_EMPTY # elif isinstance(value, numbers.Number): # if default_type == xlrd.XL_CELL_TEXT and KEEP_TEXT_TYPE: # return default_type # cty = xlrd.XL_CELL_NUMBER else: cty = default_type # if default_type == xlrd.XL_CELL_TEXT and KEEP_TEXT_TYPE: # return cty # try: # long(value) # cty = xlrd.XL_CELL_NUMBER # except ValueError: # cty = default_type return cty
[docs] def get_rich_text_list(self, text, runlist, default_font): """ получение списка строк для rich_text :param text: :param runlist: :param default_font: :result: """ rtl = [] len_runlist = len(runlist) counter = 0 # для первых символов берется дефолтный шрифт if len_runlist: rtl.append( (text[:runlist[0][0]], default_font) ) # затем строка разбивается на куски for char_num, font_id in runlist: if char_num > len(text): break if counter == len_runlist - 1: end_char_num = None else: end_char_num = runlist[counter + 1][0] rtl.append( (text[char_num:end_char_num], self.get_font(font_id)) ) counter += 1 return rtl
[docs] def get_font(self, font_index): """ Получение шрифта по индексу :param font_index: индекс шрифта :result: шрифт """ if not hasattr(self, 'fonts'): self.fonts = {} wt_font = self.fonts.get(font_index) if not wt_font: wt_font = self.create_font(font_index) self.fonts[font_index] = wt_font return wt_font
[docs] def create_font(self, rd_font_index): """ Создание шрифта :param rd_font_index: индекс шрифта в исходном файле :result: шрифт в выходном файле """ font_list = self.writer.rdbook.font_list rdf = font_list[rd_font_index] # Далее копипаста из xlutils wtf = xlwt_formatting.Font() wtf.height = rdf.height wtf.italic = rdf.italic wtf.struck_out = rdf.struck_out wtf.outline = rdf.outline wtf.shadow = rdf.outline wtf.colour_index = rdf.colour_index wtf.bold = rdf.bold #### This attribute is redundant, should be driven by weight wtf._weight = rdf.weight #### Why "private"? wtf.escapement = rdf.escapement wtf.underline = rdf.underline_type #### # wtf.???? = rdf.underline #### redundant attribute, set on the fly when writing wtf.family = rdf.family wtf.charset = rdf.character_set wtf.name = rdf.name # Конец копипасты return wtf
[docs] def write_result( self, write_coords, value, style, cell_type, (runlist, rdrowx, rdcolx) ): """ Выводим в ячейку с координатами `write_coords` значение `value`. :param write_coords: координаты ячейки :param value: значение :param style: стиль вывода :param cell_type: тип ячейки :param runlist: :param rdrowx: строка в исходном файле :param rdcolx: колонка в исходном файле """ wtcolx, wtrowx = write_coords if cell_type == EXCEL_IMAGE_TYPE: self.writer.wtsheet.insert_bitmap( value.path, wtrowx, wtcolx ) return # cell_type = self.get_cell_final_type(value, cell_type) #cell = _get_out_cell(self.writer.wtsheet, wtcolx, wtrowx) #xf_idx = cell.xf_idx # Вывод wtrow = self.writer.wtsheet.row(wtrowx) if cell_type == FORMULA_XLS_TYPE: self.writer.wtsheet.write(wtrowx, wtcolx, xlwt.Formula(value), style) elif cell_type == xlrd.XL_CELL_TEXT or cell_type == xlrd.XL_CELL_EMPTY: if runlist is not None: rich_text_list = self.get_rich_text_list(value, runlist, style.font) self.writer.wtsheet.write_rich_text( wtrowx, wtcolx, rich_text_list, style=style) else: wtrow.set_cell_text(wtcolx, value, style) elif cell_type == xlrd.XL_CELL_NUMBER: wtrow.set_cell_number(wtcolx, value, style) elif cell_type == xlrd.XL_CELL_DATE: wtrow.set_cell_text(wtcolx, value, style) elif cell_type == xlrd.XL_CELL_BLANK: wtrow.set_cell_blank(wtcolx, style) elif cell_type == xlrd.XL_CELL_BOOLEAN: wtrow.set_cell_boolean(wtcolx, value, style) elif cell_type == xlrd.XL_CELL_ERROR: wtrow.set_cell_error(wtcolx, value, style) else: raise XLSReportWriteException cell = _get_out_cell(self.writer.wtsheet, wtcolx, wtrowx) #if xf_idx: # cell.xf_idx = xf_idx
[docs]class MergeXLS(AbstractMerge): """ Конструкция Merge """ def _merge(self): self.section.writer.wtsheet.merge(self.begin_row_merge, self.end_row_merge, self._begin_merge_col, self._end_merge_col) def _calculate_merge_column(self, column): """ Подсчет колонок слияния :param column: текущая колонка :result: (1 колонка секции, 2 колонка секции) """ first_section_column = column - self.section.get_width() last_section_column = column - 1 return first_section_column, last_section_column
[docs]class XLSImage(object): """ Рисунок. Может быть использован при записи в секцию методом `flush` """ def __init__(self, path): self.path = path