commit f9796cb7bfcd340d8352ef437372401a64aa35dd Author: Miguel Date: Tue Oct 8 17:34:42 2024 +0200 Inicial diff --git a/funciones_comunes/__init__.py b/funciones_comunes/__init__.py new file mode 100644 index 0000000..33045fc --- /dev/null +++ b/funciones_comunes/__init__.py @@ -0,0 +1,2 @@ +from .funciones_base import * +from .manejoArchivos import * \ No newline at end of file diff --git a/funciones_comunes/__pycache__/__init__.cpython-310.pyc b/funciones_comunes/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7913ace Binary files /dev/null and b/funciones_comunes/__pycache__/__init__.cpython-310.pyc differ diff --git a/funciones_comunes/__pycache__/funciones_base.cpython-310.pyc b/funciones_comunes/__pycache__/funciones_base.cpython-310.pyc new file mode 100644 index 0000000..645225b Binary files /dev/null and b/funciones_comunes/__pycache__/funciones_base.cpython-310.pyc differ diff --git a/funciones_comunes/__pycache__/manejoArchivos.cpython-310.pyc b/funciones_comunes/__pycache__/manejoArchivos.cpython-310.pyc new file mode 100644 index 0000000..2c271a4 Binary files /dev/null and b/funciones_comunes/__pycache__/manejoArchivos.cpython-310.pyc differ diff --git a/funciones_comunes/funciones_base.py b/funciones_comunes/funciones_base.py new file mode 100644 index 0000000..75e7505 --- /dev/null +++ b/funciones_comunes/funciones_base.py @@ -0,0 +1,524 @@ +import re +import time +import pandas as pd +from openpyxl import load_workbook +import logging +import os +from openpyxl.utils.escape import unescape + + +# Diccionario de idiomas +IDIOMAS = { + 0: ("Italian", "it-IT"), + 1: ("English", "en-GB"), + 2: ("Portuguese", "pt-PT"), + 3: ("Spanish", "es-ES"), + 4: ("Russian", "ru-RU"), + 5: ("French", "fr-FR"), + 6: ("German", "de-DE"), + 7: ("English_US", "en-US"), +} + + +# Función para obtener el shortcode a partir del código completo +def idiomas_shortcodefromcode(code): + if code: + return code.split('-')[0] + return None # O puedes lanzar una excepción si prefieres + +# Función para obtener el idioma a partir del código +def idiomas_idiomafromcode(code): + for idx, (idioma, codigo) in IDIOMAS.items(): + if codigo == code: + return idioma + return None # O puedes lanzar una excepción si prefieres + +# Función para obtener el código a partir del idioma +def idiomas_codefromidioma(idioma): + for idx, (nombre_idioma, codigo) in IDIOMAS.items(): + if nombre_idioma == idioma: + return codigo + return None # O lanzar una excepción + +# Función para verificar si un código existe +def idiomas_existecode(code): + return any(codigo == code for idx, (idioma, codigo) in IDIOMAS.items()) + +# Función para verificar si un idioma existe +def idiomas_existeidioma(idioma): + return any(nombre_idioma == idioma for idx, (nombre_idioma, codigo) in IDIOMAS.items()) + +# Función para obtener el idioma a partir del índice +def idiomas_idiomafromindex(index): + if index in IDIOMAS: + return IDIOMAS[index][0] + else: + return None # O lanzar una excepción + +# Función para obtener el código a partir del índice +def idiomas_codefromindex(index): + if index in IDIOMAS: + return IDIOMAS[index][1] + else: + return None # O lanzar una excepción + +# Función para obtener el índice a partir del código +def idiomas_indexfromcode(code): + for idx, (idioma, codigo) in IDIOMAS.items(): + if codigo == code: + return idx + return None # O lanzar una excepción + +# Función para obtener el índice a partir del idioma +def idiomas_indexfromidioma(idioma): + for idx, (nombre_idioma, codigo) in IDIOMAS.items(): + if nombre_idioma == idioma: + return idx + return None # O lanzar una excepción + + +def mostrar_idiomas(): + print("Selecciona el idioma de destino:") + for numero, (nombre, _) in IDIOMAS.items(): + print(f"{numero}: {nombre}") + + +def configurar_logger(): + logger = logging.getLogger("translate_logger") + + # Si el logger ya tiene handlers, asumimos que ya está configurado y lo devolvemos + if logger.handlers: + return logger + + logger.setLevel(logging.DEBUG) + + os.makedirs(".\\logs", exist_ok=True) + fh = logging.FileHandler(".\\logs\\translate_log.log", encoding="utf-8") + fh.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s : ") + fh.setFormatter(formatter) + + logger.addHandler(fh) + + return logger + +def limpiar_caracteres_especiales(texto): + if isinstance(texto, str): + return unescape(texto) + return texto + +def read_excel_with_cleanup(file_path, **kwargs): + df = pd.read_excel(file_path, **kwargs) + + for col in df.columns: + df[col] = df[col].apply(lambda x: limpiar_caracteres_especiales(x) if pd.notna(x) else x) + + return df + +def read_dataframe_with_cleanup_retries(file_path, max_retries=5, retry_delay=5, **kwargs): + """ + Lee un archivo Excel con limpieza de caracteres especiales, reintentando si el archivo está en uso. + + :param file_path: La ruta del archivo Excel a leer. + :param max_retries: El número máximo de reintentos en caso de error. + :param retry_delay: El tiempo de espera (en segundos) entre cada reintento. + :param kwargs: Argumentos adicionales para pd.read_excel. + :return: DataFrame con los datos limpios. + """ + retries = 0 + while retries < max_retries: + try: + df = pd.read_excel(file_path, **kwargs) + + # Aplicar limpieza de caracteres especiales + for col in df.columns: + df[col] = df[col].apply(lambda x: limpiar_caracteres_especiales(x) if pd.notna(x) else x) + + print(f"Archivo leído y limpiado exitosamente: {file_path}") + return df + except PermissionError as e: + print(f"Error de permiso: {e}. Por favor cierre el archivo. Reintentando en {retry_delay} segundos...") + retries += 1 + time.sleep(retry_delay) + except Exception as e: + print(f"Error inesperado: {e}. Reintentando en {retry_delay} segundos...") + retries += 1 + time.sleep(retry_delay) + + raise Exception(f"No se pudo leer el archivo después de {max_retries} intentos.") + +# +# Salvar archivo Excel controlando que no este abierto. Sino espera. +# +def save_dataframe_with_retries(df, output_path, max_retries=5, retry_delay=5): + """ + Guarda un DataFrame en un archivo Excel, reintentando si el archivo está en uso. + + :param df: El DataFrame a guardar. + :param output_path: La ruta del archivo donde se guardará el DataFrame. + :param max_retries: El número máximo de reintentos en caso de error. + :param retry_delay: El tiempo de espera (en segundos) entre cada reintento. + """ + retries = 0 + while retries < max_retries: + try: + df.to_excel(output_path, sheet_name="User Texts", index=False) + print("Archivo guardado exitosamente.") + return + except PermissionError as e: + print( + f"Error de permiso: {e}. Por favor cierre el archivo. Reintentando en {retry_delay} segundos..." + ) + retries += 1 + time.sleep(retry_delay) + + print(f"No se pudo guardar el archivo después de {max_retries} intentos.") + + +def cambiar_nombre_hoja(archivo_excel, nombre_hoja_actual, nombre_hoja_nuevo): + # Cargar el archivo Excel existente + libro = load_workbook(archivo_excel) + + # Verificar si la hoja existe en el archivo + if nombre_hoja_actual in libro.sheetnames: + # Obtener la hoja actual + hoja = libro[nombre_hoja_actual] + # Cambiar el nombre de la hoja + hoja.title = nombre_hoja_nuevo + # Guardar los cambios en el archivo Excel + libro.save(archivo_excel) + print( + f"El nombre de la hoja ha sido cambiado de '{nombre_hoja_actual}' a '{nombre_hoja_nuevo}'." + ) + else: + print(f"La hoja '{nombre_hoja_actual}' no existe en el archivo.") + +# Verificar si la columna es del tipo "xx-YY" usando una expresión regular +def es_columna_tipo_xxYY(columna): + # Verificar si la columna es del tipo "xx-YY" usando una expresión regular + return bool(re.match(r"^[a-z]{2}-[A-Z]{2}$", columna)) + + +def compactar_celda_clave(tipo_PLC, celda_original): + if tipo_PLC == "siemens" : + return compactar_celda_clave_siemens(celda_original) + else : + return compactar_celda_clave_ab(celda_original) + +def compactar_celda_traducida(tipo_PLC, celda_traducida): + if tipo_PLC == "siemens" : + return compactar_celda_traducida_siemens(celda_traducida) + else : + return compactar_celda_traducida_ab(celda_traducida) + +def obtener_digitos_celda_original(tipo_PLC, celda_original): + if tipo_PLC == "siemens" : + return obtener_digitos_celda_original_siemens(celda_original) + else : + return obtener_digitos_celda_original_ab(celda_original) + +def decompactar_celda_traducida(tipo_PLC, celda_original, celda_traducida): + if tipo_PLC == "siemens" : + return decompactar_celda_traducida_siemens(celda_original, celda_traducida) + else : + return decompactar_celda_traducida_ab(celda_original, celda_traducida) + +def verificar_celda_traducida(tipo_PLC, celda_clave, celda_traducida): + if tipo_PLC == "siemens" : + return verificar_celda_traducida_siemens(celda_clave, celda_traducida) + else : + return verificar_celda_traducida_ab(celda_clave, celda_traducida) + +def texto_requiere_traduccion(tipo_PLC, texto, logger): + if tipo_PLC == "siemens" : + return texto_requiere_traduccion_siemens(texto, logger) + else : + return texto_requiere_traduccion_ab(texto, logger) + +def texto_con_campos_especiales(tipo_PLC, texto): + if tipo_PLC == "siemens" : + return texto_con_campos_especiales_siemens(texto) + else : + return texto_con_campos_especiales_ab(texto) + + + +# SIEMENS +# +# Transforma: "A271/47/6 Air - M - Necessaria Manutenzione Filtro" -> +# "A[[digits]]/[[digits]]/[[digits]] Air - M - Necessaria Manutenzione Filtro" +# +# Este procesamiento se aplica a las celdas clave +def compactar_celda_clave_siemens(celda_original): + if pd.isnull(celda_original): + return celda_original + + def reemplazar(match): + if match.group(1): # Si hay contenido dentro de <> + return f"<{match.group(1)}>" + return "[[digits]]" + + # Reemplaza dígitos fuera de <> con [[digits]], y preserva el contenido dentro de <> + return re.sub(r"<(.*?)>|\d+", reemplazar, str(celda_original)) + + +# SIEMENS +def texto_requiere_traduccion_siemens(texto, logger): + palabras = re.findall(r"\b\w{4,}\b", texto) + campos_especiales = re.findall(r"<.*?>", texto) + requiere_traduccion = len(palabras) > 0 or len(campos_especiales) != len( + re.findall(r"<#>", texto) + ) + logger.debug( + f"Decisión de traducción para texto '{texto}': {'Sí' if requiere_traduccion else 'No'} (palabras > 3 letras: {len(palabras) > 0}, solo campos especiales: {len(campos_especiales) == len(re.findall(r'<#>', texto))})" + ) + return requiere_traduccion + +# SIEMENS +def texto_con_campos_especiales_siemens(texto): + campos_especiales = len(re.findall(r"<#>", texto) ) + return campos_especiales > 0 + + + +# SIEMENS +# +# Transforma: "A[[digits]]/[[digits]]/[[digits]] Air - M - Necessaria Manutenzione Filtro" -> +# "A<>/<>/<> Air - M<#>" /> - Necessaria Manutenzione Filtro" +# +# Este procesamiento se aplica a las celdas traducidas +def compactar_celda_traducida_siemens(celda_traducida): + if pd.isnull(celda_traducida): + return celda_traducida + celda_traducida = compactar_celda_clave_siemens(celda_traducida) + + def reemplazar(match): + if match.group(1): # Si hay contenido dentro de <> + return "<#>" + return "<>" + + # Reemplaza <...> con <#> y [[digits]] con <> + return re.sub(r"<(.*?)>|\[\[digits\]\]", reemplazar, str(celda_traducida)) + +# SIEMENS +# de "A271/47/6 Air - M - Necessaria Manutenzione Filtro" -> [271,47,6] +# Obtener la secuencias de dígitos por [[digits]] +def obtener_digitos_celda_original_siemens(celda_original): + if pd.isnull(celda_original): + return [] + + # Primero, reemplazamos temporalmente el contenido de los tags con un marcador + texto_sin_tags = re.sub(r'<[^>]*>', '<>', str(celda_original)) + + # Ahora buscamos los dígitos + digitos = re.findall(r'\d+', texto_sin_tags) + + return digitos + +# SIEMENS +# Original Traducida +# Transforma: "A271/47/6 Air - M - Necessaria Manutenzione Filtro" , "A<>/<>/<> Air - M<#> - Filter Maintenance Required" -> +# "A271/47/6 Air - M - Necessaria Manutenzione Filtro" +# +# Este procesamiento se aplica a las celdas traducidas para regresar al valor original +def decompactar_celda_traducida_siemens(celda_original, celda_traducida): + digitos = obtener_digitos_celda_original_siemens(celda_original) + celda_destino = celda_traducida + + # Replace <> with digits + for d in digitos: + celda_destino = celda_destino.replace("<>", d, 1) + + # Replace <#> with original content within <> + original_tags = re.findall(r"<.*?>", celda_original) + translated_tags = re.findall(r"<#>", celda_destino) + + for orig, trans in zip(original_tags, translated_tags): + celda_destino = celda_destino.replace(trans, orig, 1) + + return celda_destino + + +# +# SIEMENS +# +def verificar_celda_traducida_siemens(celda_clave, celda_traducida): + # Contar los placeholders de dígitos + digitos_clave = celda_clave.count("[[digits]]") + digitos_traducida = celda_traducida.count("<>") + + # Contar los placeholders de tags + tags_clave = sum(1 for tag in re.findall(r"<.*?>", celda_clave) if tag != "[[digits]]") + tags_traducida = celda_traducida.count("<#>") + + # Verificar si las cantidades coinciden + if digitos_clave == digitos_traducida and tags_clave == tags_traducida: + return True , "" + else: + text_error = f"Error de verificación:" + f" - Celda clave: {celda_clave}" + f" - Celda traducida: {celda_traducida}" + text_error += f" - Dígitos en clave: {digitos_clave}, Dígitos en traducida: {digitos_traducida}" + text_error += f" - Tags en clave: {tags_clave}, Tags en traducida: {tags_traducida}" + return False, text_error + + +# ALLEN BRADLEY +def texto_requiere_traduccion_ab(texto, logger): + palabras = re.findall(r"\b\w{4,}\b", texto) + campos_especiales = re.findall(r"/\*.*?\*/", texto) + total_palabras_largas = len(palabras) > 0 + total_campos_especiales = len(campos_especiales) + total_campos_marcados = len(re.findall(r"/\*#\*/", texto)) + solo_campos_especiales = total_campos_especiales == total_campos_marcados + requiere_traduccion = total_palabras_largas or not solo_campos_especiales + + logger.debug( + f"Decisión de traducción para texto '{texto}': {'Sí' if requiere_traduccion else 'No'} " + f"(palabras > 3 letras: {total_palabras_largas}, " + f"solo campos especiales: {solo_campos_especiales})" + ) + return requiere_traduccion + + + +# ALLEN BRADLEY +# Transforma: "A271/47/6 Air - M/*field ref="0" */ - Necessaria Manutenzione Filtro" -> +# "A[[digits]]/[[digits]]/[[digits]] Air - M/*field ref="[[digits]]" */ - Necessaria Manutenzione Filtro" +# +# Este procesamiento se aplica a las celdas clave +def compactar_celda_clave_ab(celda_original): + if pd.isnull(celda_original): + return celda_original + + def reemplazar(match): + if match.group(1): # Si hay contenido dentro de /*...*/ + return f"/*{match.group(1)}*/" + return "[[digits]]" + + # Reemplaza dígitos fuera de /*...*/ con [[digits]], y preserva el contenido dentro de /*...*/ + return re.sub(r"/\*(.*?)\*/|\d+", reemplazar, str(celda_original)) + + +# ALLEN BRADLEY +# Transforma: "A[[digits]]/[[digits]]/[[digits]] Air - M/*field ref="[[digits]]" */ - Necessaria Manutenzione Filtro" -> +# "A<>/<>/<> Air - M/*#*/ - Necessaria Manutenzione Filtro" +# +# Este procesamiento se aplica a las celdas traducidas +def compactar_celda_traducida_ab(celda_traducida): + if pd.isnull(celda_traducida): + return celda_traducida + celda_traducida = compactar_celda_clave_ab(celda_traducida) + + def reemplazar(match): + if match.group(1): # Si hay contenido dentro de /*...*/ + return "/*#*/" + return "<>" + + # Reemplaza /*...*/ con /*#*/ y [[digits]] con <> + return re.sub(r"/\*(.*?)\*/|\[\[digits\]\]", reemplazar, str(celda_traducida)) + + +# ALLEN BRADLEY +# De "A271/47/6 Air - M/*field ref="0" */ - Necessaria Manutenzione Filtro" -> [271,47,6] +# Obtener las secuencias de dígitos para [[digits]] +def obtener_digitos_celda_original_ab(celda_original): + if pd.isnull(celda_original): + return [] + + # Reemplazamos temporalmente el contenido de los comentarios con un marcador + texto_sin_tags = re.sub(r'/\*[^*]*\*/', '<>', str(celda_original)) + + # Ahora buscamos los dígitos + digitos = re.findall(r'\d+', texto_sin_tags) + + return digitos + + +# ALLEN BRADLEY +# Transformación para regresar al valor original +def decompactar_celda_traducida_ab(celda_original, celda_traducida): + digitos = obtener_digitos_celda_original_ab(celda_original) + celda_destino = celda_traducida + + # Reemplaza <> con dígitos + for d in digitos: + celda_destino = celda_destino.replace("<>", d, 1) + + # Reemplaza /*#*/ con el contenido original dentro de /*...*/ + original_tags = re.findall(r"/\*.*?\*/", celda_original) + translated_tags = re.findall(r"/\*#\*/", celda_destino) + + for orig, trans in zip(original_tags, translated_tags): + celda_destino = celda_destino.replace(trans, orig, 1) + + return celda_destino + + +# +# ALLEN BRADLEY +# +def verificar_celda_traducida_ab(celda_clave, celda_traducida): + # Contar los placeholders de dígitos + digitos_clave = celda_clave.count("[[digits]]") + digitos_traducida = celda_traducida.count("<>") + + # Contar los placeholders de comentarios + tags_clave = sum(1 for tag in re.findall(r"/\*.*?\*/", celda_clave) if tag != "[[digits]]") + tags_traducida = celda_traducida.count("/*#*/") + + # Verificar si las cantidades coinciden + if digitos_clave == digitos_traducida and tags_clave == tags_traducida: + return True, "" + else: + text_error = f"Error de verificación:" + f" - Celda clave: {celda_clave}" + f" - Celda traducida: {celda_traducida}" + text_error += f" - Dígitos en clave: {digitos_clave}, Dígitos en traducida: {digitos_traducida}" + text_error += f" - Comentarios en clave: {tags_clave}, Comentarios en traducida: {tags_traducida}" + return False, text_error + + +# +# ALLEN BRADLEY +# +def texto_con_campos_especiales_ab(texto): + campos_especiales = len(re.findall(r"/\*#\*/", texto)) + return campos_especiales > 0 + + +if __name__ == "__main__": + # SIEMENS + print("****************** SIEMENS ***************************") + celda_original = 'A271/47/6 Air - M - Necessaria Manutenzione Filtro' + celda_original = 'DB/DB/DB' + celda_original = 'Text' + + celda_clave = compactar_celda_clave_siemens(celda_original) + celda_tradc = compactar_celda_traducida_siemens(celda_original) + " TEXTO " + + print() + print("Celda Original : " +celda_original) + print("Celda Clave : " + celda_clave) + print("Celda Traducida: " + celda_tradc) + print("Digitos : " + ','.join(obtener_digitos_celda_original_siemens(celda_original))) + print("Celda : " + decompactar_celda_traducida_siemens(celda_original, celda_tradc)) + print("Celda Original : " + celda_original) + + print(verificar_celda_traducida_siemens(celda_clave=celda_clave, celda_traducida= celda_tradc)) + + # ALLEN BRADLEY + print("****************** ALLEN BRADLEY ***************************") + celda_original = 'A271/47/6 /*N:4 {#1.#4.VFix[1]} NOFILL DP:1*/ m/min SPEED' + celda_original = 'Formats_x000D_Comparing' + + celda_clave = compactar_celda_clave_ab(celda_original) + celda_tradc = compactar_celda_traducida_ab(celda_original) + " TEXTO " + celda_tradc = 'Formatos_x<>D_Comparación' + + print() + print("Celda Original : " +celda_original) + print("Celda Clave : " + celda_clave) + print("Celda Traducida: " + celda_tradc) + print("Digitos : " + ','.join(obtener_digitos_celda_original_ab(celda_original))) + print("Celda : " + decompactar_celda_traducida_ab(celda_original, celda_tradc)) + print("Celda Original : " + celda_original) + + print(verificar_celda_traducida_ab(celda_clave=celda_clave, celda_traducida= celda_tradc)) diff --git a/funciones_comunes/manejoArchivos.py b/funciones_comunes/manejoArchivos.py new file mode 100644 index 0000000..bc9fcca --- /dev/null +++ b/funciones_comunes/manejoArchivos.py @@ -0,0 +1,50 @@ +import pandas as pd +import tkinter as tk +from tkinter import filedialog +import os +import subprocess + +def select_file(extension = "txt"): + """ + Opens a file dialog to select a .db file and returns the selected file path. + """ + root = tk.Tk() + root.withdraw() # Use to hide the tkinter root window + + # Open file dialog and return the selected file path + file_path = filedialog.askopenfilename( + title=f"Select a .{extension} file", + filetypes=((f"{extension} files", f"*.{extension}"), ("All files", "*.*")) + ) + return file_path + +def open_file_explorer(path): + """ + Opens the file explorer at the given path, correctly handling paths with spaces. + """ + # Normalize the path to ensure it's in the correct format + normalized_path = os.path.normpath(path) + + # Check if the path is a directory or a file and format the command accordingly + if os.path.isdir(normalized_path): + # If it's a directory, use the 'explorer' command directly + command = f'explorer "{normalized_path}"' + else: + # If it's a file, use the 'explorer /select,' command to highlight the file in its folder + command = f'explorer /select,"{normalized_path}"' + + # Execute the command using subprocess.run, with shell=True to handle paths with spaces correctly + subprocess.run(command, shell=True) + +def select_directory(): + """ + Opens a file dialog to select a directory and returns the selected directory path. + """ + root = tk.Tk() + root.withdraw() # Use to hide the tkinter root window + + # Open directory dialog and return the selected directory path + directory_path = filedialog.askdirectory( + title="Select a directory" + ) + return directory_path \ No newline at end of file