generated from ACMUD/python-package-template
-
Notifications
You must be signed in to change notification settings - Fork 8
feat: creacion de clase cellreferencevalidator. Integracion de funcio… #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
luiszlopezs
wants to merge
2
commits into
ACMUD:master
Choose a base branch
from
luiszlopezs:feature/Identificacion-de-referencias
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,236 @@ | ||
def main(): | ||
print("Funciona excel-solver") | ||
import re | ||
from typing import Any, Dict, Optional, Set | ||
|
||
|
||
class SolverError(Exception): | ||
"""Excepción personalizada para errores del módulo Solver.""" | ||
pass | ||
|
||
|
||
class CircularReferenceError(SolverError): | ||
"""Excepción lanzada cuando se detecta una referencia circular.""" | ||
pass | ||
|
||
|
||
class InvalidCellReferenceError(SolverError): | ||
"""Excepción lanzada cuando se encuentra una referencia de celda inválida.""" | ||
pass | ||
|
||
|
||
class CellReferenceValidator: | ||
""" | ||
Componente encargado de detectar y validar referencias válidas a celdas. | ||
|
||
Soporta formatos estándar como A1, B2, Z10, AA1, etc. | ||
Verifica que el formato sea correcto (letra(s) seguidas de número). | ||
""" | ||
|
||
# Patrón regex para referencias de celda válidas | ||
CELL_REFERENCE_PATTERN = re.compile(r'^[A-Z]+[1-9]\d*$') | ||
|
||
@classmethod | ||
def is_valid_cell_reference(cls, reference: str) -> bool: | ||
if not isinstance(reference, str): | ||
return False | ||
return bool(cls.CELL_REFERENCE_PATTERN.match(reference.upper())) | ||
|
||
@classmethod | ||
def find_cell_references(cls, expression: str) -> Set[str]: | ||
if not isinstance(expression, str): | ||
return set() | ||
|
||
# Buscar todas las posibles referencias de celda | ||
potential_refs = re.findall(r'\b[A-Z]+\d+\b', expression.upper()) | ||
|
||
# Filtrar solo las referencias válidas | ||
valid_refs = {ref for ref in potential_refs if cls.is_valid_cell_reference(ref)} | ||
|
||
return valid_refs | ||
|
||
class ExcelSolver: | ||
""" | ||
Módulo principal Solver para resolución de referencias de celda. | ||
|
||
Maneja la resolución segura y recursiva de referencias y detecta ciclos infinitos. | ||
""" | ||
|
||
def __init__(self): | ||
""" | ||
Inicializa el solver. | ||
""" | ||
self.cell_data: Dict[str, Any] = {} | ||
self.validator = CellReferenceValidator() | ||
|
||
def set_cell_value(self, cell_ref: str, value: Any) -> None: | ||
""" | ||
Establece el valor de una celda. | ||
|
||
Args: | ||
cell_ref (str): Referencia de la celda (ej: "A1") | ||
value (Any): Valor a asignar a la celda | ||
|
||
Raises: | ||
InvalidCellReferenceError: Si la referencia de celda no es válida | ||
""" | ||
if not self.validator.is_valid_cell_reference(cell_ref): | ||
raise InvalidCellReferenceError(f"Referencia de celda inválida: {cell_ref}") | ||
|
||
self.cell_data[cell_ref.upper()] = value | ||
|
||
def get_cell_value(self, cell_ref: str) -> Any: | ||
""" | ||
Obtiene el valor raw de una celda sin resolver referencias. | ||
|
||
Args: | ||
cell_ref (str): Referencia de la celda | ||
|
||
Returns: | ||
Any: Valor de la celda | ||
""" | ||
return self.cell_data.get(cell_ref.upper()) | ||
|
||
def _detect_circular_reference(self, cell_ref: str, visited: Set[str], | ||
recursion_stack: Set[str]) -> bool: | ||
""" | ||
Detecta referencias circulares usando DFS. | ||
|
||
Args: | ||
cell_ref (str): Referencia actual de celda | ||
visited (Set[str]): Conjunto de celdas ya visitadas | ||
recursion_stack (Set[str]): Stack de recursión actual | ||
|
||
Returns: | ||
bool: True si se detecta una referencia circular | ||
""" | ||
if cell_ref in recursion_stack: | ||
return True | ||
|
||
if cell_ref in visited: | ||
return False | ||
|
||
visited.add(cell_ref) | ||
recursion_stack.add(cell_ref) | ||
|
||
cell_value = self.get_cell_value(cell_ref) | ||
if cell_value is None: | ||
recursion_stack.remove(cell_ref) | ||
return False | ||
|
||
# Si el valor es una string, buscar referencias | ||
if isinstance(cell_value, str): | ||
references = self.validator.find_cell_references(cell_value) | ||
for ref in references: | ||
if self._detect_circular_reference(ref, visited, recursion_stack): | ||
recursion_stack.remove(cell_ref) | ||
return True | ||
|
||
recursion_stack.remove(cell_ref) | ||
return False | ||
|
||
def resolve_cell_reference(self, cell_ref: str, visited: Optional[Set[str]] = None) -> Any: | ||
""" | ||
Resuelve una referencia de celda de forma recursiva y segura. | ||
|
||
Args: | ||
cell_ref (str): Referencia de celda a resolver | ||
visited (Set[str], optional): Conjunto de celdas visitadas (para detección de ciclos) | ||
|
||
Returns: | ||
Any: Valor resuelto de la celda | ||
|
||
Raises: | ||
InvalidCellReferenceError: Si la referencia no es válida | ||
CircularReferenceError: Si se detecta una referencia circular | ||
""" | ||
# Validar referencia de celda | ||
if not self.validator.is_valid_cell_reference(cell_ref): | ||
raise InvalidCellReferenceError(f"Referencia de celda inválida: {cell_ref}") | ||
|
||
cell_ref = cell_ref.upper() | ||
|
||
# Inicializar conjunto de visitadas si es la primera llamada | ||
if visited is None: | ||
visited = set() | ||
# Verificar referencias circulares antes de comenzar | ||
if self._detect_circular_reference(cell_ref, set(), set()): | ||
raise CircularReferenceError(f"Referencia circular detectada que involucra la celda {cell_ref}") | ||
|
||
# Verificar si ya visitamos esta celda en la cadena actual | ||
if cell_ref in visited: | ||
raise CircularReferenceError(f"Referencia circular detectada que involucra la celda {cell_ref}") | ||
|
||
# Obtener valor de la celda | ||
cell_value = self.get_cell_value(cell_ref) | ||
|
||
# Si no es una string, devolver el valor directamente | ||
if not isinstance(cell_value, str): | ||
return cell_value | ||
|
||
# Agregar a visitadas | ||
visited.add(cell_ref) | ||
|
||
try: | ||
# Resolver referencias en la expresión | ||
resolved_expression = self.resolve_expression(cell_value, visited.copy()) | ||
|
||
# Si la expresión contiene funciones, debería reenviarse al módulo Logic | ||
# Por ahora, si es una expresión numérica simple, la evaluamos | ||
if self._is_simple_numeric_expression(resolved_expression): | ||
return eval(resolved_expression) | ||
else: | ||
# Para expresiones complejas con funciones, devolver la expresión resuelta | ||
# En una implementación completa, esto se enviaría al módulo Logic | ||
return resolved_expression | ||
|
||
finally: | ||
# Remover de visitadas al terminar | ||
visited.discard(cell_ref) | ||
|
||
def resolve_expression(self, expression: str, visited: Optional[Set[str]] = None) -> str: | ||
""" | ||
Resuelve todas las referencias de celda en una expresión. | ||
|
||
Args: | ||
expression (str): Expresión a resolver | ||
visited (Set[str], optional): Conjunto de celdas visitadas | ||
|
||
Returns: | ||
str: Expresión con referencias resueltas | ||
""" | ||
if visited is None: | ||
visited = set() | ||
|
||
# Encontrar todas las referencias de celda en la expresión | ||
cell_references = self.validator.find_cell_references(expression) | ||
|
||
resolved_expression = expression | ||
|
||
# Resolver cada referencia | ||
for cell_ref in cell_references: | ||
try: | ||
resolved_value = self.resolve_cell_reference(cell_ref, visited.copy()) | ||
# Reemplazar la referencia con el valor resuelto | ||
resolved_expression = resolved_expression.replace(cell_ref, str(resolved_value)) | ||
except CircularReferenceError as e: | ||
# Re-lanzar errores críticos | ||
raise e | ||
|
||
return resolved_expression | ||
|
||
def _is_simple_numeric_expression(self, expression: str) -> bool: | ||
""" | ||
Verifica si una expresión es una expresión numérica simple que puede evaluarse con eval(). | ||
|
||
Args: | ||
expression (str): Expresión a verificar | ||
|
||
Returns: | ||
bool: True si es una expresión numérica simple | ||
""" | ||
# Patrón para expresiones numéricas simples (números, operadores básicos, paréntesis) | ||
simple_pattern = re.compile(r'^[\d\+\-\*\/\(\)\.\s]+$') | ||
return bool(simple_pattern.match(expression)) | ||
|
||
def clear_all_cells(self) -> None: | ||
"""Limpia todas las celdas del solver.""" | ||
self.cell_data.clear() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Muy bien.
Aquí si quieres hacerlo mediante regex, puede que te sirva considerar el patrón
[A-Z]+[$]*[1-9]+[$]*
, el cual indica que tomas mínimo 1 valor A-Z, y puede hay 0 o más$
, seguido de mínimo un 1-9 y finaliza cero o más$
Y el siguiente patrón para rangos
cellpattern:cellpattern
.