Refactor code structure for improved readability and maintainability

This commit is contained in:
Miguel 2025-08-23 17:05:44 +02:00
parent 18f6cdaa4f
commit c3088e9957
4 changed files with 16438 additions and 111 deletions

View File

@ -3,7 +3,10 @@
from lxml import etree from lxml import etree
import traceback import traceback
# Definición de 'ns' (asegúrate de que esté definida correctamente en tu archivo) # Definición de 'ns' - Namespaces para TIA Portal XML (v15-v20)
# NOTA: Los valores iniciales corresponden a versiones recientes (v18-v20)
# La función adapt_namespaces() actualiza automáticamente estos valores
# para soportar versiones anteriores (v15, v16, v17) según el XML detectado
ns = { ns = {
"iface": "http://www.siemens.com/automation/Openness/SW/Interface/v5", "iface": "http://www.siemens.com/automation/Openness/SW/Interface/v5",
"flg": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4", "flg": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4",
@ -563,6 +566,17 @@ def adapt_namespaces(root):
"""Actualiza dinámicamente los valores en el diccionario global `ns` para que """Actualiza dinámicamente los valores en el diccionario global `ns` para que
coincidan con los namespaces reales presentes en el XML exportado por TIA. coincidan con los namespaces reales presentes en el XML exportado por TIA.
SOPORTE DE VERSIONES TIA PORTAL:
- v15, v16, v17: Versiones anteriores con URIs diferentes
- v18, v19, v20: Versiones recientes (valores por defecto en ns)
Esta función detecta automáticamente la versión de TIA Portal utilizada
analizando los namespaces declarados en el XML y actualiza el diccionario
global 'ns' para usar los URIs correctos.
Args:
root: Elemento raíz del árbol XML parseado con lxml
Debe llamarse después de obtener la raíz (`root = tree.getroot()`). Si en el Debe llamarse después de obtener la raíz (`root = tree.getroot()`). Si en el
XML aparecen nuevas versiones (p.ej. v6) de los URIs, esta función las XML aparecen nuevas versiones (p.ej. v6) de los URIs, esta función las
detectará y sobreescribirá las entradas correspondientes en `ns`. detectará y sobreescribirá las entradas correspondientes en `ns`.
@ -612,7 +626,18 @@ def adapt_namespaces(root):
detected["iface"] = iface_uri detected["iface"] = iface_uri
if detected: if detected:
print(f"INFO: Namespaces TIA Portal detectados y adaptados:")
for prefix, uri in detected.items():
# Extraer versión del URI si es posible
version_info = ""
if "/v" in uri:
version_part = uri.split("/v")[-1]
if version_part.isdigit():
version_info = f" (v{version_part})"
print(f" - {prefix}: {uri}{version_info}")
ns.update(detected) ns.update(detected)
else:
print("INFO: Usando namespaces por defecto (TIA Portal v18-v20)")
# --- función auxiliar privada para adapt_namespaces --- # --- función auxiliar privada para adapt_namespaces ---

View File

@ -4,6 +4,10 @@
Este documento describe un pipeline de scripts de Python diseñado para convertir bloques de función o funciones (FC/FB) escritos en Ladder Logic (LAD) desde archivos XML de TIA Portal Openness a un código SCL (Structured Control Language) semánticamente equivalente. Este documento describe un pipeline de scripts de Python diseñado para convertir bloques de función o funciones (FC/FB) escritos en Ladder Logic (LAD) desde archivos XML de TIA Portal Openness a un código SCL (Structured Control Language) semánticamente equivalente.
**Versiones TIA Portal Soportadas:** v15, v16, v17, v18, v19, v20
- Detección automática de namespaces XML según la versión
- Compatibilidad total con exportaciones de TIA Portal Openness
El proceso se divide en tres etapas principales, cada una manejada por un script específico: El proceso se divide en tres etapas principales, cada una manejada por un script específico:
1. **XML a JSON Enriquecido (`x1_to_json.py`):** Parsea el XML de Openness, extrae la estructura lógica y las conexiones explícitas, e **infiere conexiones implícitas** (especialmente las habilitaciones EN) para crear un archivo JSON detallado. 1. **XML a JSON Enriquecido (`x1_to_json.py`):** Parsea el XML de Openness, extrae la estructura lógica y las conexiones explícitas, e **infiere conexiones implícitas** (especialmente las habilitaciones EN) para crear un archivo JSON detallado.

View File

@ -4,6 +4,7 @@ LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
Este script convierte archivos XML de Siemens LAD/FUP a un formato JSON simplificado. Este script convierte archivos XML de Siemens LAD/FUP a un formato JSON simplificado.
""" """
# ToUpload/x1_to_json.py # ToUpload/x1_to_json.py
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json import json
@ -13,10 +14,13 @@ import sys
import traceback import traceback
import importlib import importlib
from lxml import etree from lxml import etree
from lxml.etree import XMLSyntaxError as etree_XMLSyntaxError # Alias para evitar conflicto from lxml.etree import (
XMLSyntaxError as etree_XMLSyntaxError,
) # Alias para evitar conflicto
from collections import defaultdict from collections import defaultdict
import copy import copy
import time # <-- NUEVO: Para obtener metadatos import time # <-- NUEVO: Para obtener metadatos
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
) )
@ -25,7 +29,12 @@ from backend.script_utils import load_configuration
# Importar funciones comunes y namespaces desde el nuevo módulo de utils # Importar funciones comunes y namespaces desde el nuevo módulo de utils
try: try:
from parsers.parser_utils import ns, get_multilingual_text, parse_interface_members, adapt_namespaces from parsers.parser_utils import (
ns,
get_multilingual_text,
parse_interface_members,
adapt_namespaces,
)
except ImportError as e: except ImportError as e:
print( print(
f"Error crítico: No se pudieron importar funciones desde parsers.parser_utils: {e}" f"Error crítico: No se pudieron importar funciones desde parsers.parser_utils: {e}"
@ -196,9 +205,9 @@ def load_parsers(parsers_dir="parsers"):
f" Advertencia: Parser para '{lang_upper}' en {full_module_name} sobrescribe definición anterior." f" Advertencia: Parser para '{lang_upper}' en {full_module_name} sobrescribe definición anterior."
) )
parser_map[lang_upper] = parser_func parser_map[lang_upper] = parser_func
#print( # print(
# f" - Cargado parser para '{lang_upper}' desde {module_name_rel}.py" # f" - Cargado parser para '{lang_upper}' desde {module_name_rel}.py"
#) # )
else: else:
print( print(
f" Advertencia: Formato inválido en get_parser_info de {full_module_name}." f" Advertencia: Formato inválido en get_parser_info de {full_module_name}."
@ -253,7 +262,7 @@ def convert_xml_to_json(xml_filepath, json_filepath):
parser = etree.XMLParser(remove_blank_text=True, recover=True) parser = etree.XMLParser(remove_blank_text=True, recover=True)
tree = etree.parse(xml_filepath, parser) tree = etree.parse(xml_filepath, parser)
root = tree.getroot() root = tree.getroot()
# Ajustar namespaces dinámicamente para soportar distintas versiones de TIA # Ajustar namespaces dinámicamente para soportar TIA Portal v15-v20
try: try:
adapt_namespaces(root) adapt_namespaces(root)
except Exception as e_ns: except Exception as e_ns:
@ -261,21 +270,25 @@ def convert_xml_to_json(xml_filepath, json_filepath):
print("Paso 1: Parseo XML completado.") print("Paso 1: Parseo XML completado.")
result = None result = None
the_block = None # Nodo principal (UDT, TagTable, o SW.Blocks.*) the_block = None # Nodo principal (UDT, TagTable, o SW.Blocks.*)
print("Paso 2: Detectando tipo de objeto principal...") print("Paso 2: Detectando tipo de objeto principal...")
# Intentar UDT # Intentar UDT
udt_element = root.find(".//SW.Types.PlcStruct", namespaces=root.nsmap) udt_element = root.find(".//SW.Types.PlcStruct", namespaces=root.nsmap)
if udt_element is not None: if udt_element is not None:
the_block = udt_element the_block = udt_element
result = parse_udt(the_block) # Llamar a la función de parseo de UDT result = parse_udt(the_block) # Llamar a la función de parseo de UDT
# Intentar Tag Table si no es UDT # Intentar Tag Table si no es UDT
if result is None: if result is None:
tag_table_element = root.find(".//SW.Tags.PlcTagTable", namespaces=root.nsmap) tag_table_element = root.find(
".//SW.Tags.PlcTagTable", namespaces=root.nsmap
)
if tag_table_element is not None: if tag_table_element is not None:
the_block = tag_table_element the_block = tag_table_element
result = parse_tag_table(the_block) # Llamar a la función de parseo de TagTable result = parse_tag_table(
the_block
) # Llamar a la función de parseo de TagTable
# Intentar Bloques (OB, FC, FB, DB) si no es UDT ni TagTable # Intentar Bloques (OB, FC, FB, DB) si no es UDT ni TagTable
if result is None: if result is None:
@ -286,62 +299,105 @@ def convert_xml_to_json(xml_filepath, json_filepath):
) )
if block_list: if block_list:
the_block = block_list[0] # Tomar el primer bloque encontrado the_block = block_list[0] # Tomar el primer bloque encontrado
block_tag_name = etree.QName(the_block.tag).localname # Nombre del tag (ej. SW.Blocks.OB) block_tag_name = etree.QName(
the_block.tag
).localname # Nombre del tag (ej. SW.Blocks.OB)
block_type_map = { block_type_map = {
"SW.Blocks.FC": "FC", "SW.Blocks.FB": "FB", "SW.Blocks.FC": "FC",
"SW.Blocks.GlobalDB": "GlobalDB", "SW.Blocks.OB": "OB", "SW.Blocks.FB": "FB",
"SW.Blocks.InstanceDB": "InstanceDB" # <-- ADDED: Recognize InstanceDB "SW.Blocks.GlobalDB": "GlobalDB",
"SW.Blocks.OB": "OB",
"SW.Blocks.InstanceDB": "InstanceDB", # <-- ADDED: Recognize InstanceDB
} }
block_type_found = block_type_map.get(block_tag_name, "UnknownBlockType") block_type_found = block_type_map.get(
print(f"Paso 2b: Bloque {block_tag_name} (Tipo: {block_type_found}) encontrado (ID={the_block.get('ID')}).") block_tag_name, "UnknownBlockType"
)
print(
f"Paso 2b: Bloque {block_tag_name} (Tipo: {block_type_found}) encontrado (ID={the_block.get('ID')})."
)
# --- Extraer información del Bloque (FC, FB, OB, DB) --- # --- Extraer información del Bloque (FC, FB, OB, DB) ---
print("Paso 3: Extrayendo atributos del bloque...") print("Paso 3: Extrayendo atributos del bloque...")
attribute_list_node = the_block.xpath("./AttributeList") # Buscar hijo directo attribute_list_node = the_block.xpath(
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown" "./AttributeList"
instance_of_name_val = None # <-- NUEVO: Para InstanceDB ) # Buscar hijo directo
instance_of_type_val = None # <-- NUEVO: Para InstanceDB block_name_val, block_number_val, block_lang_val = (
"Unknown",
None,
"Unknown",
)
instance_of_name_val = None # <-- NUEVO: Para InstanceDB
instance_of_type_val = None # <-- NUEVO: Para InstanceDB
block_comment_val = "" block_comment_val = ""
if attribute_list_node: if attribute_list_node:
attr_list = attribute_list_node[0] attr_list = attribute_list_node[0]
name_node = attr_list.xpath("./Name/text()") name_node = attr_list.xpath("./Name/text()")
block_name_val = name_node[0].strip() if name_node else block_name_val block_name_val = (
name_node[0].strip() if name_node else block_name_val
)
num_node = attr_list.xpath("./Number/text()") num_node = attr_list.xpath("./Number/text()")
try: block_number_val = int(num_node[0]) if num_node else None try:
except (ValueError, TypeError): block_number_val = None block_number_val = int(num_node[0]) if num_node else None
except (ValueError, TypeError):
block_number_val = None
lang_node = attr_list.xpath("./ProgrammingLanguage/text()") lang_node = attr_list.xpath("./ProgrammingLanguage/text()")
# Asignar lenguaje por defecto si no se encuentra # Asignar lenguaje por defecto si no se encuentra
block_lang_val = lang_node[0].strip() if lang_node else \ block_lang_val = (
("DB" if block_type_found in ["GlobalDB", "InstanceDB"] else "Unknown") # <-- MODIFIED: Include InstanceDB for DB language default lang_node[0].strip()
if lang_node
else (
"DB"
if block_type_found in ["GlobalDB", "InstanceDB"]
else "Unknown"
)
) # <-- MODIFIED: Include InstanceDB for DB language default
# <-- NUEVO: Extraer info de instancia si es InstanceDB --> # <-- NUEVO: Extraer info de instancia si es InstanceDB -->
if block_type_found == "InstanceDB": if block_type_found == "InstanceDB":
inst_name_node = attr_list.xpath("./InstanceOfName/text()") inst_name_node = attr_list.xpath("./InstanceOfName/text()")
instance_of_name_val = inst_name_node[0].strip() if inst_name_node else None instance_of_name_val = (
inst_type_node = attr_list.xpath("./InstanceOfType/text()") # Generalmente 'FB' inst_name_node[0].strip() if inst_name_node else None
instance_of_type_val = inst_type_node[0].strip() if inst_type_node else None )
print(f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje Bloque='{block_lang_val}'") inst_type_node = attr_list.xpath(
"./InstanceOfType/text()"
) # Generalmente 'FB'
instance_of_type_val = (
inst_type_node[0].strip() if inst_type_node else None
)
print(
f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje Bloque='{block_lang_val}'"
)
# Extraer comentario del bloque (puede estar en AttributeList o ObjectList) # Extraer comentario del bloque (puede estar en AttributeList o ObjectList)
comment_node_attr = attr_list.xpath("./Comment") comment_node_attr = attr_list.xpath("./Comment")
if comment_node_attr: if comment_node_attr:
block_comment_val = get_multilingual_text(comment_node_attr[0]) block_comment_val = get_multilingual_text(comment_node_attr[0])
else: else:
comment_node_obj = the_block.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']") comment_node_obj = the_block.xpath(
"./ObjectList/MultilingualText[@CompositionName='Comment']"
)
if comment_node_obj: if comment_node_obj:
block_comment_val = get_multilingual_text(comment_node_obj[0]) block_comment_val = get_multilingual_text(
comment_node_obj[0]
)
print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'") print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'")
else: else:
print(f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}.") print(
if block_type_found in ["GlobalDB", "InstanceDB"]: block_lang_val = "DB" # Default para DB/InstanceDB # <-- MODIFIED: Include InstanceDB f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}."
)
if block_type_found in ["GlobalDB", "InstanceDB"]:
block_lang_val = "DB" # Default para DB/InstanceDB # <-- MODIFIED: Include InstanceDB
# Inicializar diccionario de resultado para el bloque # Inicializar diccionario de resultado para el bloque
result = { result = {
"block_name": block_name_val, "block_number": block_number_val, "block_name": block_name_val,
"language": block_lang_val, "block_type": block_type_found, "block_number": block_number_val,
"language": block_lang_val,
"block_type": block_type_found,
"block_comment": block_comment_val, "block_comment": block_comment_val,
"interface": {}, "networks": [] "interface": {},
"networks": [],
} }
# --- Extraer Interfaz del Bloque --- # --- Extraer Interfaz del Bloque ---
@ -349,44 +405,81 @@ def convert_xml_to_json(xml_filepath, json_filepath):
interface_node = None interface_node = None
if attribute_list_node: if attribute_list_node:
interface_node_list = attribute_list_node[0].xpath("./Interface") interface_node_list = attribute_list_node[0].xpath("./Interface")
if interface_node_list: interface_node = interface_node_list[0] if interface_node_list:
interface_node = interface_node_list[0]
if interface_node is not None: if interface_node is not None:
# Buscar secciones dentro de la interfaz usando el namespace 'iface' # Buscar secciones dentro de la interfaz usando el namespace 'iface'
all_sections = interface_node.xpath(".//iface:Section", namespaces=ns) all_sections = interface_node.xpath(
".//iface:Section", namespaces=ns
)
if all_sections: if all_sections:
processed_sections = set() processed_sections = set()
for section in all_sections: for section in all_sections:
section_name = section.get("Name") section_name = section.get("Name")
if not section_name or section_name in processed_sections: continue if not section_name or section_name in processed_sections:
members_in_section = section.xpath("./iface:Member", namespaces=ns) continue
members_in_section = section.xpath(
"./iface:Member", namespaces=ns
)
if members_in_section: if members_in_section:
result["interface"][section_name] = parse_interface_members(members_in_section) result["interface"][section_name] = (
parse_interface_members(members_in_section)
)
processed_sections.add(section_name) processed_sections.add(section_name)
else: print("Advertencia: Nodo Interface no contiene secciones <iface:Section>.") else:
if not result["interface"]: print("Advertencia: Interface encontrada pero sin secciones procesables.") print(
"Advertencia: Nodo Interface no contiene secciones <iface:Section>."
)
if not result["interface"]:
print(
"Advertencia: Interface encontrada pero sin secciones procesables."
)
elif block_type_found == "GlobalDB": elif block_type_found == "GlobalDB":
# Buscar Static directamente si es DB y no hay nodo Interface # Buscar Static directamente si es DB y no hay nodo Interface
static_members = the_block.xpath(".//iface:Section[@Name='Static']/iface:Member", namespaces=ns) static_members = the_block.xpath(
".//iface:Section[@Name='Static']/iface:Member", namespaces=ns
)
if static_members: if static_members:
print("Paso 4: Encontrada sección Static para GlobalDB (sin nodo Interface explícito).") print(
result["interface"]["Static"] = parse_interface_members(static_members) "Paso 4: Encontrada sección Static para GlobalDB (sin nodo Interface explícito)."
else: print("Advertencia: No se encontró sección 'Static' para GlobalDB.") )
else: print(f"Advertencia: No se encontró <Interface> para bloque {block_type_found}.") result["interface"]["Static"] = parse_interface_members(
if not result["interface"]: print("Advertencia: No se pudo extraer información de la interfaz.") static_members
)
else:
print(
"Advertencia: No se encontró sección 'Static' para GlobalDB."
)
else:
print(
f"Advertencia: No se encontró <Interface> para bloque {block_type_found}."
)
if not result["interface"]:
print("Advertencia: No se pudo extraer información de la interfaz.")
# --- Procesar Redes (CompileUnits) --- # --- Procesar Redes (CompileUnits) ---
if block_type_found not in ["GlobalDB", "InstanceDB"]: # DBs/InstanceDBs no tienen redes ejecutables # <-- MODIFIED: Include InstanceDB if block_type_found not in [
"GlobalDB",
"InstanceDB",
]: # DBs/InstanceDBs no tienen redes ejecutables # <-- MODIFIED: Include InstanceDB
print("Paso 5: Buscando y PROCESANDO redes (CompileUnits)...") print("Paso 5: Buscando y PROCESANDO redes (CompileUnits)...")
networks_processed_count = 0 networks_processed_count = 0
result["networks"] = [] # Asegurar que esté inicializado result["networks"] = [] # Asegurar que esté inicializado
object_list_node = the_block.xpath("./ObjectList") object_list_node = the_block.xpath("./ObjectList")
if object_list_node: if object_list_node:
compile_units = object_list_node[0].xpath("./SW.Blocks.CompileUnit") compile_units = object_list_node[0].xpath(
print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.") "./SW.Blocks.CompileUnit"
for network_elem in compile_units: # network_elem es el nodo <SW.Blocks.CompileUnit> )
print(
f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit."
)
for (
network_elem
) in (
compile_units
): # network_elem es el nodo <SW.Blocks.CompileUnit>
networks_processed_count += 1 networks_processed_count += 1
network_id = network_elem.get("ID") network_id = network_elem.get("ID")
network_lang = "Unknown" network_lang = "Unknown"
@ -398,11 +491,17 @@ def convert_xml_to_json(xml_filepath, json_filepath):
# Determinar lenguaje # Determinar lenguaje
net_attr_list = network_elem.xpath("./AttributeList") net_attr_list = network_elem.xpath("./AttributeList")
if net_attr_list: if net_attr_list:
lang_node = net_attr_list[0].xpath("./ProgrammingLanguage/text()") lang_node = net_attr_list[0].xpath(
if lang_node: network_lang = lang_node[0].strip() "./ProgrammingLanguage/text()"
elif result["language"] != "Unknown": network_lang = result["language"] )
if lang_node:
network_lang = lang_node[0].strip()
elif result["language"] != "Unknown":
network_lang = result["language"]
print(f" - Procesando Red ID={network_id}, Lenguaje Red={network_lang}") print(
f" - Procesando Red ID={network_id}, Lenguaje Red={network_lang}"
)
parser_func = parser_map.get(network_lang.upper()) parser_func = parser_map.get(network_lang.upper())
parsed_network_data = None parsed_network_data = None
@ -410,73 +509,150 @@ def convert_xml_to_json(xml_filepath, json_filepath):
try: try:
parsed_network_data = parser_func(network_elem) parsed_network_data = parser_func(network_elem)
except Exception as e_parse: except Exception as e_parse:
print(f" ERROR durante el parseo de Red {network_id} ({network_lang}): {e_parse}") print(
parsed_network_data = {"id": network_id, "language": network_lang, "logic": [], "error": f"Parser failed: {e_parse}"} f" ERROR durante el parseo de Red {network_id} ({network_lang}): {e_parse}"
)
parsed_network_data = {
"id": network_id,
"language": network_lang,
"logic": [],
"error": f"Parser failed: {e_parse}",
}
else: else:
print(f" Advertencia: Lenguaje de red '{network_lang}' no soportado.") print(
parsed_network_data = {"id": network_id, "language": network_lang, "logic": [], "error": f"Unsupported language: {network_lang}"} f" Advertencia: Lenguaje de red '{network_lang}' no soportado."
)
parsed_network_data = {
"id": network_id,
"language": network_lang,
"logic": [],
"error": f"Unsupported language: {network_lang}",
}
# --- LÓGICA CORREGIDA: Asegurar que se procese incluso si el parser falló --- # --- LÓGICA CORREGIDA: Asegurar que se procese incluso si el parser falló ---
if parsed_network_data is None: # Si el parser falló TANTO que ni devolvió un dict if (
parsed_network_data = {"id": network_id, "language": network_lang, "logic": [], "error": "Parser function returned None"} parsed_network_data is None
): # Si el parser falló TANTO que ni devolvió un dict
parsed_network_data = {
"id": network_id,
"language": network_lang,
"logic": [],
"error": "Parser function returned None",
}
# Extraer Título y Comentario de la red SIEMPRE # Extraer Título y Comentario de la red SIEMPRE
try: try:
title_element = network_elem.xpath("./ObjectList/MultilingualText[@CompositionName='Title']") title_element = network_elem.xpath(
parsed_network_data["title"] = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" "./ObjectList/MultilingualText[@CompositionName='Title']"
)
parsed_network_data["title"] = (
get_multilingual_text(title_element[0])
if title_element
else f"Network {network_id}"
)
comment_elem_net = network_elem.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']") comment_elem_net = network_elem.xpath(
parsed_network_data["comment"] = get_multilingual_text(comment_elem_net[0]) if comment_elem_net else "" "./ObjectList/MultilingualText[@CompositionName='Comment']"
)
parsed_network_data["comment"] = (
get_multilingual_text(comment_elem_net[0])
if comment_elem_net
else ""
)
except Exception as e_comment: except Exception as e_comment:
print(f" ERROR extrayendo Título/Comentario para Red {network_id}: {e_comment}") print(
f" ERROR extrayendo Título/Comentario para Red {network_id}: {e_comment}"
)
# Añadir valores por defecto si falla la extracción # Añadir valores por defecto si falla la extracción
parsed_network_data["title"] = f"Network {network_id}" # Asegurar que title exista parsed_network_data["title"] = (
parsed_network_data["comment"] = f"// Error extrayendo comentario: {e_comment}" # Asegurar que comment exista f"Network {network_id}" # Asegurar que title exista
)
parsed_network_data["comment"] = (
f"// Error extrayendo comentario: {e_comment}" # Asegurar que comment exista
)
result["networks"].append(parsed_network_data) # Añadir SIEMPRE a la listaa result["networks"].append(
parsed_network_data
) # Añadir SIEMPRE a la listaa
if networks_processed_count == 0: print(f"Advertencia: ObjectList para {block_type_found} sin SW.Blocks.CompileUnit.") if networks_processed_count == 0:
else: print(f"Advertencia: No se encontró ObjectList para el bloque {block_type_found}.") print(
else: print(f"Paso 5: Saltando procesamiento de redes para {block_type_found}.") # <-- MODIFIED: Updated message f"Advertencia: ObjectList para {block_type_found} sin SW.Blocks.CompileUnit."
)
else:
print(
f"Advertencia: No se encontró ObjectList para el bloque {block_type_found}."
)
else:
print(
f"Paso 5: Saltando procesamiento de redes para {block_type_found}."
) # <-- MODIFIED: Updated message
else: # No se encontró ningún bloque SW.Blocks.* else: # No se encontró ningún bloque SW.Blocks.*
print("Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC/FB/GlobalDB/OB/InstanceDB>) después de descartar UDT/TagTable.") # <-- MODIFIED: Updated message print(
"Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC/FB/GlobalDB/OB/InstanceDB>) después de descartar UDT/TagTable."
) # <-- MODIFIED: Updated message
# --- Fin del manejo de Bloques --- # --- Fin del manejo de Bloques ---
# --- Escritura del JSON Final --- # --- Escritura del JSON Final ---
if result: if result:
# Añadir metadatos XML al diccionario final # Añadir metadatos XML al diccionario final
if xml_mod_time is not None: result["source_xml_mod_time"] = xml_mod_time if xml_mod_time is not None:
if xml_size is not None: result["source_xml_size"] = xml_size result["source_xml_mod_time"] = xml_mod_time
if xml_size is not None:
result["source_xml_size"] = xml_size
print("Paso 6: Escribiendo el resultado en el archivo JSON...") print("Paso 6: Escribiendo el resultado en el archivo JSON...")
# Advertencias finales si faltan partes clave # Advertencias finales si faltan partes clave
if result.get("block_type") not in ["PlcUDT", "PlcTagTable"] and not result.get("interface"): print("ADVERTENCIA FINAL: 'interface' está vacía en el JSON.") if result.get("block_type") not in [
if result.get("block_type") not in ["PlcUDT", "PlcTagTable", "GlobalDB", "InstanceDB"] and not result.get("networks"): print("ADVERTENCIA FINAL: 'networks' está vacía en el JSON.") # <-- MODIFIED: Include InstanceDB "PlcUDT",
"PlcTagTable",
] and not result.get("interface"):
print("ADVERTENCIA FINAL: 'interface' está vacía en el JSON.")
if result.get("block_type") not in [
"PlcUDT",
"PlcTagTable",
"GlobalDB",
"InstanceDB",
] and not result.get("networks"):
print(
"ADVERTENCIA FINAL: 'networks' está vacía en el JSON."
) # <-- MODIFIED: Include InstanceDB
# Escribir el archivo JSON # Escribir el archivo JSON
try: try:
with open(json_filepath, "w", encoding="utf-8") as f: with open(json_filepath, "w", encoding="utf-8") as f:
json.dump(result, f, indent=4, ensure_ascii=False) json.dump(result, f, indent=4, ensure_ascii=False)
print("Paso 6: Escritura JSON completada.") print("Paso 6: Escritura JSON completada.")
print(f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'") print(
return True # Indicar éxito f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'"
except IOError as e: print(f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}"); return False )
except TypeError as e: print(f"Error Crítico: Problema al serializar a JSON. Error: {e}"); return False return True # Indicar éxito
except IOError as e:
print(
f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}"
)
return False
except TypeError as e:
print(f"Error Crítico: Problema al serializar a JSON. Error: {e}")
return False
else: else:
# Esto no debería ocurrir si se manejaron todos los tipos o hubo error antes # Esto no debería ocurrir si se manejaron todos los tipos o hubo error antes
print("Error Crítico: No se generó ningún resultado para el archivo XML.") print("Error Crítico: No se generó ningún resultado para el archivo XML.")
return False return False
except etree_XMLSyntaxError as e: # Usar alias except etree_XMLSyntaxError as e: # Usar alias
print(f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}") print(
f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}"
)
return False return False
except Exception as e: except Exception as e:
print(f"Error Crítico: Error inesperado durante la conversión: {e}") print(f"Error Crítico: Error inesperado durante la conversión: {e}")
traceback.print_exc() traceback.print_exc()
return False return False
# --- Punto de Entrada Principal (__main__) --- # --- Punto de Entrada Principal (__main__) ---
if __name__ == "__main__": if __name__ == "__main__":
# Lógica para ejecución standalone # Lógica para ejecución standalone
@ -484,28 +660,29 @@ if __name__ == "__main__":
import tkinter as tk import tkinter as tk
from tkinter import filedialog from tkinter import filedialog
except ImportError: except ImportError:
print("Error: Tkinter no está instalado. No se puede mostrar el diálogo de archivo.", file=sys.stderr) print(
"Error: Tkinter no está instalado. No se puede mostrar el diálogo de archivo.",
file=sys.stderr,
)
# No salimos, podríamos intentar obtener el path de otra forma o fallar más adelante # No salimos, podríamos intentar obtener el path de otra forma o fallar más adelante
tk = None # Marcar como no disponible tk = None # Marcar como no disponible
xml_input_file = "" xml_input_file = ""
if tk: if tk:
root = tk.Tk() root = tk.Tk()
root.withdraw() # Ocultar la ventana principal de Tkinter root.withdraw() # Ocultar la ventana principal de Tkinter
print("Por favor, selecciona el archivo XML de entrada...") print("Por favor, selecciona el archivo XML de entrada...")
xml_input_file = filedialog.askopenfilename( xml_input_file = filedialog.askopenfilename(
title="Selecciona el archivo XML de entrada", title="Selecciona el archivo XML de entrada",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")] filetypes=[("XML files", "*.xml"), ("All files", "*.*")],
) )
root.destroy() # Cerrar Tkinter root.destroy() # Cerrar Tkinter
if not xml_input_file: if not xml_input_file:
print("No se seleccionó ningún archivo. Saliendo.", file=sys.stderr) print("No se seleccionó ningún archivo. Saliendo.", file=sys.stderr)
# sys.exit(1) # No usar sys.exit aquí # sys.exit(1) # No usar sys.exit aquí
else: else:
print( print(f"Archivo XML seleccionado: {xml_input_file}")
f"Archivo XML seleccionado: {xml_input_file}"
)
# Calcular ruta de salida JSON # Calcular ruta de salida JSON
xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0] xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0]
@ -524,4 +701,7 @@ if __name__ == "__main__":
if success: if success:
print("\nConversión completada exitosamente.") print("\nConversión completada exitosamente.")
else: else:
print(f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.", file=sys.stderr) print(
f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.",
file=sys.stderr,
)

16144
data/log.txt

File diff suppressed because it is too large Load Diff