apres refactor merge cells

This commit is contained in:
Jean-Marie Place 2023-05-20 11:29:19 +02:00
parent 7427db0ba6
commit 66480f0d11
4 changed files with 318 additions and 322 deletions

View File

@ -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

View File

@ -1,7 +1,5 @@
import openpyxl import openpyxl
from openpyxl.worksheet.worksheet import Worksheet 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 ( from app.but.prepajury_xl_format import (
SCO_COLORS, SCO_COLORS,
FMT, FMT,
@ -10,9 +8,17 @@ from app.but.prepajury_xl_format import (
SCO_HALIGN, SCO_HALIGN,
SCO_BORDERTHICKNESS, SCO_BORDERTHICKNESS,
Sco_Style, Sco_Style,
HAIR_BLACK,
SCO_NUMBER_FORMAT,
) )
from app.models import ApcCompetence, ApcParcours 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): 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: class NiveauDesc:
def __init__(self, scodocNiveau): def __init__(self, scodocNiveau):
self.fromScodoc = scodocNiveau self.fromScodoc = scodocNiveau
@ -167,14 +125,12 @@ class AnneeDesc:
for _ in range(3): for _ in range(3):
header.add_column() header.add_column()
def generate_header(self, ws: Worksheet, column: int): def generate_header(self, ws: ScoExcelSheet, column: int):
start = column start = column
but_signature = FMT.FILL_BGCOLOR.write( but_signature = FMT.FILL_BGCOLOR.write(
signature=base_signature, value=header_colors[self.codeAnnee]["BUT"].value signature=base_signature, value=header_colors[self.codeAnnee]["BUT"].value
) )
but_style = FMT.style(but_signature) ws.set_cell(
set_cell(
ws,
1, 1,
column, column,
text=self.codeAnnee, text=self.codeAnnee,
@ -184,25 +140,24 @@ class AnneeDesc:
(FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value), (FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_MEDIUM.value),
], ],
) )
but_style.apply(ws.cell(1, column))
# for niveau in self.niveaux.values(): # for niveau in self.niveaux.values():
# column = niveau.generate_header(ws, column) # column = niveau.generate_header(ws, column)
# for i in range(len(self.niveaux), 6): # for i in range(len(self.niveaux), 6):
# column = self.generate_blank_niveau(ws, column) # 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)]) # cell_format(ws.cell(2, column), but_signature, [(FMT.FONT_BOLD, True)])
ws.cell(3, column).value = "Nb" ws.set_cell(3, column, "Nb")
ws.cell(4, column).value = "RCUE" ws.set_cell(4, column, "RCUE")
column += 1 column += 1
ws.cell(2, column).value = "Année" ws.set_cell(2, column, "Année")
ws.cell(4, column).value = "Moy." ws.set_cell(4, column, "Moy.")
column += 1 column += 1
if self.codeAnnee == "BUT2": if self.codeAnnee == "BUT2":
ws.cell(3, column).value = "DUT" ws.set_cell(3, column, "DUT")
ws.cell(3, column).value = "Rés." ws.set_cell(3, column, "Rés.")
if self.codeAnnee == "BUT3": if self.codeAnnee == "BUT3":
ws.cell(3, column).value = "BUT" ws.set_cell(3, column, "BUT")
ws.cell(3, column).value = "Rés." ws.set_cell(3, column, "Rés.")
# ws.merge_cells(start_column=start, end_column=column, start_row=1, end_row=1) # ws.merge_cells(start_column=start, end_column=column, start_row=1, end_row=1)
return column return column
@ -211,18 +166,15 @@ class ParcoursDesc:
signature_header = FMT.compose( signature_header = FMT.compose(
[ [
(FMT.FILL_BGCOLOR, SCO_COLORS.LIGHT_YELLOW.value), (FMT.FILL_BGCOLOR, SCO_COLORS.LIGHT_YELLOW.value),
(FMT.FONT_BOLD, True), (FMT.NUMBER_FORMAT, SCO_NUMBER_FORMAT.NUMBER_GENERAL.value),
(FMT.FONT_SIZE, SCO_FONTSIZE.FONTSIZE_13.value), # (FMT.FONT_BOLD, True),
(FMT.ALIGNMENT_VALIGN, SCO_VALIGN.VALIGN_CENTER.value), # (FMT.FONT_SIZE, SCO_FONTSIZE.FONTSIZE_13.value),
(FMT.ALIGNMENT_HALIGN, SCO_HALIGN.HALIGN_CENTER.value), # (FMT.ALIGNMENT_VALIGN, SCO_VALIGN.VALIGN_CENTER.value),
(FMT.BORDER_LEFT_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value), # (FMT.ALIGNMENT_HALIGN, SCO_HALIGN.HALIGN_CENTER.value),
(FMT.BORDER_RIGHT_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value), # (FMT.BORDER_RIGHT, HAIR_BLACK),
(FMT.BORDER_TOP_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value), # (FMT.BORDER_LEFT, HAIR_BLACK),
(FMT.BORDER_BOTTOM_STYLE, SCO_BORDERTHICKNESS.BORDER_THIN.value), # (FMT.BORDER_TOP, HAIR_BLACK),
(FMT.BORDER_LEFT_COLOR, SCO_COLORS.BLACK.value), # (FMT.BORDER_BOTTOM, HAIR_BLACK),
(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, base_signature,
) )
@ -271,54 +223,27 @@ class ParcoursDesc:
cells4.append(worksheet.make_cell(val4)) cells4.append(worksheet.make_cell(val4))
def handle_description( def handle_description(
self, ws: Worksheet, description: tuple, row: int, column: int self, ws: ScoExcelSheet, description: tuple, row: int, column: int
) -> int: ) -> int:
title, content_list = description title, content_list = description
style = FMT.style(self.signature_header) ws.set_cell(
ws.cell(row, column).value = title row=row,
style.apply(ws.cell(row, column)) column=column,
merge_h = Merge_Engine(ws=ws, start_row=row, start_column=column) text=title,
merge_v = Merge_Engine(ws=ws, start_row=row, start_column=column) 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: if content_list is None:
merge_v.close(end_row=4) merge_v.close(end_row=4)
column += 1 column += 1
else: else:
first = column
for content in content_list: for content in content_list:
column = self.handle_description(ws, content, row + 1, column) column = self.handle_description(ws, content, row + 1, column)
merge_h.close(end_column=column - 1) 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 return column
def generate_etudiant_header(self, ws: Worksheet) -> int: def generate_etudiant_header(self, ws: ScoExcelSheet) -> int:
titles = ( titles = (
"ETUDIANT", "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) column = self.handle_description(ws, titles, 1, 1)
frame.close(4, column - 1)
return column return column
def generate_header(self, ws: Worksheet): def generate_header(self, ws: ScoExcelSheet):
column: int = self.generate_etudiant_header(ws) column: int = self.generate_etudiant_header(ws)
for codeAnnee in listeAnnees: for codeAnnee in listeAnnees:
column = self.annees[codeAnnee].generate_header(ws, column) column = self.annees[codeAnnee].generate_header(ws, column)
def generate(self, workbook: ScoExcelBook): def generate(self, workbook: ScoExcelBook):
header = _Header()
if self.fromScodoc: if self.fromScodoc:
sheet_name = self.fromScodoc.code sheet_name = self.fromScodoc.code
else: else:
sheet_name = "TC" sheet_name = "TC"
worksheet: ScoExcelSheet = workbook.create_sheet(sheet_name) worksheet: ScoExcelSheet = workbook.create_sheet(sheet_name)
self.generate_header(worksheet.ws) self.generate_header(worksheet)

View File

@ -27,10 +27,23 @@
from __future__ import annotations from __future__ import annotations
from collections import defaultdict
from openpyxl.cell import WriteOnlyCell from openpyxl.cell import WriteOnlyCell
from openpyxl.worksheet.worksheet import Worksheet 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 """ Excel file handling
""" """
@ -51,6 +64,93 @@ from app.scodoc.sco_exceptions import ScoValueError
# font, border, number_format, fill,... # font, border, number_format, fill,...
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles) # (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): def xldate_as_datetime(xldate, datemode=0):
"""Conversion d'une date Excel en datetime python """Conversion d'une date Excel en datetime python
@ -90,14 +190,14 @@ class ScoExcelBook:
self.sheets = [] # list of sheets self.sheets = [] # list of sheets
self.wb = Workbook() 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 """Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille sheet_name -- le nom de la feuille
default_style -- le style par défaut default_style -- le style par défaut
""" """
sheet_name = adjust_sheetname(sheet_name) sheet_name = adjust_sheetname(sheet_name)
ws = self.wb.create_sheet(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) self.sheets.append(sheet)
return sheet return sheet
@ -128,7 +228,12 @@ class ScoExcelSheet:
* pour finir appel de la méthode de génération * 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 """Création de la feuille. sheet_name
-- le nom de la feuille default_style -- le nom de la feuille default_style
-- le style par défaut des cellules ws -- 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. # 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' ?) # si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
self.sheet_name = adjust_sheetname(sheet_name) self.sheet_name = adjust_sheetname(sheet_name)
self.default_style = default_style or Sco_Style() self.default_signature = default_signature
if wb is None: self.merges: list[Merge_Engine] = []
if ws is None:
self.wb = Workbook() self.wb = Workbook()
self.ws = self.wb.active self.ws = self.wb.active
self.ws.title = self.sheet_name self.ws.title = self.sheet_name
else: else:
self.wb = None self.wb = None
self.ws = wb self.ws = ws
# internal data # internal data
self.rows = [] # list of list of cells self.cells = defaultdict(lambda: defaultdict(Sco_Cell))
self.column_dimensions = {} self.column_dimensions = {}
self.row_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 @staticmethod
def i2col(idx): def i2col(idx):
if idx < 26: # one letter key if idx < 26: # one letter key
@ -160,6 +288,18 @@ class ScoExcelSheet:
second = (idx % 26) + 65 second = (idx % 26) + 65
return "" + chr(first) + chr(second) 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): 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, """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 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 self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style: Sco_Style = None, comment=None): # def make_cell(self, value: any = None, style: Sco_Style = None, comment=None):
"""Construit une cellule. # """Construit une cellule.
value -- contenu de la cellule (texte, numérique, booléen ou date) # 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é # style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
""" # """
# adaptation des valeurs si nécessaire # # adaptation des valeurs si nécessaire
if value is None: # if value is None:
value = "" # value = ""
elif value is True: # elif value is True:
value = 1 # value = 1
elif value is False: # elif value is False:
value = 0 # value = 0
elif isinstance(value, datetime.datetime): # elif isinstance(value, datetime.datetime):
value = value.replace( # value = value.replace(
tzinfo=None # tzinfo=None
) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones) # ) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
#
# création de la cellule # # création de la cellule
cell = WriteOnlyCell(self.ws, value) # cell = WriteOnlyCell(self.ws, value)
#
if style is not None: # if style is not None:
style.apply(cell) # style.apply(cell)
#
if not comment is None: # if not comment is None:
cell.comment = Comment(comment, "scodoc") # cell.comment = Comment(comment, "scodoc")
lines = comment.splitlines() # lines = comment.splitlines()
cell.comment.width = 7 * max([len(line) for line in lines]) if lines else 7 # 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 # cell.comment.height = 20 * len(lines) if lines else 20
#
# test datatype to overwrite datetime format # # test datatype to overwrite datetime format
if isinstance(value, datetime.date): # if isinstance(value, datetime.date):
cell.data_type = "d" # cell.data_type = "d"
cell.number_format = FORMAT_DATE_DDMMYY # cell.number_format = FORMAT_DATE_DDMMYY
elif isinstance(value, int) or isinstance(value, float): # elif isinstance(value, int) or isinstance(value, float):
cell.data_type = "n" # cell.data_type = "n"
else: # else:
cell.data_type = "s" # cell.data_type = "s"
#
return cell # 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 prepare(self): def prepare(self):
"""génére un flux décrivant la feuille. """génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille) Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles ou pour la génération d'un classeur multi-feuilles
""" """
for row in self.column_dimensions.keys(): # for row in self.column_dimensions.keys():
self.ws.column_dimensions[row] = self.column_dimensions[row] # self.ws.column_dimensions[row] = self.column_dimensions[row]
for row in self.row_dimensions.keys(): # for row in self.row_dimensions.keys():
self.ws.row_dimensions[row] = self.row_dimensions[row] # self.ws.row_dimensions[row] = self.row_dimensions[row]
for row in self.rows: # for row in self.rows:
self.ws.append(row) # 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): def generate(self):
"""génération d'un classeur mono-feuille""" """génération d'un classeur mono-feuille"""

View File

@ -2,6 +2,7 @@ import abc
from enum import Enum from enum import Enum
import openpyxl.styles import openpyxl.styles
from openpyxl.cell import Cell
from openpyxl.styles import Side, Border, Font, PatternFill, Alignment from openpyxl.styles import Side, Border, Font, PatternFill, Alignment
from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00 from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00
@ -42,7 +43,7 @@ class SCO_BORDERTHICKNESS(Enum):
obj.width = width obj.width = width
return obj return obj
BORDER_NONE = (0, None) NONE = (0, None)
BORDER_HAIR = (1, "hair") BORDER_HAIR = (1, "hair")
BORDER_THIN = (2, "thin") BORDER_THIN = (2, "thin")
BORDER_MEDIUM = (3, "medium") BORDER_MEDIUM = (3, "medium")
@ -56,10 +57,11 @@ class SCO_FONTNAME(Enum):
obj.fontname = fontname obj.fontname = fontname
return obj return obj
FONTNAME_CALIBRI = (0, "Calibri") NONE = (0, None)
FONTNAME_ARIAL = (1, "Arial") FONTNAME_CALIBRI = (1, "Calibri")
FONTNAME_COURIER = (2, "Courier New") FONTNAME_ARIAL = (2, "Arial")
FONTNAME_TIMES = (3, "Times New Roman") FONTNAME_COURIER = (3, "Courier New")
FONTNAME_TIMES = (4, "Times New Roman")
class SCO_FONTSIZE(Enum): class SCO_FONTSIZE(Enum):
@ -69,11 +71,11 @@ class SCO_FONTSIZE(Enum):
obj.fontsize = fontsize obj.fontsize = fontsize
return obj return obj
FONTSIZE_10 = (0, 10.0) NONE = (0, None)
FONTSIZE_9 = (1, 9.0) FONTSIZE_9 = (1, 9.0)
FONTSIZE_10 = (2, 10.0)
FONTSIZE_11 = (2, 11.0) FONTSIZE_11 = (2, 11.0)
FONTSIZE_13 = (3, 13.0) FONTSIZE_13 = (4, 13.0)
FONTSIZE_16 = (4, 16.0)
class SCO_NUMBER_FORMAT(Enum): class SCO_NUMBER_FORMAT(Enum):
@ -83,6 +85,7 @@ class SCO_NUMBER_FORMAT(Enum):
obj.format = format obj.format = format
return obj return obj
NONE = (0, None)
NUMBER_GENERAL = (0, FORMAT_GENERAL) NUMBER_GENERAL = (0, FORMAT_GENERAL)
NUMBER_00 = (1, FORMAT_NUMBER_00) NUMBER_00 = (1, FORMAT_NUMBER_00)
@ -94,9 +97,10 @@ class SCO_HALIGN(Enum):
obj.position = position obj.position = position
return obj return obj
HALIGN_LEFT = (0, "left") NONE = (0, None)
HALIGN_CENTER = (1, "center") HALIGN_LEFT = (1, "left")
HALIGN_RIGHT = (2, "right") HALIGN_CENTER = (2, "center")
HALIGN_RIGHT = (3, "right")
class SCO_VALIGN(Enum): class SCO_VALIGN(Enum):
@ -151,7 +155,7 @@ class Composante_boolean(Composante):
def set(self, data: bool, signature=0) -> int: def set(self, data: bool, signature=0) -> int:
return self.write(1 if data else 0, signature) return self.write(1 if data else 0, signature)
def build(self, signature): def build(self, signature) -> bool:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
return value == 1 return value == 1
@ -167,7 +171,7 @@ class Composante_number_format(Composante):
def set(self, data: SCO_NUMBER_FORMAT, signature=0) -> int: def set(self, data: SCO_NUMBER_FORMAT, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_NUMBER_FORMAT:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
return SCO_NUMBER_FORMAT(value) return SCO_NUMBER_FORMAT(value)
@ -184,7 +188,7 @@ class Composante_Colors(Composante):
def set(self, data: SCO_COLORS, signature=0) -> int: def set(self, data: SCO_COLORS, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_COLORS:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
if value == 0: if value == 0:
@ -205,7 +209,7 @@ class Composante_borderThickness(Composante):
def set(self, data: SCO_BORDERTHICKNESS, signature=0) -> int: def set(self, data: SCO_BORDERTHICKNESS, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_BORDERTHICKNESS:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
try: try:
@ -224,7 +228,7 @@ class Composante_fontname(Composante):
def set(self, data: SCO_FONTNAME, signature=0) -> int: def set(self, data: SCO_FONTNAME, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_FONTNAME:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
try: try:
@ -243,14 +247,14 @@ class Composante_fontsize(Composante):
def set(self, data: SCO_FONTSIZE, signature=0) -> int: def set(self, data: SCO_FONTSIZE, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_FONTSIZE:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
return SCO_FONTSIZE(value) or None return SCO_FONTSIZE(value) or None
class Composante_halign(Composante): class Composante_halign(Composante):
WIDTH: int = 2 WIDTH: int = 3
def __init__(self): def __init__(self):
assert (1 << self.WIDTH) > SCO_HALIGN.__len__() assert (1 << self.WIDTH) > SCO_HALIGN.__len__()
@ -259,7 +263,7 @@ class Composante_halign(Composante):
def set(self, data: SCO_HALIGN, signature=0) -> int: def set(self, data: SCO_HALIGN, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_HALIGN:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
try: try:
@ -269,7 +273,7 @@ class Composante_halign(Composante):
class Composante_valign(Composante): class Composante_valign(Composante):
WIDTH: int = 2 WIDTH: int = 3
def __init__(self): def __init__(self):
assert (1 << self.WIDTH) > SCO_VALIGN.__len__() assert (1 << self.WIDTH) > SCO_VALIGN.__len__()
@ -278,7 +282,7 @@ class Composante_valign(Composante):
def set(self, data: SCO_VALIGN, signature=0) -> int: def set(self, data: SCO_VALIGN, signature=0) -> int:
return self.write(data.value, signature) return self.write(data.value, signature)
def build(self, signature: int): def build(self, signature: int) -> SCO_VALIGN:
value = self.read(signature) value = self.read(signature)
assert value < (1 << self.width) assert value < (1 << self.width)
try: try:
@ -312,7 +316,7 @@ class Sco_BorderSide:
def to_openpyxl(self): def to_openpyxl(self):
return Side( 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, color=None if self.color is None else self.color.argb,
) )
@ -350,8 +354,8 @@ class Sco_Alignment:
def to_openpyxl(self): def to_openpyxl(self):
return Alignment( return Alignment(
horizontal=self.halign.position, horizontal=None if self.halign is None else self.halign.position,
vertical=self.valign.position, vertical=None if self.valign is None else self.valign.position,
) )
@ -374,8 +378,8 @@ class Sco_Font:
def to_openpyxl(self): def to_openpyxl(self):
return Font( return Font(
name=self.name.fontname, name=None if self.name is None else self.name.fontname,
size=self.fontsize.fontsize, size=None if self.fontsize is None else self.fontsize.fontsize,
bold=self.bold, bold=self.bold,
italic=self.italic, italic=self.italic,
outline=self.outline, outline=self.outline,
@ -392,13 +396,13 @@ class Sco_Style:
fill: Sco_Fill = None, fill: Sco_Fill = None,
alignment: Sco_Alignment = None, alignment: Sco_Alignment = None,
borders: Sco_Borders = None, borders: Sco_Borders = None,
number_format: SCO_NUMBER_FORMAT = None, number_format: SCO_NUMBER_FORMAT = FORMAT_GENERAL,
): ):
self.font = font self.font = font or None
self.fill = fill self.fill = fill or None
self.alignment = alignment self.alignment = alignment or None
self.borders = borders self.borders = borders or None
self.number_format = number_format self.number_format = number_format or SCO_NUMBER_FORMAT.NUMBER_GENERAL
def apply(self, cell: Cell): def apply(self, cell: Cell):
if self.font: if self.font:
@ -409,8 +413,12 @@ class Sco_Style:
cell.alignment = self.alignment.to_openpyxl() cell.alignment = self.alignment.to_openpyxl()
if self.borders: if self.borders:
cell.border = self.borders.to_openpyxl() cell.border = self.borders.to_openpyxl()
if self.number_format: cell.number_format = (
cell.number_format = self.number_format.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 # 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]) super().__init__([color])
self.color = color self.color = color
def build(self, signature: int): def build(self, signature: int) -> Sco_Fill:
return Sco_Fill(color=self.color.build(signature)) return Sco_Fill(color=self.color.build(signature))
@ -470,7 +478,7 @@ class Composante_font(Composante_group):
self.italic = italic self.italic = italic
self.outline = outline self.outline = outline
def build(self, signature: int): def build(self, signature: int) -> Sco_Font:
return Sco_Font( return Sco_Font(
name=self.name.build(signature), name=self.name.build(signature),
fontsize=self.fontsize.build(signature), fontsize=self.fontsize.build(signature),
@ -487,7 +495,7 @@ class Composante_border(Composante_group):
self.thick = thick self.thick = thick
self.color = color self.color = color
def build(self, signature: int): def build(self, signature: int) -> Sco_BorderSide:
return Sco_BorderSide( return Sco_BorderSide(
thickness=self.thick.build(signature), thickness=self.thick.build(signature),
color=self.color.build(signature), color=self.color.build(signature),
@ -508,7 +516,7 @@ class Composante_borders(Composante_group):
self.top = top self.top = top
self.bottom = bottom self.bottom = bottom
def build(self, signature: int): def build(self, signature: int) -> Sco_Borders:
return Sco_Borders( return Sco_Borders(
left=self.left.lookup_or_cache(signature), left=self.left.lookup_or_cache(signature),
right=self.right.lookup_or_cache(signature), right=self.right.lookup_or_cache(signature),
@ -523,7 +531,7 @@ class Composante_alignment(Composante_group):
self.halign = halign self.halign = halign
self.valign = valign self.valign = valign
def build(self, signature: int): def build(self, signature: int) -> Sco_Alignment:
return Sco_Alignment( return Sco_Alignment(
halign=self.halign.build(signature), halign=self.halign.build(signature),
valign=self.valign.build(signature), valign=self.valign.build(signature),
@ -547,7 +555,7 @@ class Composante_all(Composante_group):
self.alignment = alignment self.alignment = alignment
self.number_format = number_format self.number_format = number_format
def build(self, signature: int): def build(self, signature: int) -> Sco_Style:
return Sco_Style( return Sco_Style(
fill=self.fill.lookup_or_cache(signature), fill=self.fill.lookup_or_cache(signature),
font=self.font.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: def make_zero_based_constant(self, enums: list[Enum]) -> int:
return self.composante.make_zero_based_constant(enums=enums) return self.composante.make_zero_based_constant(enums=enums)
def apply(self, cell: Cell, signature: int):
self.composante.build(signature).apply(cell)
@classmethod @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: for field, value in composition:
signature = field.write(value, signature) signature = field.write(value, signature)
return signature return signature
@classmethod @classmethod
def style(cls, signature: int): def style(cls, signature: int = None) -> Sco_Style:
return FMT.ALL.get_style(signature) return FMT.ALL.get_style(signature)
FONT_NAME = Composante_fontname() FONT_NAME = Composante_fontname()
@ -617,6 +628,27 @@ class FMT(Enum):
ALL = Composante_all(FONT, FILL, BORDERS, ALIGNMENT, NUMBER_FORMAT) 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( HAIR_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
enums=[SCO_BORDERTHICKNESS.BORDER_HAIR, SCO_COLORS.BLACK] enums=[SCO_BORDERTHICKNESS.BORDER_HAIR, SCO_COLORS.BLACK]
) )