# --- x4_refactored.py --- import json from typing import List, Dict, Any import sys import os import glob from x3 import flatten_db_structure script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ) sys.path.append(script_root) from backend.script_utils import load_configuration def find_working_directory(): configs = load_configuration() working_directory = configs.get("working_directory") if not working_directory: print("No working directory specified in the configuration file.") sys.exit(1) return working_directory def format_data_type_for_source(var_info: Dict[str, Any]) -> str: base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"] type_str = "" if var_info.get("array_dimensions"): dims_str = ",".join([f"{d['lower_bound']}..{d['upper_bound']}" for d in var_info["array_dimensions"]]) type_str += f"ARRAY [{dims_str}] OF " type_str += base_type if var_info["data_type"].upper() == "STRING" and var_info.get("string_length") is not None: type_str += f"[{var_info['string_length']}]" return type_str def generate_variable_declaration_for_source(var_info: Dict[str, Any], indent_level: int) -> str: indent_str = " " * indent_level type_declaration_str = format_data_type_for_source(var_info) line = f'{indent_str}{var_info["name"]} : {type_declaration_str}' if var_info.get("initial_value") is not None: initial_val = var_info["initial_value"] initial_val_str = "TRUE" if isinstance(initial_val, bool) and initial_val else \ "FALSE" if isinstance(initial_val, bool) and not initial_val else \ str(initial_val) line += f' := {initial_val_str}' is_multiline_struct_def = (var_info["data_type"].upper() == "STRUCT" and \ not var_info.get("udt_source_name") and \ var_info.get("children")) if not is_multiline_struct_def: line += ';' if var_info.get("comment"): line += f'\t// {var_info["comment"]}' return line def generate_struct_members_for_source(members: List[Dict[str, Any]], indent_level: int) -> List[str]: lines = [] for var_info in members: if var_info.get("is_udt_expanded_member"): continue if var_info["data_type"].upper() == "STRUCT" and \ not var_info.get("udt_source_name") and \ var_info.get("children"): current_indent_str = " " * indent_level lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT') lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1)) lines.append(f'{current_indent_str}END_STRUCT;') else: lines.append(generate_variable_declaration_for_source(var_info, indent_level)) return lines def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int) -> List[str]: """ Genera asignaciones del bloque BEGIN para todas las variables con valores actuales, ordenadas estrictamente por offset (byte.bit). """ indent_str = " " * indent_level lines = [] # Obtener todas las variables aplanadas y ordenadas flat_vars = flatten_db_structure(db_info) # Para cada variable en el orden correcto, generar la asignación for var in flat_vars: # Verificar que tenga un valor actual para asignar if var.get("current_value") is not None: value_str = str(var["current_value"]) # Convertir valores booleanos a TRUE/FALSE según estándar S7 if value_str.lower() == "true": value_str = "TRUE" elif value_str.lower() == "false": value_str = "FALSE" # Generar la línea de asignación lines.append(f"{indent_str}{var['full_path']} := {value_str};") return lines def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]: """ Genera una tabla markdown completa con offsets de bits correctos. """ lines = [] lines.append(f"## Documentación para DB: {db_info['name']}") lines.append("") lines.append("| Address | Name | Type | Initial Value | Actual Value | Comment |") lines.append("|---|---|---|---|---|---|") # Obtener todas las variables aplanadas (ya ordenadas por offset) flat_vars = flatten_db_structure(db_info) # Mostrar todas las variables, incluyendo elementos de array for var in flat_vars: # Usar el address_display pre-calculado address = var["address_display"] name_for_display = var["full_path"] # Formatear tipo adecuadamente según sea variable normal o elemento de array if var.get("is_array_element"): # Para elementos de array, mostrar solo el tipo base if "array_dimensions" in var: # Si todavía tenemos información de array, eliminar la parte ARRAY[..] base_type = var["data_type"] data_type_str = base_type else: data_type_str = var["data_type"] else: # Para variables normales, mostrar tipo completo data_type_str = format_data_type_for_source(var) # Formatear valores para la tabla initial_value = str(var.get("initial_value", "")).replace("|", "\\|").replace("\n", " ") actual_value = str(var.get("current_value", "")).replace("|", "\\|").replace("\n", " ") comment = str(var.get("comment", "")).replace("|", "\\|").replace("\n", " ") lines.append(f"| {address} | {name_for_display} | {data_type_str} | {initial_value} | {actual_value} | {comment} |") return lines def generate_s7_source_code_lines(data: Dict[str, Any]) -> List[str]: lines = [] for udt in data.get("udts", []): lines.append(f'TYPE "{udt["name"]}"') if udt.get("family"): lines.append(f' FAMILY : {udt["family"]}') if udt.get("version"): lines.append(f' VERSION : {udt["version"]}') lines.append("") lines.append(" STRUCT") lines.extend(generate_struct_members_for_source(udt["members"], 2)) lines.append(" END_STRUCT;") lines.append(f'END_TYPE') lines.append("") for db in data.get("dbs", []): lines.append(f'DATA_BLOCK "{db["name"]}"') if db.get("title"): lines.append(f' TITLE = {db["title"]}') if db.get("family"): lines.append(f' FAMILY : {db["family"]}') if db.get("version"): lines.append(f' VERSION : {db["version"]}') lines.append("") lines.append(" STRUCT") lines.extend(generate_struct_members_for_source(db["members"], 2)) lines.append(" END_STRUCT;") begin_assignments = generate_begin_block_assignments(db, 1) if begin_assignments: lines.append("BEGIN") lines.extend(begin_assignments) lines.append(f'END_DATA_BLOCK') lines.append("") return lines def main(): working_dir = find_working_directory() print(f"Using working directory: {working_dir}") input_json_dir = os.path.join(working_dir, "json") documentation_dir = os.path.join(working_dir, "documentation") os.makedirs(documentation_dir, exist_ok=True) print(f"Los archivos de documentación generados se guardarán en: {documentation_dir}") json_files_to_process = glob.glob(os.path.join(input_json_dir, "*.json")) if not json_files_to_process: print(f"No se encontraron archivos .json en {input_json_dir}") return print(f"Archivos JSON encontrados para procesar: {len(json_files_to_process)}") for json_input_filepath in json_files_to_process: json_filename_base = os.path.splitext(os.path.basename(json_input_filepath))[0] current_json_filename = os.path.basename(json_input_filepath) print(f"\n--- Procesando archivo JSON: {current_json_filename} ---") s7_output_filename = os.path.join(documentation_dir, f"{json_filename_base}.txt") md_output_filename = os.path.join(documentation_dir, f"{json_filename_base}.md") try: with open(json_input_filepath, 'r', encoding='utf-8') as f: data_from_json = json.load(f) print(f"Archivo JSON '{current_json_filename}' cargado correctamente.") except Exception as e: print(f"Error al cargar/leer {current_json_filename}: {e}") continue # Generar archivo S7 (.txt) s7_code_lines = generate_s7_source_code_lines(data_from_json) try: with open(s7_output_filename, 'w', encoding='utf-8') as f: for line in s7_code_lines: f.write(line + "\n") print(f"Archivo S7 reconstruido generado: {s7_output_filename}") except Exception as e: print(f"Error al escribir el archivo S7 {s7_output_filename}: {e}") # Generar archivo Markdown (.md) para todos los DBs en este JSON all_db_markdown_lines = [] if data_from_json.get("dbs"): all_db_markdown_lines.append(f"# Documentación S7 para {json_filename_base}") all_db_markdown_lines.append(f"_Fuente JSON: {current_json_filename}_") all_db_markdown_lines.append("") for db_index, db_to_document in enumerate(data_from_json["dbs"]): if db_index > 0: all_db_markdown_lines.append("\n---\n") markdown_lines_for_one_db = generate_markdown_table(db_to_document) all_db_markdown_lines.extend(markdown_lines_for_one_db) all_db_markdown_lines.append("") try: with open(md_output_filename, 'w', encoding='utf-8') as f: for line in all_db_markdown_lines: f.write(line + "\n") print(f"Archivo Markdown de documentación generado: {md_output_filename}") except Exception as e: print(f"Error al escribir el archivo Markdown {md_output_filename}: {e}") else: print(f"No se encontraron DBs en {current_json_filename} para generar documentación Markdown.") with open(md_output_filename, 'w', encoding='utf-8') as f: f.write(f"# Documentación S7 para {json_filename_base}\n\n_Fuente JSON: {current_json_filename}_\n\nNo se encontraron Bloques de Datos (DBs) en este archivo JSON.\n") print(f"Archivo Markdown generado (sin DBs): {md_output_filename}") print("\n--- Proceso de generación de documentación completado ---") if __name__ == "__main__": main()