Présentation
openpyxl-utils
est un module Python
conçu pour manipuler et réaliser des calculs avancés sur des fichiers Excel 2010
(xlsx, xlsm, xltx, xltm).
Objectifs
Au début, j’avais pour but de créer une bibliothèque utilitaire, qui fonctionne comme un wrappeur autour d’openpyxl
.
Mon choix, découlait du fait qu’openpyxl
était assez limité en terme de fonctionnalités avancées. C’est une bibliothèque qui permet
simplement de réaliser de la manipulation de données assez basique (CRUD
).
Technologies utilisées et implémentation
Ce module est écrit en python
, et certaines fonctions critiques en c
. Dans le futur, je vais surement migrer une bonne partie des
fonctions critiques en rust
.
J’ai utilisé openpyxl
comme parser Open XML
. Mon but était de pouvoir commencer à implémenter des fonctionnalités, sans devoir écrire
mon propre parser. C’est d’ailleurs pour cette raison que le projet s’appelle openpyxl-utils
(le nom sera modifié lorsque le projet ne dépendra plus d’openpyxl
).
Le but à long terme est :
- Ne plus dépendre d’
openpyxl
- Créer mon propre parser
Open XML
(c’est déjà le cas pour certaines fonctionnalités) - Sortir une première version stable avec
CRUD
et certaines fonctionnalités plus poussées déjà implémentées dans la version actuelle. - Par la suite continuer à proposer un outil simple d’utilisation, mais très performant et flexible.
Fonctionnalités et problèmes résolus
Son but n’est pas de remplacer les logiciels comme Excel
, mais plutôt d’en étendre leurs fonctionnalités.
Son objectif principal est l’automatisation; c’est à dire permettre à un utilisateur de pouvoir automatiser des processus liés à la collecte et la manipulation de données.
Automatisation
C’est un module très long et complexe, donc je ne vais pas pouvoir tout présenter. Mais j’aimerais au moins montrer comment je fais pour générer dynamiquement des colonnes, et aussi vérifier certaines données.
Présentation de la génération de colonne
Les deux fonctions principales
Le programme entier repose sur deux fonctions de conversion.
- Conversion d’une séquence de lettre à un nombre en base26.
- Conversion d’un nombre en base26 à une séquence de lettre.
def _letters_to_base26(letters_sequence: str) -> int:
"""
Converts a sequence of letters to its corresponding base-26 integer.
Logic:
- letters are converted to their alphabetical positions (A = 1, B = 2...).
- each position is multiplied by a power of 26, based on the letter's position in the sequence.
- the values are summed to get the final number, equivalent in base 26.
I think function can be optimized with something like this:
for letter in letters_sequence:
index = index * 26 + (ord(letter) - 64)
"""
power = len(letters_sequence) - 1
result = 0
for letter in letters_sequence.upper():
result += (ord(letter) - 64) * (26 ** power)
power -= 1
return result
def _base26_to_letters(n: int) -> str:
"""
Converts a base-26 integer to its corresponding sequence of letters.
"""
l = []
while n > 0:
n, r = divmod(n - 1, 26)
l.append(chr(r + 65))
return "".join(l[::-1])
Validation de cellules
def validate_cells(cells: list[str]) -> None:
"""
Validates the provided cells.
Checks if any of the provided cell conforms to the format of an Excel-type cell,
meaning it starts with one or more letters followed by one or more digits.
Cell must be within Excel's valid range, from 'A1' to 'XFD1048576'.
:param cells: list[str]
List of cells to be validated.
:return: None
:raises TypeError:
If `cells` argument is not of type list.
:raises TypeError:
If any cell is not of type str.
:raises ValueError:
If any cell is not a valid cell format, i.e., letters followed by digits (ex: 'A45', 'bcd7').
:raises OutOfRangeError:
If any cell is outside of Excel's limits, i.e., from cell 'A1' to cell 'XFD1048576'.
also see: `validate_and_split_cell` for similar function type.
"""
if not cells:
raise ValueError("`cells` argument cannot be empty.")
if not isinstance(cells, list):
raise TypeError(f"`cells` argument must be of type 'list', not '{type(cells).__name__}'.'")
for cell in cells:
ERROR_INVALID_CELL_VALUE = f"Cell '{cell}' is not a valid cell format, i.e., letters followed by digits (ex: 'A45', 'bcd7')."
if not isinstance(cell, str):
raise TypeError(f"Cell '{cell}' must be of type 'str', not '{type(cell).__name__}'.")
if not cell[0].isalpha() or not cell[-1].isdigit():
raise ValueError(ERROR_INVALID_CELL_VALUE)
cell_letters = []
cell_numbers = []
transition_count = 0 # transition counter between letters and numbers in cell.
for i, element in enumerate(cell):
if element.isalpha():
cell_letters.append(element)
elif element.isdigit():
if not cell[i-1].isdigit():
transition_count += 1
if transition_count > 1:
raise ValueError(ERROR_INVALID_CELL_VALUE)
cell_numbers.append(element)
else:
raise ValueError(ERROR_INVALID_CELL_VALUE)
letters = "".join(cell_letters)
numbers = "".join(cell_numbers)
letters_b26 = _letters_to_base26(letters)
if (int(numbers) < Limits.FIRST_ROW) or (int(numbers) > Limits.LAST_ROW) or (letters_b26 > Limits.LAST_COLUMN_BASE26):
raise OutOfRangeError(f"Cell '{cell}' is out of the valid cell range (i.e., from '{Limits.FIRST_CELL}' to '{Limits.LAST_CELL}').")
Fonctionnalités clés
- Gestion avancée des fichiers Excel.
- Contrôle précis des cellules.
- Effacement sélectif des lignes et colonnes.
- Recherche automatisée des cellules vides.
- Identification des occurrences d’informations spécifiques.
- Personnalisation de l’apparence (à venir)
- Automatisation des calculs (à venir)
- Gestion flexible de la sauvegarde.
Et bien plus encore.