forked from ScoDoc/ScoDoc
apres refactor merge cells
This commit is contained in:
parent
7427db0ba6
commit
66480f0d11
@ -1,84 +0,0 @@
|
||||
import openpyxl.cell
|
||||
from openpyxl.cell import Cell
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
from app.but.prepajury_xl import (
|
||||
ScoExcelSheet,
|
||||
)
|
||||
from app.but.prepajury_xl_format import (
|
||||
SCO_HALIGN,
|
||||
SCO_VALIGN,
|
||||
SCO_FONTNAME,
|
||||
SCO_FONTSIZE,
|
||||
FMT,
|
||||
Sco_Style,
|
||||
)
|
||||
|
||||
base_signature = (
|
||||
FMT.FONT_NAME.set(SCO_FONTNAME.FONTNAME_CALIBRI)
|
||||
+ FMT.FONT_SIZE.set(SCO_FONTSIZE.FONTSIZE_13)
|
||||
+ FMT.ALIGNMENT_HALIGN.set(SCO_HALIGN.HALIGN_CENTER)
|
||||
+ FMT.ALIGNMENT_VALIGN.set(SCO_VALIGN.VALIGN_CENTER)
|
||||
)
|
||||
|
||||
|
||||
def set_cell(
|
||||
ws: Cell,
|
||||
row: int,
|
||||
column: int,
|
||||
text: str = "",
|
||||
from_signature: int = 0,
|
||||
composition: list = [],
|
||||
):
|
||||
cell = ws.cell(row, column)
|
||||
cell.value = text
|
||||
sign = FMT.compose(composition, from_signature)
|
||||
style: Sco_Style = FMT.style(sign)
|
||||
style.apply(cell)
|
||||
|
||||
|
||||
class Merge_Engine:
|
||||
def __init__(self, ws: Worksheet, start_row: int = 1, start_column: int = 1):
|
||||
self.start_row: int = start_row
|
||||
self.start_column: int = start_column
|
||||
self.ws: Worksheet = ws
|
||||
|
||||
def close(self, end_row=None, end_column=None):
|
||||
if end_row is None:
|
||||
end_row = self.start_row
|
||||
if end_column is None:
|
||||
end_column = self.start_column
|
||||
if (end_row - self.start_row > 0) or (end_column - self.start_column > 0):
|
||||
self.ws.merge_cells(
|
||||
start_row=self.start_row,
|
||||
start_column=self.start_column,
|
||||
end_row=end_row,
|
||||
end_column=end_column,
|
||||
)
|
||||
if end_row is not None:
|
||||
end_row = end_row + 1
|
||||
if end_column is not None:
|
||||
end_column = end_column + 1
|
||||
|
||||
|
||||
class Sco_Cell:
|
||||
def __init__(
|
||||
self, text: str = None, signature: int = base_signature, comment: str = None
|
||||
):
|
||||
self.text = text
|
||||
self.signature = signature or base_signature
|
||||
self.comment = comment
|
||||
|
||||
def format(self, value=None):
|
||||
self.signature = FMT.ALL.composante.write(self.signature, value)
|
||||
|
||||
def copy(self, cell: openpyxl.cell.Cell):
|
||||
cell.value = self.text
|
||||
style: Sco_Style = FMT.style(self.signature)
|
||||
style.apply(cell)
|
||||
|
||||
def make_cell(self, worksheet: ScoExcelSheet):
|
||||
cell = worksheet.make_cell(self.text or "")
|
||||
style: Sco_Style = FMT.style(self.signature)
|
||||
style.apply(cell)
|
||||
return cell
|
@ -1,7 +1,5 @@
|
||||
import openpyxl
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
from app.but.prepajury_cells import Sco_Cell, base_signature, set_cell, Merge_Engine
|
||||
from app.but.prepajury_xl_format import (
|
||||
SCO_COLORS,
|
||||
FMT,
|
||||
@ -10,9 +8,17 @@ from app.but.prepajury_xl_format import (
|
||||
SCO_HALIGN,
|
||||
SCO_BORDERTHICKNESS,
|
||||
Sco_Style,
|
||||
HAIR_BLACK,
|
||||
SCO_NUMBER_FORMAT,
|
||||
)
|
||||
from app.models import ApcCompetence, ApcParcours
|
||||
from app.but.prepajury_xl import ScoExcelBook, ScoExcelSheet
|
||||
from app.but.prepajury_xl import (
|
||||
ScoExcelBook,
|
||||
ScoExcelSheet,
|
||||
base_signature,
|
||||
Frame_Engine,
|
||||
Merge_Engine,
|
||||
)
|
||||
|
||||
|
||||
def parite(semestre_idx):
|
||||
@ -39,54 +45,6 @@ header_colors = {
|
||||
}
|
||||
|
||||
|
||||
class _Header:
|
||||
def __init__(self):
|
||||
self.lines: list[list[Sco_Cell]] = []
|
||||
self.presets = [None, None, None, None]
|
||||
self.signatures = [
|
||||
base_signature,
|
||||
base_signature,
|
||||
base_signature,
|
||||
base_signature,
|
||||
]
|
||||
|
||||
def setLabelOnce(self, rang: int, label: str):
|
||||
self.presets[rang] = label
|
||||
|
||||
def setSignatures(self, rangs: list[int], signature: int):
|
||||
for rang in rangs:
|
||||
self.signatures[rang] = signature
|
||||
|
||||
def add_column(
|
||||
self,
|
||||
label0=None,
|
||||
label1=None,
|
||||
label2=None,
|
||||
label3=None,
|
||||
labels: list[str] = None,
|
||||
):
|
||||
if labels is None:
|
||||
labels = [label0, label1, label2, label3]
|
||||
cells: list(Sco_Cell) = [
|
||||
Sco_Cell(preset or label, signature=signature)
|
||||
for label, preset, signature in zip(labels, self.presets, self.signatures)
|
||||
]
|
||||
self.presets = [None, None, None, None]
|
||||
self.lines.append(cells)
|
||||
|
||||
def extend(self, header):
|
||||
self.lines.extend(header.lines)
|
||||
|
||||
def write(self, worksheet: ScoExcelSheet):
|
||||
column = 1
|
||||
for items in self.lines:
|
||||
items[0].copy(worksheet.ws.cell(1, column))
|
||||
items[1].copy(worksheet.ws.cell(2, column))
|
||||
items[2].copy(worksheet.ws.cell(3, column))
|
||||
items[3].copy(worksheet.ws.cell(4, column))
|
||||
column += 1
|
||||
|
||||
|
||||
class NiveauDesc:
|
||||
def __init__(self, scodocNiveau):
|
||||
self.fromScodoc = scodocNiveau
|
||||
@ -167,14 +125,12 @@ class AnneeDesc:
|
||||
for _ in range(3):
|
||||
header.add_column()
|
||||
|
||||
def generate_header(self, ws: Worksheet, column: int):
|
||||
def generate_header(self, ws: ScoExcelSheet, column: int):
|
||||
start = column
|
||||
but_signature = FMT.FILL_BGCOLOR.write(
|
||||
signature=base_signature, value=header_colors[self.codeAnnee]["BUT"].value
|
||||
)
|
||||
but_style = FMT.style(but_signature)
|
||||
set_cell(
|
||||
ws,
|
||||
ws.set_cell(
|
||||
1,
|
||||
column,
|
||||
text=self.codeAnnee,
|
||||
@ -184,25 +140,24 @@ class AnneeDesc:
|
||||
(FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value),
|
||||
],
|
||||
)
|
||||
but_style.apply(ws.cell(1, column))
|
||||
# for niveau in self.niveaux.values():
|
||||
# column = niveau.generate_header(ws, column)
|
||||
# for i in range(len(self.niveaux), 6):
|
||||
# column = self.generate_blank_niveau(ws, column)
|
||||
ws.cell(2, column).value = "Année"
|
||||
ws.set_cell(2, column, "Année")
|
||||
# cell_format(ws.cell(2, column), but_signature, [(FMT.FONT_BOLD, True)])
|
||||
ws.cell(3, column).value = "Nb"
|
||||
ws.cell(4, column).value = "RCUE"
|
||||
ws.set_cell(3, column, "Nb")
|
||||
ws.set_cell(4, column, "RCUE")
|
||||
column += 1
|
||||
ws.cell(2, column).value = "Année"
|
||||
ws.cell(4, column).value = "Moy."
|
||||
ws.set_cell(2, column, "Année")
|
||||
ws.set_cell(4, column, "Moy.")
|
||||
column += 1
|
||||
if self.codeAnnee == "BUT2":
|
||||
ws.cell(3, column).value = "DUT"
|
||||
ws.cell(3, column).value = "Rés."
|
||||
ws.set_cell(3, column, "DUT")
|
||||
ws.set_cell(3, column, "Rés.")
|
||||
if self.codeAnnee == "BUT3":
|
||||
ws.cell(3, column).value = "BUT"
|
||||
ws.cell(3, column).value = "Rés."
|
||||
ws.set_cell(3, column, "BUT")
|
||||
ws.set_cell(3, column, "Rés.")
|
||||
# ws.merge_cells(start_column=start, end_column=column, start_row=1, end_row=1)
|
||||
return column
|
||||
|
||||
@ -211,18 +166,15 @@ class ParcoursDesc:
|
||||
signature_header = FMT.compose(
|
||||
[
|
||||
(FMT.FILL_BGCOLOR, SCO_COLORS.LIGHT_YELLOW.value),
|
||||
(FMT.FONT_BOLD, True),
|
||||
(FMT.FONT_SIZE, SCO_FONTSIZE.FONTSIZE_13.value),
|
||||
(FMT.ALIGNMENT_VALIGN, SCO_VALIGN.VALIGN_CENTER.value),
|
||||
(FMT.ALIGNMENT_HALIGN, SCO_HALIGN.HALIGN_CENTER.value),
|
||||
(FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value),
|
||||
(FMT.BORDER_RIGHT_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value),
|
||||
(FMT.BORDER_TOP_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value),
|
||||
(FMT.BORDER_BOTTOM_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value),
|
||||
(FMT.BORDER_LEFT_COLOR, SCO_COLORS.BLACK.value),
|
||||
(FMT.BORDER_RIGHT_COLOR, SCO_COLORS.BLACK.value),
|
||||
(FMT.BORDER_TOP_COLOR, SCO_COLORS.BLACK.value),
|
||||
(FMT.BORDER_RIGHT_COLOR, SCO_COLORS.BLACK.value),
|
||||
(FMT.NUMBER_FORMAT, SCO_NUMBER_FORMAT.NUMBER_GENERAL.value),
|
||||
# (FMT.FONT_BOLD, True),
|
||||
# (FMT.FONT_SIZE, SCO_FONTSIZE.FONTSIZE_13.value),
|
||||
# (FMT.ALIGNMENT_VALIGN, SCO_VALIGN.VALIGN_CENTER.value),
|
||||
# (FMT.ALIGNMENT_HALIGN, SCO_HALIGN.HALIGN_CENTER.value),
|
||||
# (FMT.BORDER_RIGHT, HAIR_BLACK),
|
||||
# (FMT.BORDER_LEFT, HAIR_BLACK),
|
||||
# (FMT.BORDER_TOP, HAIR_BLACK),
|
||||
# (FMT.BORDER_BOTTOM, HAIR_BLACK),
|
||||
],
|
||||
base_signature,
|
||||
)
|
||||
@ -271,54 +223,27 @@ class ParcoursDesc:
|
||||
cells4.append(worksheet.make_cell(val4))
|
||||
|
||||
def handle_description(
|
||||
self, ws: Worksheet, description: tuple, row: int, column: int
|
||||
self, ws: ScoExcelSheet, description: tuple, row: int, column: int
|
||||
) -> int:
|
||||
title, content_list = description
|
||||
style = FMT.style(self.signature_header)
|
||||
ws.cell(row, column).value = title
|
||||
style.apply(ws.cell(row, column))
|
||||
merge_h = Merge_Engine(ws=ws, start_row=row, start_column=column)
|
||||
merge_v = Merge_Engine(ws=ws, start_row=row, start_column=column)
|
||||
ws.set_cell(
|
||||
row=row,
|
||||
column=column,
|
||||
text=title,
|
||||
from_signature=self.signature_header,
|
||||
)
|
||||
merge_h = ws.get_merge_engine(start_row=row, start_column=column)
|
||||
merge_v = ws.get_merge_engine(start_row=row, start_column=column)
|
||||
if content_list is None:
|
||||
merge_v.close(end_row=4)
|
||||
column += 1
|
||||
else:
|
||||
first = column
|
||||
for content in content_list:
|
||||
column = self.handle_description(ws, content, row + 1, column)
|
||||
merge_h.close(end_column=column - 1)
|
||||
# left_medium = FMT.compose(
|
||||
# self.signature_header,
|
||||
# [
|
||||
# (FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value),
|
||||
# (FMT.BORDER_LEFT_COLOR, SCO_COLORS.BLACK.value),
|
||||
# ],
|
||||
# )
|
||||
# right_medium = FMT.compose(
|
||||
# self.signature_header,
|
||||
# [
|
||||
# (FMT.BORDER_RIGHT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value),
|
||||
# (FMT.BORDER_RIGHT_COLOR, SCO_COLORS.BLACK.value),
|
||||
# ],
|
||||
# )
|
||||
# top_medium = FMT.compose(
|
||||
# self.signature_header,
|
||||
# [
|
||||
# (FMT.BORDER_TOP_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value),
|
||||
# (FMT.BORDER_TOP_COLOR, SCO_COLORS.BLACK.value),
|
||||
# ],
|
||||
# )
|
||||
# FMT.get_style(FMT.ALL, left_medium).apply(ws.cell(1, 1))
|
||||
# FMT.get_style(FMT.ALL, top_medium).apply(ws.cell(1, 1))
|
||||
# FMT.get_style(FMT.ALL, right_medium).apply(ws.cell(1, 1))
|
||||
# FMT.get_style(FMT.ALL, left_medium).apply(ws.cell(2, 1))
|
||||
# FMT.get_style(FMT.ALL, right_medium).apply(ws.cell(2, column - 1))
|
||||
# FMT.get_style(FMT.ALL, right_medium).apply(ws.cell(3, column - 1))
|
||||
# FMT.get_style(FMT.ALL, right_medium).apply(ws.cell(4, column - 1))
|
||||
# merge_h.close(end_column=column)
|
||||
return column
|
||||
|
||||
def generate_etudiant_header(self, ws: Worksheet) -> int:
|
||||
def generate_etudiant_header(self, ws: ScoExcelSheet) -> int:
|
||||
titles = (
|
||||
"ETUDIANT",
|
||||
[
|
||||
@ -338,19 +263,23 @@ class ParcoursDesc:
|
||||
),
|
||||
],
|
||||
)
|
||||
frame = ws.get_frame_engine(
|
||||
1, 1, SCO_BORDERTHICKNESS.BORDER_THICK, SCO_COLORS.BLUE
|
||||
)
|
||||
column = self.handle_description(ws, titles, 1, 1)
|
||||
frame.close(4, column - 1)
|
||||
|
||||
return column
|
||||
|
||||
def generate_header(self, ws: Worksheet):
|
||||
def generate_header(self, ws: ScoExcelSheet):
|
||||
column: int = self.generate_etudiant_header(ws)
|
||||
for codeAnnee in listeAnnees:
|
||||
column = self.annees[codeAnnee].generate_header(ws, column)
|
||||
|
||||
def generate(self, workbook: ScoExcelBook):
|
||||
header = _Header()
|
||||
if self.fromScodoc:
|
||||
sheet_name = self.fromScodoc.code
|
||||
else:
|
||||
sheet_name = "TC"
|
||||
worksheet: ScoExcelSheet = workbook.create_sheet(sheet_name)
|
||||
self.generate_header(worksheet.ws)
|
||||
self.generate_header(worksheet)
|
||||
|
@ -27,10 +27,23 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from openpyxl.cell import WriteOnlyCell
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
from app.but.prepajury_xl_format import Sco_Style
|
||||
from app.but.prepajury_xl_format import (
|
||||
Sco_Style,
|
||||
FMT,
|
||||
SCO_FONTNAME,
|
||||
SCO_FONTSIZE,
|
||||
SCO_HALIGN,
|
||||
SCO_VALIGN,
|
||||
SCO_NUMBER_FORMAT,
|
||||
SCO_BORDERTHICKNESS,
|
||||
SCO_COLORS,
|
||||
fmt_atomics,
|
||||
)
|
||||
|
||||
""" Excel file handling
|
||||
"""
|
||||
@ -51,6 +64,93 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
# font, border, number_format, fill,...
|
||||
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
|
||||
|
||||
base_signature = (
|
||||
FMT.FONT_NAME.set(SCO_FONTNAME.FONTNAME_CALIBRI)
|
||||
+ FMT.FONT_SIZE.set(SCO_FONTSIZE.FONTSIZE_13)
|
||||
+ FMT.ALIGNMENT_HALIGN.set(SCO_HALIGN.HALIGN_CENTER)
|
||||
+ FMT.ALIGNMENT_VALIGN.set(SCO_VALIGN.VALIGN_CENTER)
|
||||
+ FMT.NUMBER_FORMAT.set(SCO_NUMBER_FORMAT.NUMBER_GENERAL)
|
||||
)
|
||||
|
||||
|
||||
class Sco_Cell:
|
||||
def __init__(self, text: str = "", signature: int = 0):
|
||||
self.text = text
|
||||
self.signature = signature
|
||||
|
||||
def alter(self, signature: int):
|
||||
for fmt in fmt_atomics:
|
||||
value: int = fmt.composante.read(signature)
|
||||
if value > 0:
|
||||
self.signature = fmt.write(value, self.signature)
|
||||
|
||||
def build(self, ws: Worksheet, row: int, column: int):
|
||||
cell = ws.cell(row, column)
|
||||
cell.value = self.text
|
||||
FMT.ALL.apply(cell=cell, signature=self.signature)
|
||||
|
||||
|
||||
class Frame_Engine:
|
||||
def __init__(
|
||||
self,
|
||||
ws: ScoExcelSheet,
|
||||
start_row: int = 1,
|
||||
start_column: int = 1,
|
||||
thickness: SCO_BORDERTHICKNESS = SCO_BORDERTHICKNESS.NONE,
|
||||
color: SCO_COLORS = SCO_COLORS.BLACK,
|
||||
):
|
||||
self.start_row: int = start_row
|
||||
self.start_column: int = start_column
|
||||
self.ws: ScoExcelSheet = ws
|
||||
self.border_style = FMT.BORDER_LEFT.make_zero_based_constant([thickness, color])
|
||||
|
||||
def close(self, end_row: int, end_column: int):
|
||||
left_signature: int = FMT.BORDER_LEFT.write(self.border_style)
|
||||
right_signature: int = FMT.BORDER_RIGHT.write(self.border_style)
|
||||
top_signature: int = FMT.BORDER_TOP.write(self.border_style)
|
||||
bottom_signature: int = FMT.BORDER_BOTTOM.write(self.border_style)
|
||||
for row in range(self.start_row, end_row + 1):
|
||||
self.ws.cells[row][self.start_column].alter(left_signature)
|
||||
self.ws.cells[row][end_column].alter(right_signature)
|
||||
for column in range(self.start_column, end_column + 1):
|
||||
self.ws.cells[self.start_row][column].alter(top_signature)
|
||||
self.ws.cells[end_row][column].alter(bottom_signature)
|
||||
|
||||
|
||||
class Merge_Engine:
|
||||
def __init__(self, start_row: int = 1, start_column: int = 1):
|
||||
self.start_row: int = start_row
|
||||
self.start_column: int = start_column
|
||||
self.end_row: int = None
|
||||
self.end_column: int = None
|
||||
self.closed: bool = False
|
||||
|
||||
def close(self, end_row=None, end_column=None):
|
||||
if end_row is None:
|
||||
self.end_row = self.start_row + 1
|
||||
else:
|
||||
self.end_row = end_row + 1
|
||||
if end_column is None:
|
||||
self.end_column = self.start_column + 1
|
||||
else:
|
||||
self.end_column = end_column + 1
|
||||
self.closed = True
|
||||
|
||||
def write(self, ws: Worksheet):
|
||||
if self.closed:
|
||||
if (self.end_row - self.start_row > 0) and (
|
||||
self.end_column - self.start_column > 0
|
||||
):
|
||||
ws.merge_cells(
|
||||
start_row=self.start_row,
|
||||
start_column=self.start_column,
|
||||
end_row=self.end_row - 1,
|
||||
end_column=self.end_column - 1,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"( {self.start_row}:{self.start_column}-{self.end_row}:{self.end_column})[{self.closed}]"
|
||||
|
||||
|
||||
def xldate_as_datetime(xldate, datemode=0):
|
||||
"""Conversion d'une date Excel en datetime python
|
||||
@ -90,14 +190,14 @@ class ScoExcelBook:
|
||||
self.sheets = [] # list of sheets
|
||||
self.wb = Workbook()
|
||||
|
||||
def create_sheet(self, sheet_name="feuille", default_style=None):
|
||||
def create_sheet(self, sheet_name="feuille", default_signature: int = 0):
|
||||
"""Crée une nouvelle feuille dans ce classeur
|
||||
sheet_name -- le nom de la feuille
|
||||
default_style -- le style par défaut
|
||||
"""
|
||||
sheet_name = adjust_sheetname(sheet_name)
|
||||
ws = self.wb.create_sheet(sheet_name)
|
||||
sheet = ScoExcelSheet(sheet_name, default_style, ws)
|
||||
sheet = ScoExcelSheet(sheet_name, default_signature=default_signature, ws=ws)
|
||||
self.sheets.append(sheet)
|
||||
return sheet
|
||||
|
||||
@ -128,7 +228,12 @@ class ScoExcelSheet:
|
||||
* pour finir appel de la méthode de génération
|
||||
"""
|
||||
|
||||
def __init__(self, sheet_name="feuille", default_style=None, wb: Workbook = None):
|
||||
def __init__(
|
||||
self,
|
||||
sheet_name: str = "feuille",
|
||||
default_signature: int = 0,
|
||||
ws: Worksheet = None,
|
||||
):
|
||||
"""Création de la feuille. sheet_name
|
||||
-- le nom de la feuille default_style
|
||||
-- le style par défaut des cellules ws
|
||||
@ -138,19 +243,42 @@ class ScoExcelSheet:
|
||||
# Le nom de la feuille ne peut faire plus de 31 caractères.
|
||||
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
|
||||
self.sheet_name = adjust_sheetname(sheet_name)
|
||||
self.default_style = default_style or Sco_Style()
|
||||
if wb is None:
|
||||
self.default_signature = default_signature
|
||||
self.merges: list[Merge_Engine] = []
|
||||
if ws is None:
|
||||
self.wb = Workbook()
|
||||
self.ws = self.wb.active
|
||||
self.ws.title = self.sheet_name
|
||||
else:
|
||||
self.wb = None
|
||||
self.ws = wb
|
||||
self.ws = ws
|
||||
# internal data
|
||||
self.rows = [] # list of list of cells
|
||||
self.cells = defaultdict(lambda: defaultdict(Sco_Cell))
|
||||
self.column_dimensions = {}
|
||||
self.row_dimensions = {}
|
||||
|
||||
def get_frame_engine(
|
||||
self,
|
||||
start_row: int,
|
||||
start_column: int,
|
||||
thickness: SCO_BORDERTHICKNESS = SCO_BORDERTHICKNESS.NONE,
|
||||
color: SCO_COLORS = SCO_COLORS.NONE,
|
||||
):
|
||||
return Frame_Engine(
|
||||
ws=self,
|
||||
start_row=start_row,
|
||||
start_column=start_column,
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
|
||||
def get_merge_engine(self, start_row: int, start_column: int):
|
||||
merge_engine: Merge_Engine = Merge_Engine(
|
||||
start_row=start_row, start_column=start_column
|
||||
)
|
||||
self.merges.append(merge_engine)
|
||||
return merge_engine
|
||||
|
||||
@staticmethod
|
||||
def i2col(idx):
|
||||
if idx < 26: # one letter key
|
||||
@ -160,6 +288,18 @@ class ScoExcelSheet:
|
||||
second = (idx % 26) + 65
|
||||
return "" + chr(first) + chr(second)
|
||||
|
||||
def set_cell(
|
||||
self,
|
||||
row: int,
|
||||
column: int,
|
||||
text: str = "",
|
||||
from_signature: int = 0,
|
||||
composition: list = [],
|
||||
):
|
||||
cell: Sco_Cell = self.cells[row][column]
|
||||
cell.text = text
|
||||
self.signature = FMT.compose(composition, from_signature)
|
||||
|
||||
def set_column_dimension_width(self, cle=None, value=21):
|
||||
"""Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
|
||||
value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
|
||||
@ -192,83 +332,62 @@ class ScoExcelSheet:
|
||||
"""
|
||||
self.ws.row_dimensions[cle].hidden = value
|
||||
|
||||
def make_cell(self, value: any = None, style: Sco_Style = None, comment=None):
|
||||
"""Construit une cellule.
|
||||
value -- contenu de la cellule (texte, numérique, booléen ou date)
|
||||
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||
"""
|
||||
# adaptation des valeurs si nécessaire
|
||||
if value is None:
|
||||
value = ""
|
||||
elif value is True:
|
||||
value = 1
|
||||
elif value is False:
|
||||
value = 0
|
||||
elif isinstance(value, datetime.datetime):
|
||||
value = value.replace(
|
||||
tzinfo=None
|
||||
) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
|
||||
|
||||
# création de la cellule
|
||||
cell = WriteOnlyCell(self.ws, value)
|
||||
|
||||
if style is not None:
|
||||
style.apply(cell)
|
||||
|
||||
if not comment is None:
|
||||
cell.comment = Comment(comment, "scodoc")
|
||||
lines = comment.splitlines()
|
||||
cell.comment.width = 7 * max([len(line) for line in lines]) if lines else 7
|
||||
cell.comment.height = 20 * len(lines) if lines else 20
|
||||
|
||||
# test datatype to overwrite datetime format
|
||||
if isinstance(value, datetime.date):
|
||||
cell.data_type = "d"
|
||||
cell.number_format = FORMAT_DATE_DDMMYY
|
||||
elif isinstance(value, int) or isinstance(value, float):
|
||||
cell.data_type = "n"
|
||||
else:
|
||||
cell.data_type = "s"
|
||||
|
||||
return cell
|
||||
|
||||
def make_row(self, values: list, style=None, comments=None) -> list:
|
||||
"build a row"
|
||||
# TODO make possible differents styles in a row
|
||||
if comments is None:
|
||||
comments = [None] * len(values)
|
||||
return [
|
||||
self.make_cell(value, style, comment)
|
||||
for value, comment in zip(values, comments)
|
||||
]
|
||||
|
||||
def append_single_cell_row(self, value: any, style=None):
|
||||
"""construit une ligne composée d'une seule cellule et l'ajoute à la feuille.
|
||||
mêmes paramètres que make_cell:
|
||||
value -- contenu de la cellule (texte ou numérique)
|
||||
style -- style par défaut de la feuille si non spécifié
|
||||
"""
|
||||
self.append_row([self.make_cell(value, style)])
|
||||
|
||||
def append_blank_row(self):
|
||||
"""construit une ligne vide et l'ajoute à la feuille."""
|
||||
self.append_row([None])
|
||||
|
||||
def append_row(self, row):
|
||||
"""ajoute une ligne déjà construite à la feuille."""
|
||||
self.rows.append(row)
|
||||
# def make_cell(self, value: any = None, style: Sco_Style = None, comment=None):
|
||||
# """Construit une cellule.
|
||||
# value -- contenu de la cellule (texte, numérique, booléen ou date)
|
||||
# style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||
# """
|
||||
# # adaptation des valeurs si nécessaire
|
||||
# if value is None:
|
||||
# value = ""
|
||||
# elif value is True:
|
||||
# value = 1
|
||||
# elif value is False:
|
||||
# value = 0
|
||||
# elif isinstance(value, datetime.datetime):
|
||||
# value = value.replace(
|
||||
# tzinfo=None
|
||||
# ) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
|
||||
#
|
||||
# # création de la cellule
|
||||
# cell = WriteOnlyCell(self.ws, value)
|
||||
#
|
||||
# if style is not None:
|
||||
# style.apply(cell)
|
||||
#
|
||||
# if not comment is None:
|
||||
# cell.comment = Comment(comment, "scodoc")
|
||||
# lines = comment.splitlines()
|
||||
# cell.comment.width = 7 * max([len(line) for line in lines]) if lines else 7
|
||||
# cell.comment.height = 20 * len(lines) if lines else 20
|
||||
#
|
||||
# # test datatype to overwrite datetime format
|
||||
# if isinstance(value, datetime.date):
|
||||
# cell.data_type = "d"
|
||||
# cell.number_format = FORMAT_DATE_DDMMYY
|
||||
# elif isinstance(value, int) or isinstance(value, float):
|
||||
# cell.data_type = "n"
|
||||
# else:
|
||||
# cell.data_type = "s"
|
||||
#
|
||||
# return cell
|
||||
|
||||
def prepare(self):
|
||||
"""génére un flux décrivant la feuille.
|
||||
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
|
||||
ou pour la génération d'un classeur multi-feuilles
|
||||
"""
|
||||
for row in self.column_dimensions.keys():
|
||||
self.ws.column_dimensions[row] = self.column_dimensions[row]
|
||||
for row in self.row_dimensions.keys():
|
||||
self.ws.row_dimensions[row] = self.row_dimensions[row]
|
||||
for row in self.rows:
|
||||
self.ws.append(row)
|
||||
# for row in self.column_dimensions.keys():
|
||||
# self.ws.column_dimensions[row] = self.column_dimensions[row]
|
||||
# for row in self.row_dimensions.keys():
|
||||
# self.ws.row_dimensions[row] = self.row_dimensions[row]
|
||||
# for row in self.rows:
|
||||
# self.ws.append(row)
|
||||
for row in self.cells:
|
||||
for column in self.cells[row]:
|
||||
self.cells[row][column].build(self.ws, row, column)
|
||||
for merge_engine in self.merges:
|
||||
merge_engine.write(self.ws)
|
||||
|
||||
def generate(self):
|
||||
"""génération d'un classeur mono-feuille"""
|
||||
|
@ -2,6 +2,7 @@ import abc
|
||||
from enum import Enum
|
||||
|
||||
import openpyxl.styles
|
||||
from openpyxl.cell import Cell
|
||||
from openpyxl.styles import Side, Border, Font, PatternFill, Alignment
|
||||
from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00
|
||||
|
||||
@ -42,7 +43,7 @@ class SCO_BORDERTHICKNESS(Enum):
|
||||
obj.width = width
|
||||
return obj
|
||||
|
||||
BORDER_NONE = (0, None)
|
||||
NONE = (0, None)
|
||||
BORDER_HAIR = (1, "hair")
|
||||
BORDER_THIN = (2, "thin")
|
||||
BORDER_MEDIUM = (3, "medium")
|
||||
@ -56,10 +57,11 @@ class SCO_FONTNAME(Enum):
|
||||
obj.fontname = fontname
|
||||
return obj
|
||||
|
||||
FONTNAME_CALIBRI = (0, "Calibri")
|
||||
FONTNAME_ARIAL = (1, "Arial")
|
||||
FONTNAME_COURIER = (2, "Courier New")
|
||||
FONTNAME_TIMES = (3, "Times New Roman")
|
||||
NONE = (0, None)
|
||||
FONTNAME_CALIBRI = (1, "Calibri")
|
||||
FONTNAME_ARIAL = (2, "Arial")
|
||||
FONTNAME_COURIER = (3, "Courier New")
|
||||
FONTNAME_TIMES = (4, "Times New Roman")
|
||||
|
||||
|
||||
class SCO_FONTSIZE(Enum):
|
||||
@ -69,11 +71,11 @@ class SCO_FONTSIZE(Enum):
|
||||
obj.fontsize = fontsize
|
||||
return obj
|
||||
|
||||
FONTSIZE_10 = (0, 10.0)
|
||||
NONE = (0, None)
|
||||
FONTSIZE_9 = (1, 9.0)
|
||||
FONTSIZE_10 = (2, 10.0)
|
||||
FONTSIZE_11 = (2, 11.0)
|
||||
FONTSIZE_13 = (3, 13.0)
|
||||
FONTSIZE_16 = (4, 16.0)
|
||||
FONTSIZE_13 = (4, 13.0)
|
||||
|
||||
|
||||
class SCO_NUMBER_FORMAT(Enum):
|
||||
@ -83,6 +85,7 @@ class SCO_NUMBER_FORMAT(Enum):
|
||||
obj.format = format
|
||||
return obj
|
||||
|
||||
NONE = (0, None)
|
||||
NUMBER_GENERAL = (0, FORMAT_GENERAL)
|
||||
NUMBER_00 = (1, FORMAT_NUMBER_00)
|
||||
|
||||
@ -94,9 +97,10 @@ class SCO_HALIGN(Enum):
|
||||
obj.position = position
|
||||
return obj
|
||||
|
||||
HALIGN_LEFT = (0, "left")
|
||||
HALIGN_CENTER = (1, "center")
|
||||
HALIGN_RIGHT = (2, "right")
|
||||
NONE = (0, None)
|
||||
HALIGN_LEFT = (1, "left")
|
||||
HALIGN_CENTER = (2, "center")
|
||||
HALIGN_RIGHT = (3, "right")
|
||||
|
||||
|
||||
class SCO_VALIGN(Enum):
|
||||
@ -151,7 +155,7 @@ class Composante_boolean(Composante):
|
||||
def set(self, data: bool, signature=0) -> int:
|
||||
return self.write(1 if data else 0, signature)
|
||||
|
||||
def build(self, signature):
|
||||
def build(self, signature) -> bool:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
return value == 1
|
||||
@ -167,7 +171,7 @@ class Composante_number_format(Composante):
|
||||
def set(self, data: SCO_NUMBER_FORMAT, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_NUMBER_FORMAT:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
return SCO_NUMBER_FORMAT(value)
|
||||
@ -184,7 +188,7 @@ class Composante_Colors(Composante):
|
||||
def set(self, data: SCO_COLORS, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_COLORS:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
if value == 0:
|
||||
@ -205,7 +209,7 @@ class Composante_borderThickness(Composante):
|
||||
def set(self, data: SCO_BORDERTHICKNESS, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_BORDERTHICKNESS:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
try:
|
||||
@ -224,7 +228,7 @@ class Composante_fontname(Composante):
|
||||
def set(self, data: SCO_FONTNAME, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_FONTNAME:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
try:
|
||||
@ -243,14 +247,14 @@ class Composante_fontsize(Composante):
|
||||
def set(self, data: SCO_FONTSIZE, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_FONTSIZE:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
return SCO_FONTSIZE(value) or None
|
||||
|
||||
|
||||
class Composante_halign(Composante):
|
||||
WIDTH: int = 2
|
||||
WIDTH: int = 3
|
||||
|
||||
def __init__(self):
|
||||
assert (1 << self.WIDTH) > SCO_HALIGN.__len__()
|
||||
@ -259,7 +263,7 @@ class Composante_halign(Composante):
|
||||
def set(self, data: SCO_HALIGN, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_HALIGN:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
try:
|
||||
@ -269,7 +273,7 @@ class Composante_halign(Composante):
|
||||
|
||||
|
||||
class Composante_valign(Composante):
|
||||
WIDTH: int = 2
|
||||
WIDTH: int = 3
|
||||
|
||||
def __init__(self):
|
||||
assert (1 << self.WIDTH) > SCO_VALIGN.__len__()
|
||||
@ -278,7 +282,7 @@ class Composante_valign(Composante):
|
||||
def set(self, data: SCO_VALIGN, signature=0) -> int:
|
||||
return self.write(data.value, signature)
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> SCO_VALIGN:
|
||||
value = self.read(signature)
|
||||
assert value < (1 << self.width)
|
||||
try:
|
||||
@ -312,7 +316,7 @@ class Sco_BorderSide:
|
||||
|
||||
def to_openpyxl(self):
|
||||
return Side(
|
||||
border_style=self.thickness.width,
|
||||
border_style=None if self.thickness is None else self.thickness.width,
|
||||
color=None if self.color is None else self.color.argb,
|
||||
)
|
||||
|
||||
@ -350,8 +354,8 @@ class Sco_Alignment:
|
||||
|
||||
def to_openpyxl(self):
|
||||
return Alignment(
|
||||
horizontal=self.halign.position,
|
||||
vertical=self.valign.position,
|
||||
horizontal=None if self.halign is None else self.halign.position,
|
||||
vertical=None if self.valign is None else self.valign.position,
|
||||
)
|
||||
|
||||
|
||||
@ -374,8 +378,8 @@ class Sco_Font:
|
||||
|
||||
def to_openpyxl(self):
|
||||
return Font(
|
||||
name=self.name.fontname,
|
||||
size=self.fontsize.fontsize,
|
||||
name=None if self.name is None else self.name.fontname,
|
||||
size=None if self.fontsize is None else self.fontsize.fontsize,
|
||||
bold=self.bold,
|
||||
italic=self.italic,
|
||||
outline=self.outline,
|
||||
@ -392,13 +396,13 @@ class Sco_Style:
|
||||
fill: Sco_Fill = None,
|
||||
alignment: Sco_Alignment = None,
|
||||
borders: Sco_Borders = None,
|
||||
number_format: SCO_NUMBER_FORMAT = None,
|
||||
number_format: SCO_NUMBER_FORMAT = FORMAT_GENERAL,
|
||||
):
|
||||
self.font = font
|
||||
self.fill = fill
|
||||
self.alignment = alignment
|
||||
self.borders = borders
|
||||
self.number_format = number_format
|
||||
self.font = font or None
|
||||
self.fill = fill or None
|
||||
self.alignment = alignment or None
|
||||
self.borders = borders or None
|
||||
self.number_format = number_format or SCO_NUMBER_FORMAT.NUMBER_GENERAL
|
||||
|
||||
def apply(self, cell: Cell):
|
||||
if self.font:
|
||||
@ -409,8 +413,12 @@ class Sco_Style:
|
||||
cell.alignment = self.alignment.to_openpyxl()
|
||||
if self.borders:
|
||||
cell.border = self.borders.to_openpyxl()
|
||||
if self.number_format:
|
||||
cell.number_format = self.number_format.format
|
||||
cell.number_format = (
|
||||
FORMAT_GENERAL
|
||||
if self.number_format is None
|
||||
or self.number_format == SCO_NUMBER_FORMAT.NONE
|
||||
else self.number_format.format
|
||||
)
|
||||
|
||||
|
||||
# Composantes groupant d'autres composantes et dotées d'un mécanisme de cache
|
||||
@ -448,7 +456,7 @@ class Composante_fill(Composante_group):
|
||||
super().__init__([color])
|
||||
self.color = color
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_Fill:
|
||||
return Sco_Fill(color=self.color.build(signature))
|
||||
|
||||
|
||||
@ -470,7 +478,7 @@ class Composante_font(Composante_group):
|
||||
self.italic = italic
|
||||
self.outline = outline
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_Font:
|
||||
return Sco_Font(
|
||||
name=self.name.build(signature),
|
||||
fontsize=self.fontsize.build(signature),
|
||||
@ -487,7 +495,7 @@ class Composante_border(Composante_group):
|
||||
self.thick = thick
|
||||
self.color = color
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_BorderSide:
|
||||
return Sco_BorderSide(
|
||||
thickness=self.thick.build(signature),
|
||||
color=self.color.build(signature),
|
||||
@ -508,7 +516,7 @@ class Composante_borders(Composante_group):
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_Borders:
|
||||
return Sco_Borders(
|
||||
left=self.left.lookup_or_cache(signature),
|
||||
right=self.right.lookup_or_cache(signature),
|
||||
@ -523,7 +531,7 @@ class Composante_alignment(Composante_group):
|
||||
self.halign = halign
|
||||
self.valign = valign
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_Alignment:
|
||||
return Sco_Alignment(
|
||||
halign=self.halign.build(signature),
|
||||
valign=self.valign.build(signature),
|
||||
@ -547,7 +555,7 @@ class Composante_all(Composante_group):
|
||||
self.alignment = alignment
|
||||
self.number_format = number_format
|
||||
|
||||
def build(self, signature: int):
|
||||
def build(self, signature: int) -> Sco_Style:
|
||||
return Sco_Style(
|
||||
fill=self.fill.lookup_or_cache(signature),
|
||||
font=self.font.lookup_or_cache(signature),
|
||||
@ -576,14 +584,17 @@ class FMT(Enum):
|
||||
def make_zero_based_constant(self, enums: list[Enum]) -> int:
|
||||
return self.composante.make_zero_based_constant(enums=enums)
|
||||
|
||||
def apply(self, cell: Cell, signature: int):
|
||||
self.composante.build(signature).apply(cell)
|
||||
|
||||
@classmethod
|
||||
def compose(cls, composition: list, signature: int = 0) -> int:
|
||||
def compose(cls, composition: list[("FMT", int)], signature: int = 0) -> int:
|
||||
for field, value in composition:
|
||||
signature = field.write(value, signature)
|
||||
return signature
|
||||
|
||||
@classmethod
|
||||
def style(cls, signature: int):
|
||||
def style(cls, signature: int = None) -> Sco_Style:
|
||||
return FMT.ALL.get_style(signature)
|
||||
|
||||
FONT_NAME = Composante_fontname()
|
||||
@ -617,6 +628,27 @@ class FMT(Enum):
|
||||
ALL = Composante_all(FONT, FILL, BORDERS, ALIGNMENT, NUMBER_FORMAT)
|
||||
|
||||
|
||||
fmt_atomics = {
|
||||
FMT.FONT_NAME,
|
||||
FMT.FONT_SIZE,
|
||||
FMT.FONT_COLOR,
|
||||
FMT.FONT_BOLD,
|
||||
FMT.FONT_ITALIC,
|
||||
FMT.FONT_OUTLINE,
|
||||
FMT.BORDER_LEFT_STYLE,
|
||||
FMT.BORDER_LEFT_COLOR,
|
||||
FMT.BORDER_RIGHT_STYLE,
|
||||
FMT.BORDER_RIGHT_COLOR,
|
||||
FMT.BORDER_TOP_STYLE,
|
||||
FMT.BORDER_TOP_COLOR,
|
||||
FMT.BORDER_BOTTOM_STYLE,
|
||||
FMT.BORDER_BOTTOM_COLOR,
|
||||
FMT.FILL_BGCOLOR,
|
||||
FMT.ALIGNMENT_HALIGN,
|
||||
FMT.ALIGNMENT_VALIGN,
|
||||
FMT.NUMBER_FORMAT,
|
||||
}
|
||||
|
||||
HAIR_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
|
||||
enums=[SCO_BORDERTHICKNESS.BORDER_HAIR, SCO_COLORS.BLACK]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user