Refactor code structure for improved readability and maintainability
This commit is contained in:
parent
18f6cdaa4f
commit
c3088e9957
|
@ -3,7 +3,10 @@
|
|||
from lxml import etree
|
||||
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 = {
|
||||
"iface": "http://www.siemens.com/automation/Openness/SW/Interface/v5",
|
||||
"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
|
||||
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
|
||||
XML aparecen nuevas versiones (p.ej. v6) de los URIs, esta función las
|
||||
detectará y sobreescribirá las entradas correspondientes en `ns`.
|
||||
|
@ -612,7 +626,18 @@ def adapt_namespaces(root):
|
|||
detected["iface"] = iface_uri
|
||||
|
||||
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)
|
||||
else:
|
||||
print("INFO: Usando namespaces por defecto (TIA Portal v18-v20)")
|
||||
|
||||
|
||||
# --- función auxiliar privada para adapt_namespaces ---
|
||||
|
|
|
@ -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.
|
||||
|
||||
**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:
|
||||
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
"""
|
||||
|
||||
# ToUpload/x1_to_json.py
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
@ -13,10 +14,13 @@ import sys
|
|||
import traceback
|
||||
import importlib
|
||||
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
|
||||
import copy
|
||||
import time # <-- NUEVO: Para obtener metadatos
|
||||
|
||||
script_root = os.path.dirname(
|
||||
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
|
||||
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:
|
||||
print(
|
||||
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."
|
||||
)
|
||||
parser_map[lang_upper] = parser_func
|
||||
#print(
|
||||
# print(
|
||||
# f" - Cargado parser para '{lang_upper}' desde {module_name_rel}.py"
|
||||
#)
|
||||
# )
|
||||
else:
|
||||
print(
|
||||
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)
|
||||
tree = etree.parse(xml_filepath, parser)
|
||||
root = tree.getroot()
|
||||
# Ajustar namespaces dinámicamente para soportar distintas versiones de TIA
|
||||
# Ajustar namespaces dinámicamente para soportar TIA Portal v15-v20
|
||||
try:
|
||||
adapt_namespaces(root)
|
||||
except Exception as e_ns:
|
||||
|
@ -272,10 +281,14 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
|
||||
# Intentar Tag Table si no es UDT
|
||||
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:
|
||||
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
|
||||
if result is None:
|
||||
|
@ -287,19 +300,33 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
|
||||
if block_list:
|
||||
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 = {
|
||||
"SW.Blocks.FC": "FC", "SW.Blocks.FB": "FB",
|
||||
"SW.Blocks.GlobalDB": "GlobalDB", "SW.Blocks.OB": "OB",
|
||||
"SW.Blocks.InstanceDB": "InstanceDB" # <-- ADDED: Recognize InstanceDB
|
||||
"SW.Blocks.FC": "FC",
|
||||
"SW.Blocks.FB": "FB",
|
||||
"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")
|
||||
print(f"Paso 2b: Bloque {block_tag_name} (Tipo: {block_type_found}) encontrado (ID={the_block.get('ID')}).")
|
||||
block_type_found = block_type_map.get(
|
||||
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) ---
|
||||
print("Paso 3: Extrayendo atributos del bloque...")
|
||||
attribute_list_node = the_block.xpath("./AttributeList") # Buscar hijo directo
|
||||
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown"
|
||||
attribute_list_node = the_block.xpath(
|
||||
"./AttributeList"
|
||||
) # Buscar hijo directo
|
||||
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 = ""
|
||||
|
@ -307,41 +334,70 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
if attribute_list_node:
|
||||
attr_list = attribute_list_node[0]
|
||||
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()")
|
||||
try: block_number_val = int(num_node[0]) if num_node else None
|
||||
except (ValueError, TypeError): block_number_val = None
|
||||
try:
|
||||
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()")
|
||||
# Asignar lenguaje por defecto si no se encuentra
|
||||
block_lang_val = 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
|
||||
block_lang_val = (
|
||||
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 -->
|
||||
if block_type_found == "InstanceDB":
|
||||
inst_name_node = attr_list.xpath("./InstanceOfName/text()")
|
||||
instance_of_name_val = inst_name_node[0].strip() if inst_name_node else None
|
||||
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}'")
|
||||
instance_of_name_val = (
|
||||
inst_name_node[0].strip() if inst_name_node else None
|
||||
)
|
||||
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)
|
||||
comment_node_attr = attr_list.xpath("./Comment")
|
||||
if comment_node_attr:
|
||||
block_comment_val = get_multilingual_text(comment_node_attr[0])
|
||||
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:
|
||||
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]}...'")
|
||||
else:
|
||||
print(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
|
||||
print(
|
||||
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
|
||||
result = {
|
||||
"block_name": block_name_val, "block_number": block_number_val,
|
||||
"language": block_lang_val, "block_type": block_type_found,
|
||||
"block_name": block_name_val,
|
||||
"block_number": block_number_val,
|
||||
"language": block_lang_val,
|
||||
"block_type": block_type_found,
|
||||
"block_comment": block_comment_val,
|
||||
"interface": {}, "networks": []
|
||||
"interface": {},
|
||||
"networks": [],
|
||||
}
|
||||
|
||||
# --- Extraer Interfaz del Bloque ---
|
||||
|
@ -349,44 +405,81 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
interface_node = None
|
||||
if attribute_list_node:
|
||||
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:
|
||||
# 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:
|
||||
processed_sections = set()
|
||||
for section in all_sections:
|
||||
section_name = section.get("Name")
|
||||
if not section_name or section_name in processed_sections: continue
|
||||
members_in_section = section.xpath("./iface:Member", namespaces=ns)
|
||||
if not section_name or section_name in processed_sections:
|
||||
continue
|
||||
members_in_section = section.xpath(
|
||||
"./iface:Member", namespaces=ns
|
||||
)
|
||||
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)
|
||||
else: print("Advertencia: Nodo Interface no contiene secciones <iface:Section>.")
|
||||
if not result["interface"]: print("Advertencia: Interface encontrada pero sin secciones procesables.")
|
||||
else:
|
||||
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":
|
||||
# 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:
|
||||
print("Paso 4: Encontrada sección Static para GlobalDB (sin nodo Interface explícito).")
|
||||
result["interface"]["Static"] = parse_interface_members(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.")
|
||||
|
||||
print(
|
||||
"Paso 4: Encontrada sección Static para GlobalDB (sin nodo Interface explícito)."
|
||||
)
|
||||
result["interface"]["Static"] = parse_interface_members(
|
||||
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) ---
|
||||
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)...")
|
||||
networks_processed_count = 0
|
||||
result["networks"] = [] # Asegurar que esté inicializado
|
||||
object_list_node = the_block.xpath("./ObjectList")
|
||||
|
||||
if object_list_node:
|
||||
compile_units = object_list_node[0].xpath("./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>
|
||||
compile_units = object_list_node[0].xpath(
|
||||
"./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
|
||||
network_id = network_elem.get("ID")
|
||||
network_lang = "Unknown"
|
||||
|
@ -398,11 +491,17 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
# Determinar lenguaje
|
||||
net_attr_list = network_elem.xpath("./AttributeList")
|
||||
if net_attr_list:
|
||||
lang_node = net_attr_list[0].xpath("./ProgrammingLanguage/text()")
|
||||
if lang_node: network_lang = lang_node[0].strip()
|
||||
elif result["language"] != "Unknown": network_lang = result["language"]
|
||||
lang_node = net_attr_list[0].xpath(
|
||||
"./ProgrammingLanguage/text()"
|
||||
)
|
||||
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())
|
||||
parsed_network_data = None
|
||||
|
||||
|
@ -410,73 +509,150 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
|||
try:
|
||||
parsed_network_data = parser_func(network_elem)
|
||||
except Exception as e_parse:
|
||||
print(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}"}
|
||||
print(
|
||||
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:
|
||||
print(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}"}
|
||||
print(
|
||||
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ó ---
|
||||
if 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"}
|
||||
if (
|
||||
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
|
||||
try:
|
||||
title_element = network_elem.xpath("./ObjectList/MultilingualText[@CompositionName='Title']")
|
||||
parsed_network_data["title"] = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}"
|
||||
title_element = network_elem.xpath(
|
||||
"./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']")
|
||||
parsed_network_data["comment"] = get_multilingual_text(comment_elem_net[0]) if comment_elem_net else ""
|
||||
comment_elem_net = network_elem.xpath(
|
||||
"./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:
|
||||
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
|
||||
parsed_network_data["title"] = f"Network {network_id}" # Asegurar que title exista
|
||||
parsed_network_data["comment"] = f"// Error extrayendo comentario: {e_comment}" # Asegurar que comment exista
|
||||
parsed_network_data["title"] = (
|
||||
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.")
|
||||
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
|
||||
if networks_processed_count == 0:
|
||||
print(
|
||||
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.*
|
||||
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 ---
|
||||
|
||||
# --- Escritura del JSON Final ---
|
||||
if result:
|
||||
# Añadir metadatos XML al diccionario final
|
||||
if xml_mod_time is not None: result["source_xml_mod_time"] = xml_mod_time
|
||||
if xml_size is not None: result["source_xml_size"] = xml_size
|
||||
if xml_mod_time is not None:
|
||||
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...")
|
||||
# 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 ["PlcUDT", "PlcTagTable", "GlobalDB", "InstanceDB"] and not result.get("networks"): print("ADVERTENCIA FINAL: 'networks' está vacía en el JSON.") # <-- MODIFIED: Include InstanceDB
|
||||
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 [
|
||||
"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
|
||||
try:
|
||||
with open(json_filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, indent=4, ensure_ascii=False)
|
||||
print("Paso 6: Escritura JSON completada.")
|
||||
print(f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'")
|
||||
print(
|
||||
f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'"
|
||||
)
|
||||
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
|
||||
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:
|
||||
# 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.")
|
||||
return False
|
||||
|
||||
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
|
||||
except Exception as e:
|
||||
print(f"Error Crítico: Error inesperado durante la conversión: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
# --- Punto de Entrada Principal (__main__) ---
|
||||
if __name__ == "__main__":
|
||||
# Lógica para ejecución standalone
|
||||
|
@ -484,7 +660,10 @@ if __name__ == "__main__":
|
|||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
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
|
||||
tk = None # Marcar como no disponible
|
||||
|
||||
|
@ -495,7 +674,7 @@ if __name__ == "__main__":
|
|||
print("Por favor, selecciona el archivo XML de entrada...")
|
||||
xml_input_file = filedialog.askopenfilename(
|
||||
title="Selecciona el archivo XML de entrada",
|
||||
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
|
||||
filetypes=[("XML files", "*.xml"), ("All files", "*.*")],
|
||||
)
|
||||
root.destroy() # Cerrar Tkinter
|
||||
|
||||
|
@ -503,9 +682,7 @@ if __name__ == "__main__":
|
|||
print("No se seleccionó ningún archivo. Saliendo.", file=sys.stderr)
|
||||
# sys.exit(1) # No usar sys.exit aquí
|
||||
else:
|
||||
print(
|
||||
f"Archivo XML seleccionado: {xml_input_file}"
|
||||
)
|
||||
print(f"Archivo XML seleccionado: {xml_input_file}")
|
||||
|
||||
# Calcular ruta de salida JSON
|
||||
xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0]
|
||||
|
@ -524,4 +701,7 @@ if __name__ == "__main__":
|
|||
if success:
|
||||
print("\nConversión completada exitosamente.")
|
||||
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
16144
data/log.txt
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue