# coding: utf-8
import copy
from datetime import datetime
from decimal import Decimal
import re
from lxml.etree import QName, SubElement
from simple_report.core.shared_table import SharedStringsTable
from simple_report.core.tags import TemplateTags
from simple_report.interface import ISpreadsheetSection
from simple_report.utils import (ColumnHelper, get_addr_cell, date_to_float,
FormulaWriteExcel)
from simple_report.xlsx.cursor import Cursor
from simple_report.core.spreadsheet_section import SpreadsheetSection, AbstractMerge
from simple_report.core.exception import SheetDataException
from simple_report.xlsx.formula import Formula
from simple_report.xlsx.cursor import CalculateNextCursorXLSX
__author__ = 'prefer'
XLSX_GROUPING_COUNT = 30
[docs]class SheetData(object):
u"""
self.read_data:
<sheetData>
<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.8" outlineLevel="0" r="5">
<c r="C5" s="1" t="s">
<v>0</v>
</c>
<c r="D5" s="1"/>
<c r="E5" s="1"/>
</row>
<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.8" outlineLevel="0" r="6">
<c r="C6" s="0" t="s">
<v>1</v>
</c>
</row>
</sheetData>
self.read_dimension:
<dimension ref="B5:I10"/>
self.read_merge_cell:
<mergeCells count="1">
<mergeCell ref="C5:E5"/>
</mergeCells>
Данные для подобных write атрибутов должны быть такой же структуры
"""
XPATH_TEMPLATE_ROW = '*[@r="%d"]'
XPATH_TEMPLATE_CELL = '*[@r="%s"]'
PREFIX_TAG = '%'
FIND_PARAMS = re.compile(u'#[A-zА-яёЁ_0-9]+#')
FIND_TEMPLATE_TAGS = re.compile(u'#{0}[A-zА-яёЁ_0-9]+{0}#'.format(PREFIX_TAG))
COLUMN_INDEX = re.compile('[A-Z]+')
ROW_INDEX = re.compile('[1-9][0-9]*')
def __init__(self, sheet_xml, tags, cursor, ns, shared_table):
# namespace
self.ns = ns
self.formula_id_dict = {}
# Шаблонные теги
assert isinstance(tags, TemplateTags)
self.tags = tags
assert isinstance(cursor, Cursor)
self._cursor = cursor
self._last_section = Cursor()
assert isinstance(shared_table, SharedStringsTable)
self.shared_table = shared_table
self._read_xml = sheet_xml
self.read_data = sheet_xml.find(QName(self.ns, 'sheetData'))
if self.read_data is None:
self.read_data = []
self.read_dimension = sheet_xml.find(QName(self.ns, 'dimension'))
if self.read_dimension is None:
self.read_dimension = []
self.read_merge_cell = sheet_xml.find(QName(self.ns, 'mergeCells'))
if self.read_merge_cell is None:
self.read_merge_cell = []
# Строчные разделители страниц
self.read_rowbreaks = self._read_xml.find(QName(self.ns, 'rowBreaks'))
# Колоночные разделители страниц
self.read_colbreaks = self._read_xml.find(QName(self.ns, 'colBreaks'))
self._write_xml = copy.deepcopy(sheet_xml)
# Ссылка на тег данных строк и столбцов листа с очищенными значениями
self.write_data = self._write_xml.find(QName(self.ns, 'sheetData'))
self.write_data_dict = {}
if not self.write_data is None:
self.write_data.clear()
# Ссылка на размеры листа
self.write_dimension = self._write_xml.find(QName(self.ns, 'dimension'))
# Ссылка на объединенные ячейки листа с очищенными значениями
self.write_merge_cell = self._write_xml.find(QName(self.ns, 'mergeCells'))
if self.write_merge_cell is not None:
self.write_merge_cell.clear()
# Ссылка на ширины столбцов
self.write_cols = self._write_xml.find(QName(self.ns, 'cols'))
# if self.write_cols is None:
# self.write_cols = SubElement(self._write_xml, 'cols', attrib={})
# Строчные разделители страниц
self.write_rowbreaks = self._write_xml.find(QName(self.ns, 'rowBreaks'))
# очистим, т.к. будем заполнять при копировании
if self.write_rowbreaks is None:
self.write_rowbreaks = SubElement(self._write_xml, 'rowBreaks', attrib={"count":"0", "manualBreakCount":"0"})
else:
self.write_rowbreaks.clear()
self.write_rowbreaks.set("count", "0")
self.write_rowbreaks.set("manualBreakCount", "0")
# Колоночные разделители страниц
self.write_colbreaks = self._write_xml.find(QName(self.ns, 'colBreaks'))
# очистим, т.к. будем заполнять при копировании
if self.write_colbreaks is None:
self.write_colbreaks = SubElement(self._write_xml, 'colBreaks', attrib={"count":"0", "manualBreakCount":"0"})
else:
self.write_colbreaks.clear()
self.write_colbreaks.set("count", "0")
self.write_colbreaks.set("manualBreakCount", "0")
@property
def cursor(self):
u"""
Курсор
"""
return self._cursor
@cursor.setter
[docs] def cursor(self, value):
u"""
Установка курсора
:param value: новый курсор
:type value: Cursor
"""
assert isinstance(value, Cursor)
self._cursor = value
@property
def last_section(self):
u"""
Последняя секция
"""
return self._last_section
@last_section.setter
[docs] def last_section(self, value):
"""
Установка последней секции
:param value: курсор
:type value: Cursor
"""
assert isinstance(value, Cursor)
self._last_section = value
[docs] def flush(self, begin, end, start_cell, params, used_formulas=None):
"""
Вывод секции
:param begin: Начало секции, пример ('A', 1)
:param end: Конец секции, пример ('E', 6)
:param start_cell: ячейка с которой надо выводить
:param params: данные для вывода
"""
self.set_section(begin, end, start_cell, params, used_formulas)
self.set_merge_cells(begin, end, start_cell)
self.set_dimension()
if self.write_cols is not None:
self.set_columns_width(begin, end, start_cell)
self.set_pagebreaks(begin, end, start_cell)
[docs] def set_dimension(self):
u"""
Установка диапазона
"""
_, row_index = self.cursor.row
col_index, _ = self.cursor.column
dimension = 'A1:%s' % \
(ColumnHelper.add(col_index, -1) + str(row_index - 1))
self.write_dimension.set('ref', dimension)
def _get_merge_cells(self):
for cell in self.read_merge_cell:
yield cell.attrib['ref'].split(':')
[docs] def set_merge_cells(self, section_begin, section_end, start_cell):
"""
Объединение ячеек
:param section_begin: начало секции
:type section_begin: 2-tuple
:param section_end: конец секции
:type section_end: 2-tuple
:param start_cell: стартовая ячейка
:type start_cell: 2-tuple
"""
def cell_dimensions(section, merge_cell, start_cell):
"""
Получение координаты ячейки после смещения из-за
объединения ячеек
:param section: начало секции
:type section: 2-tuple
:param merge_cell: начало объединенной ячейки
:type merge_cell: 2-tuple
:param start_cell: стартовая ячейка
:type start_cell: 2-tuple
"""
section_begin_col, section_begin_row = section
start_col, start_row = start_cell
begin_col, begin_row = merge_cell
new_begin_row = start_row + begin_row - section_begin_row
new_begin_col = ColumnHelper.add(start_col, ColumnHelper.difference(begin_col, section_begin_col))
return new_begin_col + str(new_begin_row)
range_rows, range_cols = self._range(section_begin, section_end)
for begin, end in self._get_merge_cells():
begin_col, begin_row = get_addr_cell(begin)
end_col, end_row = get_addr_cell(end)
# Если объединяемый диапазон лежит внутри секции
if (begin_col in range_cols and end_col in range_cols and
begin_row in range_rows and end_row in range_rows):
begin_merge = cell_dimensions(section_begin, (begin_col, begin_row), start_cell)
end_merge = cell_dimensions(section_begin, (end_col, end_row), start_cell)
attrib = {'ref': ':'.join((begin_merge, end_merge))}
SubElement(self.write_merge_cell, 'mergeCell', attrib)
if self.write_merge_cell is not None:
count_merge_cells = len(self.write_merge_cell)
self.write_merge_cell.set('count', str(count_merge_cells))
def _find_rows(self, range_rows):
"""
Итератор по строкам
:param range_rows: номера строк
:type range_rows: iterable
"""
for i, num_row in enumerate(range_rows):
row = self.read_data.find(self.XPATH_TEMPLATE_ROW % num_row)
if not row is None:
yield i, num_row, row
def _find_cells(self, range_cols, num_row, row):
"""
Итератор по ячейкам
:param range_cols: диапазон колонок
:type range_cols: iterable
:param num_row: номер строки
:type num_row: int
:param row: объект строки
:type row: lxml.etree.Element
"""
for j, col in enumerate(range_cols):
cell = row.find(self.XPATH_TEMPLATE_CELL % (col + str(num_row)))
if not cell is None:
yield j, col, cell
def _get_tag_value(self, cell):
"""
Получение значения ячейки
:param cell: ячейка
:type cell: lxml.etree.Element
"""
return cell.find(QName(self.ns, 'v'))
def _get_params(self, cell):
"""
Получение параметров ячейки
:param cell: ячейка
:type cell: lxml.etree.Element
"""
value = self._get_tag_value(cell)
if not value is None and cell.get('t') == 's': # 't' = 's' - значит есть значения shared strings
index_value = int(value.text)
value_string = self.shared_table.get_value(index_value)
return self._get_values_by_re(value_string, self.FIND_PARAMS)
else:
return []
def _get_values_by_re(self, value_string, what_found=None):
"""
Получение значений по регулярному выражению
:param value_string: строка, в которой ищем
:type value_string: basestring
:param what_found: регулярное выражение
:type what_found: regexp
"""
"""
"""
if what_found is None:
# Если значение поиска неопределено выводим поиск для всех параметров
return self._get_values_by_re(value_string, self.FIND_PARAMS) + \
self._get_values_by_re(value_string, self.FIND_TEMPLATE_TAGS)
who_found_params = what_found.findall(value_string)
if who_found_params is not None:
return [found_param for found_param in who_found_params]
else:
return []
[docs] def find_all_parameters(self, begin, end):
"""
Получение всех параметров
:param begin: начальная ячейка
:type begin: 2-tuple
:param end: конечная ячейка
:type end: 2-tuple
"""
range_rows, range_cols = self._range(begin, end)
for i, num_row, row in self._find_rows(range_rows):
for j, col, cell in self._find_cells(range_cols, num_row, row):
for param in self._get_params(cell):
yield param
def _get_tag_formula(self, cell):
u"""
Получение тега с формулой
:param cell: ячейка
:type cell: lxml.etree.Element
"""
return cell.find(QName(self.ns, 'f'))
def _create_or_get_output_row(self, row_index, attrib_row):
u"""
Получение выходной строки
:param row_index: индекс строки
:type row_index: int
:param attrib_row: атрибуты строки
:type attrib_row: dict
"""
# найдем существующую строку в результирующих данных
key = self.XPATH_TEMPLATE_ROW % row_index
row_el = self.write_data_dict.get(key)
if row_el is None:
# если не нашли - создадим
row_el = SubElement(self.write_data, 'row', attrib=attrib_row)
self.write_data_dict[key] = row_el
return row_el
def _create_or_get_output_row_old(self, row_index, attrib_row):
"""
Deprecated/unused
:param row_index:
:type row_index:
:param attrib_row:
:type attrib_row:
"""
# найдем существующую строку в результирующих данных
row_el = self.write_data.find(self.XPATH_TEMPLATE_ROW % row_index)
if row_el is None:
# если не нашли - создадим
row_el = SubElement(self.write_data, 'row', attrib=attrib_row)
return row_el
def _create_or_get_output_cell(self, row_el, cell_index, attrib_cell):
"""
Получение выходной ячейки
:param row_el: элемент строки
:type row_el: lxml.etree.Element
:param cell_index: индекс ячейки
:type cell_index: int
:param attrib_cell: атрибуты ячейки
:type attrib_cell: dict
"""
# найдем существующую ячейку в строке
cell_el = row_el.find(self.XPATH_TEMPLATE_CELL % cell_index)
if cell_el is None:
# если не нашли - создадим
cell_el = SubElement(row_el, 'c', attrib=attrib_cell)
return cell_el
# def _add_formula_to_calc_chain(self, row, column):
# """
# Добавление формулы в цепочку вычислений (если файл есть в
# шаблоне)
# """
# if self.calc_chain:
# calc = SubElement(self.calc_chain._root, 'c')
# calc.attrib['r'] = '%s%s' % (column, row)
# @profile
[docs] def set_section(self, begin, end, start_cell, params, used_formulas=None):
u"""
Вывод секции
:param begin: начальная ячейка секции
:type begin: 2-tuple
:param end: конечная ячейка секции
:type end: 2-tuple
:param start_cell: стартовая ячейка (с которой начинается запись)
:type start_cell: 2-tuple
:param params: параметры отчета
:type params: dict
:param used_formulas: используемые формулы
:type used_formulas: dict
"""
# TODO: разбить на методы
range_rows, range_cols = self._range(begin, end)
start_column, start_row = start_cell
if used_formulas is None:
used_formulas = {}
for i, num_row, row in self._find_rows(range_rows):
attrib_row = dict(row.items())
row_index = start_row + i
attrib_row['r'] = str(row_index)
# следующая строка создает линейное время для флаша от числа флашей
row_el = self._create_or_get_output_row(row_index, attrib_row)
for j, col, cell in self._find_cells(range_cols, num_row, row):
attrib_cell = dict(cell.items())
col_index = ColumnHelper.add(start_column, j)
attrib_cell['r'] = col_index + str(row_index)
cell_el = self._create_or_get_output_cell(row_el, col_index + str(row_index), attrib_cell)
# Перенос формул
formula = self._get_tag_formula(cell)
if formula is not None:
formula_el = SubElement(cell_el, 'f')
row_cursor_column, row_cursor_row = self.cursor.row
column_cursor_column, column_cursor_row = self.cursor.column
formula_el.text = Formula.get_instance(formula.text).get_next_formula(row_cursor_row,
column_cursor_column)
# Если есть формула, то значение является вычисляемым параметром и не сильно интересует
continue
value = self._get_tag_value(cell)
if not value is None:
value_el = SubElement(cell_el, 'v')
if attrib_cell.get('t') in ('n', None): # number
value_el.text = value.text
elif attrib_cell.get('t') == 's': # 't' = 's' - значит есть значения shared strings
index_value = int(value.text)
value_string = self.shared_table.get_value(index_value)
who_found_params = self._get_values_by_re(value_string)
is_int = False
if who_found_params:
for found_param in who_found_params:
param_name = found_param[1:-1]
param_value = params.get(param_name)
if used_formulas:
formula_id_list = used_formulas.get(param_name)
assert (
formula_id_list is None or
isinstance(
formula_id_list,
(list, tuple))
), ("used_formulas values must be "
"lists or tuples")
if formula_id_list is not None:
for formula_id in formula_id_list:
cell_string = ''.join([col_index,
str(row_index)])
self.formula_id_dict.setdefault(
formula_id, []).append(cell_string)
# Находим теги шаблонов, если есть таковые
if param_name[0] == self.PREFIX_TAG and param_name[-1] == self.PREFIX_TAG:
param_value = self.tags.get(param_name[1:-1])
if isinstance(param_value, datetime) and found_param == value_string:
# В OpenXML хранится дата относительно 1900 года
days = date_to_float(param_value)
if days > 0:
# Дата конвертируется в int, начиная с 31.12.1899
is_int = True
cell_el.attrib['t'] = 'n' # type - number
value_el.text = unicode(days)
else:
date_less_1900 = '%s.%s.%s' % (
param_value.date().day,
param_value.date().month,
param_value.date().year
)
# strftime(param_value, locale.nl_langinfo(locale.D_FMT)) - неработает для 1900 и ниже
value_string = value_string.replace(found_param, unicode(date_less_1900))
# Добавил long, возможно нужно использовать
# общего предка numbers.Number
elif isinstance(
param_value,
(int, float, long, Decimal)
) and found_param == value_string:
# В первую очередь добавляем числовые значения
is_int = True
cell_el.attrib['t'] = 'n' # type - number
value_el.text = unicode(param_value)
elif isinstance(param_value, basestring):
# Строковые параметры
value_string = value_string.replace(found_param, unicode(param_value))
elif isinstance(param_value, FormulaWriteExcel):
# Записываем формулу, которой не было в
# шаблоне
func_ = param_value.excel_function
f_id = param_value.formula_id
if f_id and func_:
attrib_cell['t'] = 'e'
# тип вычислимого по формуле поля
cell_el.remove(value_el)
# значения в ячейке с формулой нет
formula_el = SubElement(cell_el, 'f')
row_cursor_column, row_cursor_row = self.cursor.row
column_cursor_column, column_cursor_row = self.cursor.column
f_cells = self.formula_id_dict.get(
f_id, [])
if param_value.ranged and f_cells:
formula_el.text = '%s(%s)' % (
func_, ':'.join([f_cells[0],
f_cells[-1]])
)
elif f_cells:
# Группируем по 30 элементов
cells_number = int(
len(f_cells) / XLSX_GROUPING_COUNT
) + 1
val_list = []
for x in range(cells_number):
sub_args = (
','.join(
f_cells[x * XLSX_GROUPING_COUNT:(x + 1) * XLSX_GROUPING_COUNT]
)
)
if sub_args:
sub_val = '(%s)' % (
sub_args
)
val_list.append(sub_val)
formula_el.text = '%s(%s)' % (
func_,
','.join(val_list)
)
self.formula_id_dict[f_id] = []
else:
continue
elif param_value:
# любой объект, например список или
# словарь. Вынесено для производительности
value_string = value_string.replace(found_param, unicode(param_value))
else:
# Не передано значение параметра
value_string = value_string.replace(found_param, '')
if not is_int:
# Добавим данные в shared strings
new_index = self.shared_table.get_new_index(value_string)
value_el.text = new_index
else:
# Параметры в поле не найдены
index = self.shared_table.get_new_index(value_string)
value_el.text = index
elif attrib_cell.get('t'):
raise SheetDataException("Unknown value '%s' for tag t" % attrib_cell.get('t'))
def _range(self, begin, end):
u"""
Диапазон строк, колонок
:param begin: начальная ячейка
:type begin: 2-tuple
:param end: конечная ячейка
:type end: 2-tuple
"""
# Если есть объединенная ячейка, и она попадает на конец секции, то адресс конца секции записывается как конец
# объединенной ячейки
end = self.get_cell_end(end)
rows = begin[1], end[1] + 1
cols = begin[0], end[0]
range_rows = xrange(*rows)
range_cols = list(ColumnHelper.get_range(*cols))
return range_rows, range_cols
def _addr_in_range(self, addr, begin, end):
u"""
Проверяет, попадает ли адрес в диапазон
:param addr: адрес ячейки
:type addr: 2-tuple
:param begin: начальная ячейка диапазона
:type begin: 2-tuple
:param end: конечная ячейка диапазона
:type end: 2-tuple
"""
col, row = addr
rows = xrange(begin[1], end[1] + 1)
cols = list(ColumnHelper.get_range(begin[0], end[0]))
return all([col in cols, row in rows])
[docs] def get_cell_end(self, cell_addr):
"""
Получение (правого нижнего) конца ячейки
:param cell_addr: адрес ячейки
:type cell_addr: 2-tuple
"""
cell_end = cell_addr
# Если указанный адрес пападает в объединенную ячейку, то адресс конца ячейки указывается как конец
# объединенной ячейки
for begin_merge, end_merge in self._get_merge_cells():
begin_addr = get_addr_cell(begin_merge)
end_addr = get_addr_cell(end_merge)
if self._addr_in_range(cell_addr, begin_addr, end_addr):
cell_end = end_addr
break
return cell_end
[docs] def new_sheet(self):
"""
"""
if self.write_merge_cell is not None:
childs = self.write_merge_cell.getchildren()
if not childs:
self._write_xml.remove(self.write_merge_cell)
if self.write_cols is not None and not self.write_cols.getchildren():
self._write_xml.remove(self.write_cols)
# если небыло разделителей страниц, то удалим раздел
if not self.write_colbreaks.getchildren():
self._write_xml.remove(self.write_colbreaks)
if not self.write_rowbreaks.getchildren():
self._write_xml.remove(self.write_rowbreaks)
return self._write_xml
def _create_or_get_output_col(self, col_index, attrib_col=None):
"""
Найдем существующую колонку в результирующих данных
Если не передали начальные данные, то колонка не создается,
если не найдена,
:param col_index: индекс колонки
:type col_index: int
:param attrib_col: атрибуты элемента колонки
:type attrib_col: dict
"""
# найдем интервал, в который попадаем искомый индекс
col_index = ColumnHelper.column_to_number(col_index) + 1
col_el = None
for col in self.write_cols.getchildren():
begin_col = int(col.attrib['min'])
end_col = int(col.attrib['max'])
if begin_col <= col_index <= end_col:
col_el = col
break
if col_el is None:
# если не нашли - создадим
if attrib_col is None:
return None
attrib_col["min"] = str(col_index)
attrib_col["max"] = str(col_index)
col_el = SubElement(self.write_cols, 'col', attrib=attrib_col)
return col_el
def _set_new_column_width(self, col_index, src_col, dst_col):
"""
Установка новой ширины колонки
Особенность в том, что нужно разбивать интервалы, если потребуется
:param col_index: индекс колонки
:type col_index: int
:param src_col: элемент исходной колонки
:type src_col: lxml.etree.Element
:param dst_col: элемент выходной колонки
:type dst_col: lxml.etree.Element
"""
col_index = ColumnHelper.column_to_number(col_index) + 1
# если ширина колонок отличается и колонка-приемник является интервалом,
# то нужно разбить интервал на части с разной шириной колонок
if dst_col.attrib["width"] != src_col.attrib["width"]:
begin_col = int(dst_col.attrib['min'])
end_col = int(dst_col.attrib['max'])
# если задано интервалом, то будем разбивать
if end_col - begin_col > 0:
if col_index > begin_col:
# предыдущий интервал: max = col-1
prev_attrib_col = dict(dst_col.items())
prev_attrib_col['max'] = str(col_index - 1)
prev_col_el = SubElement(self.write_cols, 'col', attrib=prev_attrib_col)
if col_index < end_col:
# следующий интервал: min = col+1
next_attrib_col = dict(dst_col.items())
next_attrib_col["min"] = str(col_index + 1)
next_col_el = SubElement(self.write_cols, 'col', attrib=next_attrib_col)
# интервал для текущей колонки: min = max = col
dst_col.attrib["min"] = str(col_index)
dst_col.attrib["max"] = str(col_index)
dst_col.attrib["width"] = src_col.attrib["width"]
[docs] def set_columns_width(self, begin, end, start_cell):
"""
Копирование ширины колонок
:param begin: начало секции, пример ('A', 1)
:type begin: 2-tuple
:param end: конец секции, пример ('E', 6)
:type end: 2-tuple
:param start_cell: ячейка с которой выводилась секция
:type start_cell: 2-tuple
"""
# определим интервал столбцов из которых надо взять ширину
end = self.get_cell_end(end)
cols = list(ColumnHelper.get_range(begin[0], end[0]))
# определим интервал столбцов, начинаемый со столбца начальной ячейки, куда надо прописать ширину
end_col = ColumnHelper.add(start_cell[0], ColumnHelper.difference(end[0], begin[0]))
new_cols = list(ColumnHelper.get_range(start_cell[0], end_col))
# запишем ширину столбцов в интервал
for index, src_col in enumerate(cols):
dst_col = new_cols[index]
src_col_el = self._create_or_get_output_col(src_col)
# если нет исходной колонки, то не надо копировать
if not src_col_el is None:
# копируем данные
attrib_col = dict(src_col_el.items())
dst_col_el = self._create_or_get_output_col(dst_col, attrib_col)
# записываем в новую колонку
self._set_new_column_width(dst_col, src_col_el, dst_col_el)
[docs] def get_rowbreaks(self):
u"""
Получение разрывов строк
"""
breaks = [elem.attrib['id'] for elem in self.write_rowbreaks.getchildren()]
return tuple(breaks)
[docs] def get_colbreaks(self):
u"""
Получение разрывов колонок
"""
breaks = [elem.attrib['id'] for elem in self.write_colbreaks.getchildren()]
return tuple(breaks)
def _set_colpagebreaks(self, colbreaks_list):
"""
Добавление разделителей страниц по колонкам
:param colbreaks_list:
:type colbreaks_list:
"""
# добавим разделители
for new_col_index in colbreaks_list:
# проверим что такого разделителя еще нет
found = False
for elem in self.write_colbreaks.getchildren():
index = int(elem.attrib['id'])
if index == new_col_index:
found = True
break
# добавим
if not found:
col_break_attr = {'id': str(new_col_index), 'max': '1048575', 'man':'1'}
elem = SubElement(self.write_colbreaks, 'brk', attrib=col_break_attr)
count = int(self.write_colbreaks.get('count', 0))
man_count = int(self.write_colbreaks.get('manualBreakCount', 0))
self.write_colbreaks.attrib['count'] = str(count + 1)
self.write_colbreaks.attrib['manualBreakCount'] = str(man_count + 1)
def _set_rowpagebreaks(self, rowbreaks_list):
"""
Добавление разделителей страниц по строкам
:param rowbreaks_list:
:type rowbreaks_list:
"""
# добавим разделители
for new_row_index in rowbreaks_list:
# проверим что такого разделителя еще нет
found = False
for elem in self.write_rowbreaks.getchildren():
index = int(elem.attrib['id'])
if index == new_row_index:
found = True
break
# добавим
if not found:
row_break_attr = {'id': str(new_row_index), 'max': '16383', 'man':'1'}
elem = SubElement(self.write_rowbreaks, 'brk', attrib=row_break_attr)
count = int(self.write_rowbreaks.get('count', 0))
man_count = int(self.write_rowbreaks.get('manualBreakCount', 0))
self.write_rowbreaks.attrib['count'] = str(count + 1)
self.write_rowbreaks.attrib['manualBreakCount'] = str(man_count + 1)
[docs] def set_pagebreaks(self, begin, end, start_cell):
"""
Копирование разделителей страниц
:param begin: начало секции, пример ('A', 1)
:type begin: 2-tuple
:param end: конец секции, пример ('E', 6)
:type end: 2-tuple
:param start_cell: ячейка с которой выводилась секция
:type start_cell: 2-tuple
"""
# определим интервал столбцов и колонок из которых надо взять разделители
end = self.get_cell_end(end)
begin_col = ColumnHelper.column_to_number(begin[0])
end_col = ColumnHelper.column_to_number(end[0])
begin_row = int(begin[1])
end_row = int(end[1])
# определим интервал столбцов и колонок, начинаемый с начальной ячейки, куда надо добавить разделители
new_begin_col = ColumnHelper.column_to_number(start_cell[0])
new_begin_row = int(start_cell[1])
# вытащим смещение индексов столбцов у которых есть разделители и которые попали в этот интервал
colbreaks = []
if self.read_colbreaks is not None:
for elem in self.read_colbreaks.getchildren():
col_index = int(elem.attrib['id'])
if begin_col <= col_index - 1 <= end_col:
colbreaks.append(col_index - begin_col + new_begin_col)
self._set_colpagebreaks(colbreaks)
# вытащим смещение индексов строк у которых есть разделители и которые попали в этот интервал
rowbreaks = []
if self.read_rowbreaks is not None:
for elem in self.read_rowbreaks.getchildren():
row_index = int(elem.attrib['id'])
if begin_row <= row_index <= end_row:
rowbreaks.append(row_index - begin_row + new_begin_row)
self._set_rowpagebreaks(rowbreaks)
[docs] def prepare_merge(self, begin_new_merge, end_new_merge):
"""
Если в документе имеются объединенные ячейки и мы добавляем свою
область перес. данную, то необходимо прежде всего удалить,
то что уже имеется.
:param begin_new_merge: начало диапазона
:type begin_new_merge: 2-tuple
:param end_new_merge: конец диапазона
:type end_new_merge: 2-tuple
"""
begin_new_column, begin_new_row = get_addr_cell(begin_new_merge)
end_new_column, end_new_row = get_addr_cell(end_new_merge)
# Все смерженные ячейки на листе
merge_cells = self._write_xml.xpath('.//mergeCell')
for merge_cell in merge_cells:
ref_attr = merge_cell.attrib.get('ref') # D1:D3 Например
begin_old_merge, end_old_merge = ref_attr.split(':') # D1, D3
begin_old_column, begin_old_row = get_addr_cell(begin_old_merge)
end_old_column, end_old_row = get_addr_cell(end_old_merge)
if not (begin_new_column > end_old_column or end_new_column < begin_old_column or begin_new_row > end_old_row or
end_new_row < begin_old_row):
self.write_merge_cell.remove(merge_cell)
[docs]class Section(SpreadsheetSection, ISpreadsheetSection):
u"""
Секция отчета
"""
def __init__(self, sheet_data, name, begin=None, end=None):
"""
:param sheet_data: Данные листа
:param name: Название секции
:param begin: Начало секции, пример ('A', 1)
:param end: Конец секции, пример ('E', 6)
"""
super(Section, self).__init__(sheet_data, name, begin, end)
# Ссылка на курсор листа. Метод flush вставляет данные относительно курсора
# и меняет его местоположение
assert isinstance(sheet_data, SheetData)
self.sheet_data = sheet_data
def __str__(self):
return 'Section "{0} - ({1},{2}) \n\t sheet_data - {3}" '.format(
self.name, self.begin, self.end, self.sheet_data)
def __repr__(self):
return self.__str__()
[docs] def flush(
self, params, oriented=ISpreadsheetSection.LEFT_DOWN,
used_formulas=None
):
"""
Вывод. Имеется два механизма вывода.
Для использования старого не передавать direction
:param params: параметры замены
:type params: dict
:param oriented: направление ориентации
:type oriented: ISpreadsheetSection
:param used_formulas: использованные формулы
:type used_formulas: dict
"""
assert isinstance(params, dict)
assert oriented in (Section.VERTICAL,
Section.HORIZONTAL,
Section.LEFT_DOWN,
Section.RIGHT_UP,
Section.RIGHT,
Section.HIERARCHICAL)
# Тут смещение курсора, копирование данных из листа и общих строк
# Генерация новых данных и новых общих строк
cursor = self.sheet_data.cursor
begin_col, begin_row = self.begin
end_col, end_row = self.sheet_data.get_cell_end(self.end)
current_col, current_row = CalculateNextCursorXLSX().get_next_cursor(cursor, (begin_col, begin_row),
(end_col, end_row), oriented, self)
self.sheet_data.last_section.row = (current_col, current_row)
self.sheet_data.last_section.column = (ColumnHelper.add(current_col,
ColumnHelper.difference(end_col, begin_col)), current_row + end_row - begin_row)
self.sheet_data.flush(self.begin, self.end, (current_col, current_row),
params, used_formulas)
[docs] def get_all_parameters(self):
u"""
Возвращает все параметры секции
"""
return self.sheet_data.find_all_parameters(self.begin, self.end)
[docs] def get_width(self):
u"""
Получение ширины секции
"""
begin_col, begin_row = self.begin
end_col, end_row = self.sheet_data.get_cell_end(self.end)
return ColumnHelper.difference(end_col, begin_col) + 1
[docs]class MergeXLSX(AbstractMerge):
u"""
Менеджер контекста для объединения ячеек
"""
def _merge(self):
begin_merge = ''.join([self._begin_merge_col, str(self.begin_row_merge)])
end_merge = ''.join([self._end_merge_col, str(self.end_row_merge)])
attrib = {'ref': ':'.join([begin_merge, end_merge])}
if self.section.sheet_data.write_merge_cell is None:
self.section.sheet_data.write_merge_cell = SubElement(self.section.sheet_data._write_xml, 'mergeCells')
self.sheet_data.prepare_merge(begin_merge, end_merge)
SubElement(self.section.sheet_data.write_merge_cell, 'mergeCell', attrib)
def _calculate_merge_column(self, column):
column_number = ColumnHelper.column_to_number(column)
first_section_column = ColumnHelper.number_to_column(column_number - self.section.get_width())
last_section_column = ColumnHelper.number_to_column(column_number - 1)
return first_section_column, last_section_column