import json from typing import List, Dict, Any, Optional def format_data_type_for_display(var_info: Dict[str, Any]) -> str: """Formatea la declaración de tipo para visualización en Markdown.""" 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 format_offset_for_display(byte_offset: float) -> str: """Formatea el offset como X.Y o solo X si es .0.""" if byte_offset == float(int(byte_offset)): return str(int(byte_offset)) return f"{byte_offset:.1f}" def generate_members_table_md( members: List[Dict[str, Any]], path_prefix: str = "", is_udt_definition: bool = False, include_current_value: bool = False ) -> List[str]: """Genera líneas de tabla Markdown para una lista de miembros.""" md_lines = [] for var_info in members: name_display = f"{path_prefix}{var_info['name']}" # Para miembros expandidos de un UDT, su nombre ya está completo en la jerarquía del JSON. # La recursión ya habrá construido el path_prefix. # No necesitamos hacer nada especial aquí si `is_udt_expanded_member` es true, # ya que esta función se llama recursivamente sobre `children`. data_type_display = format_data_type_for_display(var_info) offset_display = format_offset_for_display(var_info['byte_offset']) size_bytes_display = str(var_info['size_in_bytes']) bit_size_display = str(var_info.get('bit_size', '0')) if var_info.get('bit_size', 0) > 0 else "" initial_value_display = str(var_info.get('initial_value', '')).replace("|", "\\|").replace("\n", " ") comment_display = str(var_info.get('comment', '')).replace("|", "\\|").replace("\n", " ") row = f"| `{name_display}` | `{data_type_display}` | {offset_display} | {size_bytes_display} | {bit_size_display} | `{initial_value_display}` |" if include_current_value: current_value_display = "" # Si es un array y tiene current_element_values if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict): # Mostrar un resumen o un placeholder para arrays complejos en la tabla principal # Los valores detallados del array se listarán en la sección BEGIN. num_elements = sum(dim['count'] for dim in var_info.get('array_dimensions', [])) if var_info.get('array_dimensions') else 1 if num_elements == 0 and var_info.get("array_dimensions"): # Caso de ARRAY [x..y] donde x > y (raro, pero posible) num_elements = 1 # Para evitar división por cero o lógica extraña. assigned_elements = len(var_info["current_element_values"]) if assigned_elements > 0: current_value_display = f"{assigned_elements} elemento(s) asignado(s) en BEGIN" elif var_info.get("current_value") is not None: # Para arrays con una asignación global (raro en BEGIN) current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ") else: current_value_display = "" elif var_info.get("current_value") is not None: current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ") row += f" `{current_value_display}` |" row += f" {comment_display} |" md_lines.append(row) # Recursión para hijos de STRUCTs o miembros expandidos de UDTs # `is_udt_expanded_member` en el JSON nos dice si los 'children' son la expansión de un UDT. if var_info.get("children"): # El prefijo para los hijos es el nombre completo del padre actual. # Si el hijo es un miembro expandido de UDT, su propio nombre en 'children' ya es el nombre final del miembro. # Si el hijo es parte de un STRUCT anidado, su nombre es relativo al STRUCT. md_lines.extend(generate_members_table_md( var_info["children"], f"{name_display}.", is_udt_definition, include_current_value )) return md_lines def generate_json_documentation(data: Dict[str, Any], output_filename: str): """Genera la documentación Markdown completa para el archivo JSON parseado.""" lines = [] lines.append(f"# Documentación del Archivo de Datos S7 Parseado") current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") lines.append(f"_Generado el: {current_date}_") lines.append("") lines.append("Este documento describe la estructura y el contenido del archivo JSON generado por el parser de fuentes S7 (`x3.py`).") lines.append("") # --- Sección UDTs --- lines.append("## 1. Tipos de Datos de Usuario (UDTs)") lines.append("") if data.get("udts"): for udt in data["udts"]: lines.append(f'### 1.{data["udts"].index(udt) + 1}. UDT: `{udt["name"]}`') if udt.get("family"): lines.append(f"- **Familia**: {udt['family']}") if udt.get("version"): lines.append(f"- **Versión**: {udt['version']}") lines.append(f"- **Tamaño Total**: {udt['total_size_in_bytes']} bytes") lines.append("") lines.append("#### Miembros del UDT:") lines.append("| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |") lines.append("|---|---|---|---|---|---|---|") lines.extend(generate_members_table_md(udt.get("members", []), is_udt_definition=True, include_current_value=False)) lines.append("") else: lines.append("No se encontraron UDTs en el archivo JSON.") lines.append("") # --- Sección DBs --- lines.append("## 2. Bloques de Datos (DBs)") lines.append("") if data.get("dbs"): for db in data["dbs"]: lines.append(f'### 2.{data["dbs"].index(db) + 1}. DB: `{db["name"]}`') if db.get("title"): lines.append(f"- **TITLE**: `{db['title']}`") if db.get("family"): lines.append(f"- **Familia**: {db['family']}") if db.get("version"): lines.append(f"- **Versión**: {db['version']}") lines.append(f"- **Tamaño Declaraciones**: {db['total_size_in_bytes']} bytes") lines.append("") lines.append("#### Miembros del DB (Sección de Declaración):") lines.append("| Nombre Miembro (Ruta) | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial (Decl.) | Valor Actual (Efectivo) | Comentario |") lines.append("|---|---|---|---|---|---|---|---|") lines.extend(generate_members_table_md(db.get("members", []), include_current_value=True)) lines.append("") # Sección BEGIN ordered_assignments = db.get("_begin_block_assignments_ordered") if ordered_assignments: lines.append("#### Contenido del Bloque `BEGIN` (Valores Actuales Asignados):") lines.append("El bloque `BEGIN` define los valores actuales de las variables en el DB. Las siguientes asignaciones fueron encontradas, en orden:") lines.append("") lines.append("```scl") # Usar SCL para syntax highlighting si el visualizador Markdown lo soporta for path, value in ordered_assignments: val_str = str(value) if val_str.lower() == "true": val_str = "TRUE" elif val_str.lower() == "false": val_str = "FALSE" lines.append(f" {path} := {val_str};") lines.append("```") lines.append("") else: lines.append("No se encontraron asignaciones en el bloque `BEGIN` (o no fue parseado).") lines.append("") else: lines.append("No se encontraron DBs en el archivo JSON.") # Guardar el archivo Markdown try: with open(output_filename, 'w', encoding='utf-8') as f: for line in lines: f.write(line + "\n") print(f"Documentación Markdown completa generada: {output_filename}") except Exception as e: print(f"Error al escribir el archivo Markdown de documentación {output_filename}: {e}") # --- Main --- if __name__ == "__main__": from datetime import datetime # Mover import aquí # Asume que el JSON es generado por la última versión de x3.py # (ej. parsed_s7_data_x3_v_final_3.json o el nombre que uses) json_input_filename = "parsed_s7_data.json" markdown_output_filename = "documentacion_json_s7.md" try: with open(json_input_filename, 'r', encoding='utf-8') as f: data_from_json = json.load(f) print(f"Archivo JSON '{json_input_filename}' cargado correctamente.") except FileNotFoundError: print(f"Error: No se encontró el archivo JSON de entrada: {json_input_filename}") exit() except json.JSONDecodeError: print(f"Error: El archivo JSON de entrada no es válido: {json_input_filename}") exit() except Exception as e: print(f"Error al leer el archivo JSON {json_input_filename}: {e}") exit() generate_json_documentation(data_from_json, markdown_output_filename)