diff --git a/app/but/prepajury_cells.py b/app/but/prepajury_cells.py deleted file mode 100644 index 3d591f8f8..000000000 --- a/app/but/prepajury_cells.py +++ /dev/null @@ -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 diff --git a/app/but/prepajury_desc.py b/app/but/prepajury_desc.py index 651636886..cea08c980 100644 --- a/app/but/prepajury_desc.py +++ b/app/but/prepajury_desc.py @@ -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) diff --git a/app/but/prepajury_xl.py b/app/but/prepajury_xl.py index 8f5d10eb5..3eeec2ab5 100644 --- a/app/but/prepajury_xl.py +++ b/app/but/prepajury_xl.py @@ -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""" diff --git a/app/but/prepajury_xl_format.py b/app/but/prepajury_xl_format.py index 0ad656f12..235335e2e 100644 --- a/app/but/prepajury_xl_format.py +++ b/app/but/prepajury_xl_format.py @@ -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] )