From c536336017901d4c9b799dcef9fb91dcfa7e7757 Mon Sep 17 00:00:00 2001 From: jmpla Date: Wed, 17 May 2023 08:07:35 +0200 Subject: [PATCH] refacto --- app/but/prepajury_but.py | 5 +- app/but/prepajury_cells.py | 39 ++++- app/but/prepajury_desc.py | 207 +++++++++++++++++++------- app/but/prepajury_xl.py | 20 +-- app/but/prepajury_xl_format.py | 261 +++++++++++++++++++++++---------- 5 files changed, 370 insertions(+), 162 deletions(-) diff --git a/app/but/prepajury_but.py b/app/but/prepajury_but.py index 0e628171e..cf1db73d4 100644 --- a/app/but/prepajury_but.py +++ b/app/but/prepajury_but.py @@ -33,7 +33,6 @@ import time from flask import flash, abort from app.but.cursus_but import EtudCursusBUT -from app.but.prepajury_cells import Cell from app.but.prepajury_desc import ParcoursDesc from app.comp import res_sem from app.comp.res_but import ResultatsSemestreBUT @@ -46,7 +45,7 @@ from app.models import ( ApcCompetence, ) import app.scodoc.sco_utils as scu -from app.scodoc.sco_excel import ScoExcelBook, ScoExcelSheet +from app.but.prepajury_xl import ScoExcelBook class _Bilan: @@ -220,8 +219,8 @@ def feuille_preparation_jury_but(formsemestre_id): abort(404) formsemestre = FormSemestre.get_formsemestre(formsemestre_id) # res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - breakpoint() """Feuille excel pour préparation des jurys adaptée pour le BUT.""" + breakpoint() compilation = _Compilation(formsemestre) compilation.computes_decision() filename = scu.sanitize_filename( diff --git a/app/but/prepajury_cells.py b/app/but/prepajury_cells.py index aa08fdd3b..b16f07688 100644 --- a/app/but/prepajury_cells.py +++ b/app/but/prepajury_cells.py @@ -1,3 +1,6 @@ +import openpyxl.cell +from openpyxl.cell import Cell + from app.but.prepajury_xl import ( ScoExcelSheet, ) @@ -11,14 +14,29 @@ from app.but.prepajury_xl_format import ( ) base_signature = ( - FMT.FONT_NAME.write(SCO_FONTNAME.FONTNAME_CALIBRI) - + FMT.FONT_SIZE.write(SCO_FONTSIZE.FONTSIZE_10) - + FMT.ALIGNMENT_HALIGN.write(SCO_HALIGN.HALIGN_CENTER) - + FMT.ALIGNEMENT_VALIGN.write(SCO_VALIGN.VALIGN_CENTER) + 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) ) -class Cell: +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 Sco_Cell: def __init__( self, text: str = None, signature: int = base_signature, comment: str = None ): @@ -26,11 +44,16 @@ class Cell: self.signature = signature or base_signature self.comment = comment - def format(self, FMT: FMT, value=None): - self.signature = FMT.composante.write(self.signature, value) + 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.ALL.get_style(self.signature) + style: Sco_Style = FMT.style(self.signature) style.apply(cell) return cell diff --git a/app/but/prepajury_desc.py b/app/but/prepajury_desc.py index ec7c4c72c..2868e6693 100644 --- a/app/but/prepajury_desc.py +++ b/app/but/prepajury_desc.py @@ -1,10 +1,18 @@ -from openpyxl.styles.colors import BLACK +import openpyxl +from openpyxl.worksheet.worksheet import Worksheet -from app.but.prepajury_cells import Cell, base_style -from app.but.prepajury_xl import Sco_Style -from app.but.prepajury_xl_format import SCO_COLORS +from app.but.prepajury_cells import Sco_Cell, base_signature, set_cell +from app.but.prepajury_xl_format import ( + SCO_COLORS, + FMT, + SCO_FONTSIZE, + SCO_VALIGN, + SCO_HALIGN, + SCO_BORDERTHICKNESS, + Sco_Style, +) from app.models import ApcCompetence, ApcParcours -from app.scodoc.sco_excel import ScoExcelBook, ScoExcelSheet +from app.but.prepajury_xl import ScoExcelBook, ScoExcelSheet def parite(semestre_idx): @@ -33,16 +41,21 @@ header_colors = { class _Header: def __init__(self): - self.lines: list[list[Cell]] = [] + self.lines: list[list[Sco_Cell]] = [] self.presets = [None, None, None, None] - self.styles = [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 setStyles(self, rangs: list[int], style: Sco_Style): + def setSignatures(self, rangs: list[int], signature: int): for rang in rangs: - self.styles[rang] = style + self.signatures[rang] = signature def add_column( self, @@ -54,9 +67,9 @@ class _Header: ): if labels is None: labels = [label0, label1, label2, label3] - cells: list(Cell) = [ - Cell(preset or label, style=style) - for label, preset, style in zip(labels, self.presets, self.styles) + 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) @@ -65,19 +78,13 @@ class _Header: self.lines.extend(header.lines) def write(self, worksheet: ScoExcelSheet): - row1 = [] - row2 = [] - row3 = [] - row4 = [] + column = 1 for items in self.lines: - row1.append(items[0].make_cell(worksheet)) - row2.append(items[1].make_cell(worksheet)) - row3.append(items[2].make_cell(worksheet)) - row4.append(items[3].make_cell(worksheet)) - worksheet.append_row(row1) - worksheet.append_row(row2) - worksheet.append_row(row3) - worksheet.append_row(row4) + 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: @@ -91,17 +98,17 @@ class NiveauDesc: self.ue[parite(scodocUe.semestre_idx)] = scodocUe def generate_header(self, header): - rcue_style = Sco_Style( - fromStyle=base_style, - bgcolor=header_colors[self.fromScodoc.annee]["RCUE"], + rcue_signature = FMT.FILL_BGCOLOR.write( + value=header_colors[self.fromScodoc.annee]["RCUE"].value, + signature=base_signature, ) - ue_style = Sco_Style( - fromStyle=base_style, - bgcolor=header_colors[self.fromScodoc.annee]["UE"], + ue_signature = FMT.FILL_BGCOLOR.write( + value=header_colors[self.fromScodoc.annee]["UE"].value, + signature=base_signature, ) header.setLabelOnce(1, self.fromScodoc.competence.titre) - header.setStyles([1], rcue_style) - header.setStyles([2, 3], ue_style) + header.setSignatures([1], rcue_signature) + header.setSignatures([2, 3], ue_signature) for ue in self.ue: if ue is None: header.setLabelOnce(2, "XXX") @@ -111,7 +118,7 @@ class NiveauDesc: header.setLabelOnce(2, ue.acronyme) header.add_column(label3="Note") header.add_column(label3="Res.") - header.setStyles([1, 2, 3], rcue_style) + header.setSignatures([1, 2, 3], rcue_signature) header.add_column(label2="RCUE", label3="Note") header.add_column(label3="Res.") return header @@ -160,24 +167,67 @@ class AnneeDesc: for _ in range(3): header.add_column() - def generate_header(self, header): - but_style = Sco_Style( - fromStyle=base_style, bgcolor=header_colors[self.codeAnnee]["BUT"] + def generate_header(self, ws: Worksheet, column: int, codeAnnee: str): + start = column + but_signature = FMT.FILL_BGCOLOR.write( + signature=base_signature, value=header_colors[self.codeAnnee]["BUT"].value ) - header.setStyles([0], but_style) - for niveau in self.niveaux.values(): - niveau.generate_header(header) - for i in range(len(self.niveaux), 6): - self.generate_blank_niveau(header) - header.add_column(label1="Année", label2="Nb", label3="RCUE") - header.add_column(label3="Rés.") + but_style = FMT.style(but_signature) + set_cell( + ws, + 1, + column, + text=codeAnnee, + from_signature=but_signature, + composition=[ + (FMT.BORDER_LEFT_COLOR, SCO_COLORS.BLACK.value), + (FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value), + ], + ) + ws.cell(1, column).value = codeAnnee + 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" + # cell_format(ws.cell(2, column), but_signature, [(FMT.FONT_BOLD, True)]) + ws.cell(3, column).value = "Nb" + ws.cell(4, column).value = "RCUE" + column += 1 + ws.cell(2, column).value = "Année" + ws.cell(4, column).value = "Moy." + column += 1 if self.codeAnnee == "BUT2": - header.add_column(label1="DUT", label3="Rés.") + ws.cell(3, column).value = "DUT" + ws.cell(3, column).value = "Rés." if self.codeAnnee == "BUT3": - header.add_column(label1="BUT", label3="Rés.") + ws.cell(3, column).value = "BUT" + ws.cell(3, column).value = "Rés." + # ws.merge_cells(start_column=start, end_column=column, start_row=1, end_row=1) + return column 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), + ], + base_signature, + ) + def __init__(self, formation, scodocParcour: ApcParcours = None): self.fromScodoc: ApcParcours = scodocParcour # None pour le tronc commun 'TC' self.etudiants = [] @@ -221,16 +271,60 @@ class ParcoursDesc: cells3.append(worksheet.make_cell(val3)) cells4.append(worksheet.make_cell(val4)) - def handle_description(self, header, description, level): + def handle_description( + self, ws: Worksheet, description: tuple, row: int, column: int + ) -> int: title, content_list = description - header.setLabelOnce(level, title) + style = FMT.style(self.signature_header) + ws.cell(row, column).value = title + style.apply(ws.cell(row, column)) if content_list is None: - header.add_column() + ws.merge_cells( + start_row=row, end_row=4, start_column=column, end_column=column + ) + column += 1 else: + first = column for content in content_list: - self.handle_description(header, content, level + 1) + column = self.handle_description(ws, content, row + 1, column) + if column - first > 1: + ws.merge_cells( + start_row=row, + end_row=row, + start_column=first, + 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)) + return column - def generate_etudiant_header(self, header): + def generate_etudiant_header(self, ws: Worksheet) -> int: titles = ( "ETUDIANT", [ @@ -250,14 +344,13 @@ class ParcoursDesc: ), ], ) - self.handle_description(header, titles, 0) + column = self.handle_description(ws, titles, 1, 1) + return column - def generate_header(self, header) -> _Header: - self.generate_etudiant_header(header) + def generate_header(self, ws: Worksheet): + column: int = self.generate_etudiant_header(ws) for codeAnnee in listeAnnees: - header.setLabelOnce(0, codeAnnee) - self.annees[codeAnnee].generate_header(header) - return header + column = self.annees[codeAnnee].generate_header(ws, column, codeAnnee) def generate(self, workbook: ScoExcelBook): header = _Header() @@ -266,4 +359,4 @@ class ParcoursDesc: else: sheet_name = "TC" worksheet: ScoExcelSheet = workbook.create_sheet(sheet_name) - self.generate_header(header).write(worksheet) + self.generate_header(worksheet.ws) diff --git a/app/but/prepajury_xl.py b/app/but/prepajury_xl.py index 7a1c7e50e..294fc67a9 100644 --- a/app/but/prepajury_xl.py +++ b/app/but/prepajury_xl.py @@ -87,7 +87,7 @@ class ScoExcelBook: def __init__(self): self.sheets = [] # list of sheets - self.wb = Workbook(write_only=True) + self.wb = Workbook() def create_sheet(self, sheet_name="feuille", default_style=None): """Crée une nouvelle feuille dans ce classeur @@ -208,21 +208,9 @@ class ScoExcelSheet: # création de la cellule cell = WriteOnlyCell(self.ws, value) - # recopie des styles - if style is None: - style = self.default_style - if "font" in style: - cell.font = style["font"] - if "alignment" in style: - cell.alignment = style["alignment"].get_openxl() - if "border" in style: - cell.border = style["border"].get_openxl() - if "number_format" in style: - cell.number_format = style["number_format"] - if "fill" in style: - cell.fill = style["fill"] - if "alignment" in style: - cell.alignment = style["alignment"].get_openxl() + if style is not None: + style.apply(cell) + if not comment is None: cell.comment = Comment(comment, "scodoc") lines = comment.splitlines() diff --git a/app/but/prepajury_xl_format.py b/app/but/prepajury_xl_format.py index 660d9106d..c41e3f38c 100644 --- a/app/but/prepajury_xl_format.py +++ b/app/but/prepajury_xl_format.py @@ -2,9 +2,11 @@ import abc from enum import Enum import openpyxl.styles -from openpyxl.styles import Side, Border, Font, PatternFill +from openpyxl.styles import Side, Border, Font, PatternFill, Alignment from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00 +# Formatting Enums + class SCO_COLORS(Enum): def __new__(cls, value, argb): @@ -13,6 +15,7 @@ class SCO_COLORS(Enum): obj.argb = argb return obj + NONE = (0, None) BLACK = (1, "FF000000") WHITE = (2, "FFFFFFFF") RED = (3, "FFFF0000") @@ -39,11 +42,11 @@ class SCO_BORDERTHICKNESS(Enum): obj.width = width return obj - BORDER_NONE = (1, None) - BORDER_HAIR = (2, "hair") - BORDER_THIN = (3, "thin") - BORDER_MEDIUM = (4, "medium") - BORDER_THICK = (5, "thick") + BORDER_NONE = (0, None) + BORDER_HAIR = (1, "hair") + BORDER_THIN = (2, "thin") + BORDER_MEDIUM = (3, "medium") + BORDER_THICK = (4, "thick") class SCO_FONTNAME(Enum): @@ -53,10 +56,10 @@ class SCO_FONTNAME(Enum): obj.fontname = fontname return obj + FONTNAME_CALIBRI = (0, "Calibri") FONTNAME_ARIAL = (1, "Arial") FONTNAME_COURIER = (2, "Courier New") - FONTNAME_CALIBRI = (3, "Calibri") - FONTNAME_TIMES = (4, "Times New Roman") + FONTNAME_TIMES = (3, "Times New Roman") class SCO_FONTSIZE(Enum): @@ -66,11 +69,11 @@ class SCO_FONTSIZE(Enum): obj.fontsize = fontsize return obj - FONTSIZE_9 = (1, 9) - FONTSIZE_10 = (1, 10) - FONTSIZE_11 = (1, 11) - FONTSIZE_13 = (1, 13) - FONTSIZE_16 = (1, 16) + FONTSIZE_10 = (0, 10.0) + FONTSIZE_9 = (1, 9.0) + FONTSIZE_11 = (2, 11.0) + FONTSIZE_13 = (3, 13.0) + FONTSIZE_16 = (4, 16.0) class SCO_NUMBER_FORMAT(Enum): @@ -91,9 +94,9 @@ class SCO_HALIGN(Enum): obj.position = position return obj + HALIGN_LEFT = (0, "left") HALIGN_CENTER = (1, "center") HALIGN_RIGHT = (2, "right") - HALIGN_LEFT = (3, "left") class SCO_VALIGN(Enum): @@ -103,18 +106,18 @@ class SCO_VALIGN(Enum): obj.position = position return obj + VALIGN_BOTTOM = (0, "bottom") VALIGN_TOP = (1, "top") - VALIGN_BOTTOM = (2, "bottom") - VALIGN_CENTER = (3, "center") + VALIGN_CENTER = (2, "center") +# Composante (bitfield) atomique. Based on Enums free = 0 class Composante: def __init__(self, base=None, width: int = 1): global free - self.cache = {} if base is None: self.base = free free += width @@ -131,13 +134,7 @@ class Composante: return signature & ~self.mask def write(self, index, signature=0) -> int: - return self.clear(signature) + index << self.base - - def lookup_or_cache(self, signature: int): - value = self.read(signature) - if not value in self.cache: - self.cache[signature] = self.build(value) - return self.cache[value] + return self.clear(signature) + (index << self.base) @abc.abstractmethod def build(self, value: int): @@ -148,8 +145,12 @@ class Composante_boolean(Composante): def __init__(self): super().__init__(width=1) + def set(self, data: bool, signature=0) -> int: + return self.write(1 if data else 0, signature) + def build(self, signature): value = self.read(signature) + assert value < (1 << self.width) return value == 1 @@ -157,44 +158,76 @@ class Composante_number_format(Composante): def __init__(self): super().__init__(width=2) + def set(self, data: SCO_NUMBER_FORMAT, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) - return SCO_NUMBER_FORMAT(value) or None + assert value < (1 << self.width) + return SCO_NUMBER_FORMAT(value) class Composante_Colors(Composante): - def __init__(self): + def __init__(self, default: SCO_COLORS = SCO_COLORS.BLACK): super().__init__(width=5) + self.default: SCO_COLORS = default + + def set(self, data: SCO_COLORS, signature=0) -> int: + return self.write(data.value, signature) def build(self, signature: int): value = self.read(signature) - return SCO_COLORS(value) or SCO_COLORS.BLACK + assert value < (1 << self.width) + if value == 0: + return None + try: + return SCO_COLORS(value) + except: + return self.default class Composante_borderThickness(Composante): def __init__(self): super().__init__(width=2) + def set(self, data: SCO_BORDERTHICKNESS, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) - return SCO_BORDERTHICKNESS(value) or None + assert value < (1 << self.width) + try: + return SCO_BORDERTHICKNESS(value) + except: + return None class Composante_fontname(Composante): def __init__(self): super().__init__(width=3) + def set(self, data: SCO_FONTNAME, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) - return SCO_FONTNAME(value) or None + assert value < (1 << self.width) + try: + return SCO_FONTNAME(value) + except: + return SCO_FONTNAME.FONTNAME_CALIBRI class Composante_fontsize(Composante): def __init__(self): super().__init__(width=3) + def set(self, data: SCO_FONTSIZE, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) + assert value < (1 << self.width) return SCO_FONTSIZE(value) or None @@ -202,18 +235,46 @@ class Composante_halign(Composante): def __init__(self): super().__init__(width=2) + def set(self, data: SCO_HALIGN, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) - return SCO_HALIGN(value) or None + assert value < (1 << self.width) + try: + return SCO_HALIGN(value) + except: + return SCO_HALIGN.HALIGN_LEFT class Composante_valign(Composante): def __init__(self): super().__init__(width=2) + def set(self, data: SCO_VALIGN, signature=0) -> int: + return self.write(data.value, signature) + def build(self, signature: int): value = self.read(signature) - return SCO_VALIGN(signature) or None + assert value < (1 << self.width) + try: + return SCO_VALIGN(value) + except: + return SCO_VALIGN.VALIGN_CENTER + + +# Formatting objects + + +class Sco_Fill: + def __init__(self, color: SCO_COLORS): + self.color = color + + def to_openpyxl(self): + return PatternFill( + fill_type="solid", + fgColor=None if self.color is None else self.color.argb, + ) class Sco_BorderSide: @@ -225,9 +286,11 @@ class Sco_BorderSide: self.thickness = thickness self.color: SCO_COLORS = color - def get_openxl(self): - side: Side = Side(border_style=self.thickness.width, color=self.color.argb) - return side + def to_openpyxl(self): + return Side( + border_style=self.thickness.width, + color=None if self.color is None else self.color.argb, + ) class Sco_Borders: @@ -243,12 +306,12 @@ class Sco_Borders: self.top = top self.bottom = bottom - def get_openxl(self): - border: Border = Border( - left=self.left.get_openxl(), - right=self.right.get_openxl(), - top=self.top.get_openxl(), - bottom=self.bottom.get_openxl(), + def to_openpyxl(self): + return Border( + left=self.left.to_openpyxl(), + right=self.right.to_openpyxl(), + top=self.top.to_openpyxl(), + bottom=self.bottom.to_openpyxl(), ) @@ -261,18 +324,18 @@ class Sco_Alignment: self.halign = halign self.valign = valign - def get_openxl(self): - al = openpyxl.styles.Alignment() - al.horizontal = self.halign.position - al.vertical = self.valign.position - return al + def to_openpyxl(self): + return Alignment( + horizontal=self.halign.position, + vertical=self.valign.position, + ) class Sco_Font: def __init__( self, - name: str = None, - fontsize: int = None, + name: SCO_FONTNAME = SCO_FONTNAME(0), + fontsize: SCO_FONTSIZE = SCO_FONTSIZE(0), bold: bool = None, italic: bool = None, outline: bool = None, @@ -285,40 +348,54 @@ class Sco_Font: self.color = color self.fontsize = fontsize + def to_openpyxl(self): + return Font( + name=self.name.fontname, + size=self.fontsize.fontsize, + bold=self.bold, + italic=self.italic, + outline=self.outline, + color=None if self.color is None else self.color.argb, + ) + class Sco_Style: - from openpyxl.cell import WriteOnlyCell, Cell + from openpyxl.cell import Cell def __init__( self, font: Sco_Font = None, - bgcolor: SCO_COLORS = None, + fill: Sco_Fill = None, alignment: Sco_Alignment = None, borders: Sco_Borders = None, number_format: SCO_NUMBER_FORMAT = None, ): self.font = font - self.bgcolor = bgcolor + self.fill = fill self.alignment = alignment self.borders = borders self.number_format = number_format def apply(self, cell: Cell): if self.font: - cell.font = self.font.get_openxl() - if self.bgcolor: - cell.fill = PatternFill(fill_type="solid", fgColor=self.bgcolor.argb) + cell.font = self.font.to_openpyxl() + if self.fill and self.fill.color: + cell.fill = self.fill.to_openpyxl() if self.alignment: - cell.alignment = self.alignment.get_openxl() + cell.alignment = self.alignment.to_openpyxl() if self.borders: - cell.border = self.borders.get_openxl() + cell.border = self.borders.to_openpyxl() if self.number_format: - cell.number_format = self.number_format.get_openxl() + cell.number_format = self.number_format.format + + +# Composantes groupant d'autres composantes et dotées d'un mécanisme de cache class Composante_group(Composante): def __init__(self, composantes: list[Composante]): self.composantes = composantes + self.cache = {} mini = min([comp.base for comp in composantes]) maxi = max([comp.end for comp in composantes]) width = sum([comp.width for comp in composantes]) @@ -326,6 +403,22 @@ class Composante_group(Composante): raise Exception("Composante group non complete ou non connexe") super().__init__(base=mini, width=width) + def lookup_or_cache(self, signature: int): + value = self.read(signature) + assert value < (1 << self.width) + if not value in self.cache: + self.cache[value] = self.build(signature) + return self.cache[value] + + +class Composante_fill(Composante_group): + def __init__(self, color: Composante_Colors): + super().__init__([color]) + self.color = color + + def build(self, signature: int): + return Sco_Fill(color=self.color.build(signature)) + class Composante_font(Composante_group): def __init__( @@ -345,14 +438,14 @@ class Composante_font(Composante_group): self.italic = italic self.outline = outline - def build(self, signature): + def build(self, signature: int): return Sco_Font( - name=self.name.lookup_or_cache(signature), - fontsize=self.fontsize.lookup_or_cache(signature), - color=self.color.lookup_or_cache(signature), - bold=self.bold.lookup_or_cache(signature), - italic=self.italic.lookup_or_cache(signature), - outline=self.outline.lookup_or_cache(signature), + name=self.name.build(signature), + fontsize=self.fontsize.build(signature), + color=self.color.build(signature), + bold=self.bold.build(signature), + italic=self.italic.build(signature), + outline=self.outline.build(signature), ) @@ -364,8 +457,8 @@ class Composante_border(Composante_group): def build(self, signature: int): return Sco_BorderSide( - thickness=self.thick.lookup_or_cache(signature), - color=self.color.lookup_or_cache(signature), + thickness=self.thick.build(signature), + color=self.color.build(signature), ) @@ -400,36 +493,34 @@ class Composante_alignment(Composante_group): def build(self, signature: int): return Sco_Alignment( - halign=self.halign.lookup_or_cache(signature), - valign=self.valign.lookup_or_cache(signature), + halign=self.halign.build(signature), + valign=self.valign.build(signature), ) - # ALL = Composante_all(FONT, BGCOLOR, BORDERS, ALIGNMENT, NUMBER_FORMAT) - class Composante_all(Composante_group): def __init__( self, font: Composante_font, - bgcolor: Composante_Colors, + fill: Composante_fill, borders: Composante_borders, alignment: Composante_alignment, number_format: Composante_number_format, ): - super().__init__([font, bgcolor, borders, alignment, number_format]) + super().__init__([font, fill, borders, alignment, number_format]) self.font = font - self.bgcolor = bgcolor + self.fill = fill self.borders = borders self.alignment = alignment self.number_format = number_format def build(self, signature: int): return Sco_Style( - bgcolor=self.bgcolor.lookup_or_cache(signature), + fill=self.fill.lookup_or_cache(signature), font=self.font.lookup_or_cache(signature), borders=self.borders.lookup_or_cache(signature), alignment=self.alignment.lookup_or_cache(signature), - number_format=self.number_format.lookup_or_cache(signature), + number_format=self.number_format.build(signature), ) def get_style(self, signature: int): @@ -443,9 +534,22 @@ class FMT(Enum): def write(self, value, signature=0) -> int: return self.composante.write(value, signature) + def set(self, data, signature: int = 0) -> int: + return self.composante.set(data, signature) + def get_style(self, signature: int): return self.composante.lookup_or_cache(signature) + @classmethod + def compose(cls, composition: list, signature: int = 0) -> int: + for field, value in composition: + signature = field.write(value, signature) + return signature + + @classmethod + def style(cls, signature: int): + return FMT.ALL.get_style(signature) + FONT_NAME = Composante_fontname() FONT_SIZE = Composante_fontsize() FONT_COLOR = Composante_Colors() @@ -460,17 +564,18 @@ class FMT(Enum): BORDER_TOP_COLOR = Composante_Colors() BORDER_BOTTOM_STYLE = Composante_borderThickness() BORDER_BOTTOM_COLOR = Composante_Colors() - BGCOLOR = Composante_Colors() + FILL_BGCOLOR = Composante_Colors(None) ALIGNMENT_HALIGN = Composante_halign() - ALIGNEMENT_VALIGN = Composante_valign() + ALIGNMENT_VALIGN = Composante_valign() NUMBER_FORMAT = Composante_number_format() FONT = Composante_font( FONT_NAME, FONT_SIZE, FONT_COLOR, FONT_BOLD, FONT_ITALIC, FONT_OUTLINE ) + FILL = Composante_fill(FILL_BGCOLOR) BORDER_LEFT = Composante_border(BORDER_LEFT_STYLE, BORDER_LEFT_COLOR) BORDER_RIGHT = Composante_border(BORDER_RIGHT_STYLE, BORDER_RIGHT_COLOR) BORDER_TOP = Composante_border(BORDER_TOP_STYLE, BORDER_TOP_COLOR) BORDER_BOTTOM = Composante_border(BORDER_BOTTOM_STYLE, BORDER_BOTTOM_COLOR) BORDERS = Composante_borders(BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM) - ALIGNMENT = Composante_alignment(ALIGNMENT_HALIGN, ALIGNMENT_HALIGN) - ALL = Composante_all(FONT, BGCOLOR, BORDERS, ALIGNMENT, NUMBER_FORMAT) + ALIGNMENT = Composante_alignment(ALIGNMENT_HALIGN, ALIGNMENT_VALIGN) + ALL = Composante_all(FONT, FILL, BORDERS, ALIGNMENT, NUMBER_FORMAT)