Varsion Base Utilizable

This commit is contained in:
Miguel 2025-04-18 12:18:49 +02:00
parent 099553f4b4
commit 1b727fa4bb
2 changed files with 178 additions and 291 deletions

View File

@ -2,6 +2,7 @@
"block_name": "BlenderRun_ProdTime", "block_name": "BlenderRun_ProdTime",
"block_number": 2040, "block_number": 2040,
"language": "LAD", "language": "LAD",
"block_comment": "",
"interface": { "interface": {
"Temp": [ "Temp": [
{ {
@ -48,6 +49,7 @@
{ {
"id": "9", "id": "9",
"title": "Seconds", "title": "Seconds",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "26", "instruction_uid": "26",
@ -124,6 +126,7 @@
{ {
"id": "1A", "id": "1A",
"title": "Reset Hours", "title": "Reset Hours",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "24", "instruction_uid": "24",
@ -175,6 +178,7 @@
{ {
"id": "2B", "id": "2B",
"title": "Seconds Counter", "title": "Seconds Counter",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "26", "instruction_uid": "26",
@ -251,6 +255,7 @@
{ {
"id": "3C", "id": "3C",
"title": "Minute", "title": "Minute",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "24", "instruction_uid": "24",
@ -299,6 +304,7 @@
{ {
"id": "4D", "id": "4D",
"title": "Minute Counter", "title": "Minute Counter",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "27", "instruction_uid": "27",
@ -379,6 +385,7 @@
{ {
"id": "5E", "id": "5E",
"title": "Hour", "title": "Hour",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "24", "instruction_uid": "24",
@ -427,6 +434,7 @@
{ {
"id": "6F", "id": "6F",
"title": "Hour Counter", "title": "Hour Counter",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "30", "instruction_uid": "30",
@ -536,6 +544,7 @@
{ {
"id": "80", "id": "80",
"title": "Counter reset", "title": "Counter reset",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "29", "instruction_uid": "29",
@ -665,6 +674,7 @@
{ {
"id": "91", "id": "91",
"title": "Running Seconds", "title": "Running Seconds",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "26", "instruction_uid": "26",
@ -741,6 +751,7 @@
{ {
"id": "A2", "id": "A2",
"title": "Running Minutes", "title": "Running Minutes",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "35", "instruction_uid": "35",
@ -800,7 +811,15 @@
"name": "\"MOD60\"" "name": "\"MOD60\""
} }
] ]
},
"eno_logic": [
{
"target_pin": "pre",
"target_type": "instruction",
"target_uid": "37",
"target_name": "Eq"
} }
]
}, },
{ {
"instruction_uid": "37", "instruction_uid": "37",
@ -938,6 +957,7 @@
{ {
"id": "B3", "id": "B3",
"title": "Running Hours for Maintenance", "title": "Running Hours for Maintenance",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "32", "instruction_uid": "32",
@ -1016,7 +1036,15 @@
"name": "\"MOD60\"" "name": "\"MOD60\""
} }
] ]
},
"eno_logic": [
{
"target_pin": "pre",
"target_type": "instruction",
"target_uid": "35",
"target_name": "Eq"
} }
]
}, },
{ {
"instruction_uid": "35", "instruction_uid": "35",
@ -1084,6 +1112,7 @@
{ {
"id": "C4", "id": "C4",
"title": "Running Hours for Maintenance", "title": "Running Hours for Maintenance",
"comment": "",
"logic": [ "logic": [
{ {
"instruction_uid": "23", "instruction_uid": "23",

View File

@ -2,27 +2,22 @@
import json import json
import os import os
from lxml import etree from lxml import etree
import traceback # Para obtener detalles de excepciones import traceback
# --- Namespaces --- # --- Namespaces ---
# Namespaces usados comúnmente en los archivos XML de TIA Portal Openness
ns = { ns = {
# Namespace principal para elementos de la interfaz del bloque (Input, Output, Temp, etc.)
'iface': 'http://www.siemens.com/automation/Openness/SW/Interface/v5', 'iface': 'http://www.siemens.com/automation/Openness/SW/Interface/v5',
# Namespace para elementos dentro de la lógica de red LAD/FBD (FlgNet)
'flg': 'http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4' 'flg': 'http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4'
# Podrían añadirse otros si fueran necesarios (p.ej., para SCL, DBs)
} }
# --- Helper Functions --- # --- Helper Functions ---
# (get_multilingual_text, get_symbol_name, parse_access, parse_part sin cambios)
def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'): def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'):
""" """
Intenta extraer texto de un elemento MultilingualText, priorizando idiomas. Intenta extraer texto de un elemento MultilingualText, priorizando idiomas.
Busca directamente los Text dentro de Items/AttributeList/Text bajo las Culture especificadas. Busca directamente los Text dentro de Items/AttributeList/Text bajo las Culture especificadas.
""" """
if element is None: if element is None:
# print("DEBUG: get_multilingual_text llamado con element=None")
return "" return ""
try: try:
# Intenta encontrar el idioma por defecto # Intenta encontrar el idioma por defecto
@ -30,7 +25,6 @@ def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'):
f"/*[local-name()='AttributeList']/*[local-name()='Text']" f"/*[local-name()='AttributeList']/*[local-name()='Text']"
text_items = element.xpath(xpath_expr) text_items = element.xpath(xpath_expr)
if text_items and text_items[0].text is not None: if text_items and text_items[0].text is not None:
# print(f"DEBUG: Texto encontrado para {default_lang}: {text_items[0].text.strip()}")
return text_items[0].text.strip() return text_items[0].text.strip()
# Si no, intenta encontrar el idioma de fallback # Si no, intenta encontrar el idioma de fallback
@ -38,17 +32,14 @@ def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'):
f"/*[local-name()='AttributeList']/*[local-name()='Text']" f"/*[local-name()='AttributeList']/*[local-name()='Text']"
text_items = element.xpath(xpath_expr) text_items = element.xpath(xpath_expr)
if text_items and text_items[0].text is not None: if text_items and text_items[0].text is not None:
# print(f"DEBUG: Texto encontrado para {fallback_lang}: {text_items[0].text.strip()}")
return text_items[0].text.strip() return text_items[0].text.strip()
# Si no, toma el primer texto que encuentre # Si no, toma el primer texto que encuentre
xpath_expr = f".//*[local-name()='MultilingualTextItem']/*[local-name()='AttributeList']/*[local-name()='Text']" xpath_expr = f".//*[local-name()='MultilingualTextItem']/*[local-name()='AttributeList']/*[local-name()='Text']"
text_items = element.xpath(xpath_expr) text_items = element.xpath(xpath_expr)
if text_items and text_items[0].text is not None: if text_items and text_items[0].text is not None:
# print(f"DEBUG: Texto encontrado (primer disponible): {text_items[0].text.strip()}")
return text_items[0].text.strip() return text_items[0].text.strip()
# print("DEBUG: No se encontró ningún texto en MultilingualTextItem")
return "" # Devuelve cadena vacía si no se encuentra nada return "" # Devuelve cadena vacía si no se encuentra nada
except Exception as e: except Exception as e:
print(f"Advertencia: Error extrayendo MultilingualText desde {etree.tostring(element, encoding='unicode')[:100]}...: {e}") print(f"Advertencia: Error extrayendo MultilingualText desde {etree.tostring(element, encoding='unicode')[:100]}...: {e}")
@ -60,7 +51,6 @@ def get_symbol_name(symbol_element):
Encapsula cada componente entre comillas dobles y los une con puntos. Encapsula cada componente entre comillas dobles y los une con puntos.
""" """
if symbol_element is None: if symbol_element is None:
# print("DEBUG: get_symbol_name llamado con symbol_element=None")
return None return None
try: try:
# Selecciona el atributo 'Name' de cada elemento 'Component' hijo directo del Symbol # Selecciona el atributo 'Name' de cada elemento 'Component' hijo directo del Symbol
@ -68,10 +58,8 @@ def get_symbol_name(symbol_element):
if components: if components:
# Une los componentes, asegurándose de que cada uno esté entre comillas dobles # Une los componentes, asegurándose de que cada uno esté entre comillas dobles
full_name = ".".join(f'"{c}"' for c in components) full_name = ".".join(f'"{c}"' for c in components)
# print(f"DEBUG: Nombre de símbolo construido: {full_name}")
return full_name return full_name
else: else:
# print(f"Advertencia: No se encontraron 'Component' en Symbol: {etree.tostring(symbol_element, encoding='unicode')}")
return None # Indica que no se pudo formar un nombre return None # Indica que no se pudo formar un nombre
except Exception as e: except Exception as e:
print(f"Advertencia: Excepción en get_symbol_name para {etree.tostring(symbol_element, encoding='unicode')[:100]}...: {e}") print(f"Advertencia: Excepción en get_symbol_name para {etree.tostring(symbol_element, encoding='unicode')[:100]}...: {e}")
@ -84,67 +72,48 @@ def parse_access(access_element):
Devuelve un diccionario con la información o None si hay un error crítico. Devuelve un diccionario con la información o None si hay un error crítico.
""" """
if access_element is None: if access_element is None:
# print("DEBUG: parse_access llamado con access_element=None")
return None return None
uid = access_element.get('UId') uid = access_element.get('UId')
scope = access_element.get('Scope') scope = access_element.get('Scope')
# print(f"DEBUG: Parseando Access UID={uid}, Scope={scope}")
info = {'uid': uid, 'scope': scope, 'type': 'unknown'} # Inicializa info info = {'uid': uid, 'scope': scope, 'type': 'unknown'} # Inicializa info
# Intenta encontrar un elemento Symbol (indica una variable)
symbol = access_element.xpath("./*[local-name()='Symbol']") symbol = access_element.xpath("./*[local-name()='Symbol']")
# Intenta encontrar un elemento Constant (indica un valor literal)
constant = access_element.xpath("./*[local-name()='Constant']") constant = access_element.xpath("./*[local-name()='Constant']")
if symbol: if symbol:
info['type'] = 'variable' info['type'] = 'variable'
info['name'] = get_symbol_name(symbol[0]) info['name'] = get_symbol_name(symbol[0])
if info['name'] is None: if info['name'] is None:
# Si get_symbol_name falló, marca como error y reporta
info['type'] = 'error_parsing_symbol' info['type'] = 'error_parsing_symbol'
print(f"Error: No se pudo parsear el nombre del símbolo para Access UID={uid}") print(f"Error: No se pudo parsear el nombre del símbolo para Access UID={uid}")
return info # Devolver la info con el error marcado return info
# print(f"DEBUG: Access UID={uid} es Variable: {info['name']}")
elif constant: elif constant:
info['type'] = 'constant' info['type'] = 'constant'
# Extrae el tipo de dato de la constante
const_type_elem = constant[0].xpath("./*[local-name()='ConstantType']") const_type_elem = constant[0].xpath("./*[local-name()='ConstantType']")
const_val_elem = constant[0].xpath("./*[local-name()='ConstantValue']") const_val_elem = constant[0].xpath("./*[local-name()='ConstantValue']")
# Obtiene el texto del tipo de dato, o 'Unknown' si no está presente
info['datatype'] = const_type_elem[0].text if const_type_elem and const_type_elem[0].text is not None else 'Unknown' info['datatype'] = const_type_elem[0].text if const_type_elem and const_type_elem[0].text is not None else 'Unknown'
# Obtiene el texto del valor, o None si no está presente
value_str = const_val_elem[0].text if const_val_elem and const_val_elem[0].text is not None else None value_str = const_val_elem[0].text if const_val_elem and const_val_elem[0].text is not None else None
if value_str is None: if value_str is None:
# Si no hay valor, marca como error y reporta
info['type'] = 'error_parsing_constant' info['type'] = 'error_parsing_constant'
info['value'] = None info['value'] = None
print(f"Error: Constante sin valor encontrada para Access UID={uid}") print(f"Error: Constante sin valor encontrada para Access UID={uid}")
return info # Devolver la info con el error marcado return info
# Intenta inferir el tipo si es 'Unknown'
if info['datatype'] == 'Unknown': if info['datatype'] == 'Unknown':
val_lower = value_str.lower() val_lower = value_str.lower()
if val_lower in ['true', 'false']: info['datatype'] = 'Bool' if val_lower in ['true', 'false']: info['datatype'] = 'Bool'
elif value_str.isdigit() or (value_str.startswith('-') and value_str[1:].isdigit()): info['datatype'] = 'Int' # Asume Int base elif value_str.isdigit() or (value_str.startswith('-') and value_str[1:].isdigit()): info['datatype'] = 'Int'
elif '.' in value_str: elif '.' in value_str:
try: try: float(value_str); info['datatype'] = 'Real'
float(value_str) except ValueError: pass
info['datatype'] = 'Real' # Asume Real base elif '#' in value_str: info['datatype'] = 'TypedConstant'
except ValueError: pass # Sigue siendo Unknown si no parece número real
elif '#' in value_str:
info['datatype'] = 'TypedConstant' # Ej: DINT#60
# print(f"DEBUG: Tipo de constante inferido para UID={uid}: {info['datatype']} (desde '{value_str}')")
info['value'] = value_str
# Intenta convertir el valor a un tipo Python nativo
info['value'] = value_str # Valor por defecto es el string original
dtype_lower = info['datatype'].lower() dtype_lower = info['datatype'].lower()
# Procesa el valor, quitando prefijos tipo 'DINT#' si existen
val_str_processed = value_str.split('#')[-1] if '#' in value_str else value_str val_str_processed = value_str.split('#')[-1] if '#' in value_str else value_str
try: try:
if dtype_lower in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword', 'byte']: if dtype_lower in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword', 'byte']:
@ -153,32 +122,23 @@ def parse_access(access_element):
info['value'] = val_str_processed.lower() == 'true' or val_str_processed == '1' info['value'] = val_str_processed.lower() == 'true' or val_str_processed == '1'
elif dtype_lower in ['real', 'lreal']: elif dtype_lower in ['real', 'lreal']:
info['value'] = float(val_str_processed) info['value'] = float(val_str_processed)
# Para TypedConstant u otros, mantenemos el string original como valor, pero registramos el tipo
elif dtype_lower == 'typedconstant': elif dtype_lower == 'typedconstant':
# Podríamos intentar extraer el tipo y valor aquí si fuera necesario info['value'] = value_str
info['value'] = value_str # Mantiene el string original
# Nota: Los tipos Time, Date, String, etc., se mantendrán como strings
except (ValueError, TypeError) as e: except (ValueError, TypeError) as e:
# Si la conversión falla, mantenemos el string original y reportamos
print(f"Advertencia: No se pudo convertir el valor '{val_str_processed}' a tipo {dtype_lower} para UID={uid}. Se mantiene como string. Error: {e}") print(f"Advertencia: No se pudo convertir el valor '{val_str_processed}' a tipo {dtype_lower} para UID={uid}. Se mantiene como string. Error: {e}")
info['value'] = value_str # Asegura que se mantenga el string original si falla la conversión info['value'] = value_str
# print(f"DEBUG: Access UID={uid} es Constante: Tipo={info['datatype']}, Valor={info['value']}")
else: else:
# Si no es ni Symbol ni Constant, es una estructura desconocida
info['type'] = 'unknown_structure' info['type'] = 'unknown_structure'
print(f"Advertencia: Access UID={uid} no es ni Symbol ni Constant.") print(f"Advertencia: Access UID={uid} no es ni Symbol ni Constant.")
return info # Devolver la info con el tipo 'unknown_structure'
# Verificación final: si es variable, debe tener nombre
if info['type'] == 'variable' and info.get('name') is None:
# Esto no debería ocurrir si get_symbol_name funciona, pero es una salvaguarda
print(f"Error Interno: parse_access terminó con tipo 'variable' pero sin nombre para UID {uid}.")
info['type'] = 'error_no_name' # Marca un estado de error específico
return info return info
return info # Devuelve el diccionario con la información parseada if info['type'] == 'variable' and info.get('name') is None:
print(f"Error Interno: parse_access terminó con tipo 'variable' pero sin nombre para UID {uid}.")
info['type'] = 'error_no_name'
return info
return info
def parse_part(part_element): def parse_part(part_element):
""" """
@ -187,34 +147,29 @@ def parse_part(part_element):
Devuelve un diccionario con la información o None si el elemento es inválido. Devuelve un diccionario con la información o None si el elemento es inválido.
""" """
if part_element is None: if part_element is None:
# print("DEBUG: parse_part llamado con part_element=None")
return None return None
uid = part_element.get('UId') uid = part_element.get('UId')
name = part_element.get('Name') name = part_element.get('Name')
# print(f"DEBUG: Parseando Part UID={uid}, Name={name}")
if not uid or not name: if not uid or not name:
print(f"Error: Part encontrado sin UID o Name: {etree.tostring(part_element, encoding='unicode')}") print(f"Error: Part encontrado sin UID o Name: {etree.tostring(part_element, encoding='unicode')}")
return None # Ignora partes inválidas return None
# Extrae los TemplateValue si existen (información adicional sobre la instrucción)
template_values = {} template_values = {}
try: try:
# Selecciona los atributos Name y Type de cada TemplateValue hijo
for tv in part_element.xpath("./*[local-name()='TemplateValue']"): for tv in part_element.xpath("./*[local-name()='TemplateValue']"):
tv_name = tv.get('Name') tv_name = tv.get('Name')
tv_type = tv.get('Type') tv_type = tv.get('Type')
if tv_name and tv_type: if tv_name and tv_type:
template_values[tv_name] = tv_type template_values[tv_name] = tv_type
# print(f"DEBUG: TemplateValues para Part UID={uid}: {template_values}")
except Exception as e: except Exception as e:
print(f"Advertencia: Error extrayendo TemplateValues para Part UID={uid}: {e}") print(f"Advertencia: Error extrayendo TemplateValues para Part UID={uid}: {e}")
return { return {
'uid': uid, 'uid': uid,
'name': name, 'name': name,
'template_values': template_values 'template_values': template_values # Mantenemos esto por si acaso, aunque no se use prominentemente
} }
# --- Main Parsing Logic --- # --- Main Parsing Logic ---
@ -222,382 +177,285 @@ def parse_part(part_element):
def parse_network(network_element): def parse_network(network_element):
""" """
Parsea un elemento SW.Blocks.CompileUnit (representa una red de lógica) Parsea un elemento SW.Blocks.CompileUnit (representa una red de lógica)
y extrae su ID, título y la lógica interna simplificada en formato JSON. y extrae su ID, título, comentario y la lógica interna simplificada en formato JSON,
incluyendo la lógica conectada a ENO si no es un simple EN->ENO.
""" """
if network_element is None: if network_element is None:
print("Error: parse_network llamado con network_element=None") print("Error: parse_network llamado con network_element=None")
return {'id': 'ERROR', 'title': 'Invalid Network Element', 'logic': [], 'error': 'Input element was None'} return {'id': 'ERROR', 'title': 'Invalid Network Element', 'comment': '', 'logic': [], 'error': 'Input element was None'}
network_id = network_element.get('ID') network_id = network_element.get('ID')
# print(f"--- Parseando Red ID={network_id} ---")
# Extrae el título de la red usando la función auxiliar # Extrae el título de la red
title_element = network_element.xpath(".//*[local-name()='MultilingualText'][@CompositionName='Title']") title_element = network_element.xpath(".//*[local-name()='MultilingualText'][@CompositionName='Title']")
network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}"
# print(f"DEBUG: Título de la Red: {network_title}")
# Encuentra el contenedor FlgNet que tiene la lógica LAD/FBD # *** NUEVO: Extrae el comentario de la red ***
# Usa '//' para buscar en cualquier nivel descendiente y el namespace 'flg' network_comment = ""
comment_title_element = network_element.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']")
if comment_title_element:
network_comment = get_multilingual_text(comment_title_element[0])
# print(f"DEBUG: Comentario Red {network_id}: '{network_comment[:50]}...'")
flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns) flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns)
if not flgnet_list: if not flgnet_list:
print(f"Error: No se encontró FlgNet en la red ID={network_id}") print(f"Error: No se encontró FlgNet en la red ID={network_id}")
return {'id': network_id, 'title': network_title, 'logic': [], 'error': 'FlgNet not found'} return {'id': network_id, 'title': network_title, 'comment': network_comment, 'logic': [], 'error': 'FlgNet not found'}
flgnet = flgnet_list[0] # Toma el primer FlgNet encontrado flgnet = flgnet_list[0]
# 1. Parsea todos los Access (operandos) y Parts (instrucciones) dentro de FlgNet # 1. Parsear Access y Parts
access_map = {} access_map = {}
for acc in flgnet.xpath(".//flg:Access", namespaces=ns): for acc in flgnet.xpath(".//flg:Access", namespaces=ns):
acc_info = parse_access(acc) acc_info = parse_access(acc)
if acc_info and 'uid' in acc_info: if acc_info and 'uid' in acc_info:
access_map[acc_info['uid']] = acc_info access_map[acc_info['uid']] = acc_info
else:
print(f"Advertencia: Se ignoró un Access inválido en la red {network_id}")
parts_map = {} parts_map = {}
for part in flgnet.xpath(".//flg:Part", namespaces=ns): for part in flgnet.xpath(".//flg:Part", namespaces=ns):
part_info = parse_part(part) part_info = parse_part(part)
if part_info and 'uid' in part_info: if part_info and 'uid' in part_info:
parts_map[part_info['uid']] = part_info parts_map[part_info['uid']] = part_info
else:
print(f"Advertencia: Se ignoró un Part inválido en la red {network_id}")
# print(f"DEBUG: Red {network_id}: {len(access_map)} Access encontrados, {len(parts_map)} Parts encontrados.") # 2. Parsear Wires y construir mapa de conexiones de entrada y *salida ENO*
wire_connections = {} # Clave=(dest_uid, dest_pin), Valor=lista de (source_uid, source_pin)
# 2. Parsea todas las conexiones (Wires) para entender el flujo eno_outputs = {} # Clave=source_part_uid, Valor=lista de (dest_uid, dest_pin)
wire_connections = {} # Diccionario: clave=(dest_uid, dest_pin), valor=lista de (source_uid, source_pin) flg_ns_uri = ns['flg']
flg_ns_uri = ns['flg'] # Obtiene la URI del namespace 'flg' para comparaciones de tags
for wire in flgnet.xpath(".//flg:Wire", namespaces=ns): for wire in flgnet.xpath(".//flg:Wire", namespaces=ns):
# print(f"DEBUG: Procesando Wire: {etree.tostring(wire, encoding='unicode').strip()}")
source_uid, source_pin, dest_uid, dest_pin = None, None, None, None source_uid, source_pin, dest_uid, dest_pin = None, None, None, None
children = wire.getchildren() # Obtiene los hijos directos del Wire (conexiones) children = wire.getchildren()
if len(children) < 2: continue
if len(children) < 2:
# print(f"Advertencia: Wire con menos de 2 hijos ignorado en red {network_id}: {etree.tostring(wire, encoding='unicode')}")
continue # Un wire necesita al menos un origen y un destino
source_elem, dest_elem = children[0], children[1] source_elem, dest_elem = children[0], children[1]
# Determina la fuente de la conexión # Determina la fuente
# Utiliza etree.QName para comparar tags incluyendo el namespace if source_elem.tag == etree.QName(flg_ns_uri, 'Powerrail'): source_uid, source_pin = 'POWERRAIL', 'out'
if source_elem.tag == etree.QName(flg_ns_uri, 'Powerrail'): elif source_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): source_uid, source_pin = source_elem.get('UId'), 'value'
source_uid, source_pin = 'POWERRAIL', 'out' # Fuente es la barra de potencia elif source_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): source_uid, source_pin = source_elem.get('UId'), source_elem.get('Name')
elif source_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'):
# Conexión a un operando (Access)
source_uid, source_pin = source_elem.get('UId'), 'value' # Usamos 'value' como pin genérico para Access
elif source_elem.tag == etree.QName(flg_ns_uri, 'NameCon'):
# Conexión desde un pin específico de una instrucción (Part)
source_uid, source_pin = source_elem.get('UId'), source_elem.get('Name')
else:
# print(f"Advertencia: Tipo de fuente de Wire desconocido '{source_elem.tag}' en red {network_id}")
pass # Ignora fuentes desconocidas por ahora
# Determina el destino de la conexión # Determina el destino
if dest_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): if dest_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): dest_uid, dest_pin = dest_elem.get('UId'), 'value'
# Conexión a un operando (Access) - Generalmente una asignación de salida elif dest_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): dest_uid, dest_pin = dest_elem.get('UId'), dest_elem.get('Name')
dest_uid, dest_pin = dest_elem.get('UId'), 'value' # Usamos 'value' como pin genérico
elif dest_elem.tag == etree.QName(flg_ns_uri, 'NameCon'):
# Conexión a un pin específico de una instrucción (Part) - Generalmente una entrada
dest_uid, dest_pin = dest_elem.get('UId'), dest_elem.get('Name')
else:
# print(f"Advertencia: Tipo de destino de Wire desconocido '{dest_elem.tag}' en red {network_id}")
pass # Ignora destinos desconocidos
# Si tenemos un destino válido y una fuente válida, registra la conexión # Registrar conexión de entrada normal
if dest_uid and dest_pin and source_uid is not None: if dest_uid and dest_pin and source_uid is not None:
dest_key = (dest_uid, dest_pin) dest_key = (dest_uid, dest_pin)
source_info = (source_uid, source_pin) source_info = (source_uid, source_pin)
# print(f"DEBUG: Wire Conexión: De ({source_uid}, {source_pin}) a ({dest_uid}, {dest_pin})") if dest_key not in wire_connections: wire_connections[dest_key] = []
if source_info not in wire_connections[dest_key]: wire_connections[dest_key].append(source_info)
# Añade la fuente a la lista de fuentes para ese pin de destino # *** NUEVO: Registrar conexiones que SALEN de un pin ENO ***
if dest_key not in wire_connections: if source_pin == 'eno' and source_uid in parts_map:
wire_connections[dest_key] = [] if source_uid not in eno_outputs: eno_outputs[source_uid] = []
if source_info not in wire_connections[dest_key]: # Evita duplicados si el XML fuera redundante eno_dest_info = (dest_uid, dest_pin)
wire_connections[dest_key].append(source_info) if eno_dest_info not in eno_outputs[source_uid]:
eno_outputs[source_uid].append(eno_dest_info)
# print(f"DEBUG: Red {network_id} - ENO de {source_uid} conectado a ({dest_uid}, {dest_pin})")
# print(f"DEBUG: Red {network_id}: {len(wire_connections)} Conexiones de destino procesadas.")
# 3. Construye la representación lógica de cada instrucción (Part) # 3. Construir la representación lógica principal
all_logic_steps = {} # Diccionario para almacenar la representación JSON de cada instrucción all_logic_steps = {}
for part_uid, part_info in parts_map.items(): for part_uid, part_info in parts_map.items():
instruction_repr = { instruction_repr = {'instruction_uid': part_uid, 'type': part_info['name'], 'inputs': {}, 'outputs': {}}
'instruction_uid': part_uid,
'type': part_info['name'],
'inputs': {}, # Pines de entrada de la instrucción
'outputs': {} # Pines de salida de la instrucción
}
# Busca todas las conexiones que *llegan* a esta instrucción (Part) # Procesar Entradas (igual que antes)
for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items():
if conn_dest_uid == part_uid: # Si el destino es esta instrucción if conn_dest_uid == part_uid:
# print(f"DEBUG: Part UID={part_uid}, Pin Entrada='{conn_dest_pin}', Fuentes={sources_list}") input_sources_repr = []
input_sources_repr = [] # Lista para representar las fuentes de este pin
for source_uid, source_pin in sources_list: for source_uid, source_pin in sources_list:
if source_uid == 'POWERRAIL': if source_uid == 'POWERRAIL': input_sources_repr.append({'type': 'powerrail'})
input_sources_repr.append({'type': 'powerrail'}) elif source_uid in access_map: input_sources_repr.append(access_map[source_uid])
elif source_uid in access_map:
# La fuente es una variable o constante
input_sources_repr.append(access_map[source_uid])
elif source_uid in parts_map: elif source_uid in parts_map:
# La fuente es la salida de otra instrucción input_sources_repr.append({'type': 'connection', 'source_instruction_uid': source_uid,
input_sources_repr.append({ 'source_instruction_type': parts_map[source_uid]['name'], 'source_pin': source_pin})
'type': 'connection', else: input_sources_repr.append({'type': 'unknown_source', 'uid': source_uid})
'source_instruction_uid': source_uid,
'source_instruction_type': parts_map[source_uid]['name'],
'source_pin': source_pin
})
else:
# Fuente desconocida (error o caso no manejado)
input_sources_repr.append({'type': 'unknown_source', 'uid': source_uid})
print(f"Advertencia: Fuente desconocida UID={source_uid} encontrada como entrada para Part UID={part_uid}, Pin={conn_dest_pin}")
# Asigna la representación de las fuentes al pin de entrada correspondiente if len(input_sources_repr) == 1: instruction_repr['inputs'][conn_dest_pin] = input_sources_repr[0]
# Si solo hay una fuente, la asigna directamente. elif len(input_sources_repr) > 1: instruction_repr['inputs'][conn_dest_pin] = input_sources_repr
# Si hay múltiples fuentes (caso de ramas OR en LAD), las agrupa.
# NOTA: Esta lógica de OR_Branch es una simplificación y podría no cubrir todos los casos complejos.
# Asumimos que múltiples wires llegando al mismo pin de entrada implican un OR.
if len(input_sources_repr) == 1:
instruction_repr['inputs'][conn_dest_pin] = input_sources_repr[0]
elif len(input_sources_repr) > 1:
# Si hay varias fuentes para el mismo pin de entrada, las marcamos
# print(f"DEBUG: Múltiples fuentes para Part UID={part_uid}, Pin={conn_dest_pin}. Asumiendo OR.")
# Comprobamos si todas las fuentes son simples conexiones o powerrail
is_simple_or = all(s['type'] in ['powerrail', 'connection'] for s in input_sources_repr)
if is_simple_or:
# Si son conexiones simples, mantenemos la lista (implica OR)
instruction_repr['inputs'][conn_dest_pin] = input_sources_repr
else:
# Si hay operandos directos (variables/constantes), esto es más complejo.
# Por ahora, lo marcamos como una rama OR genérica.
# Una mejora sería analizar la estructura LAD/FBD más a fondo.
print(f"Advertencia: Rama OR compleja detectada para Part UID={part_uid}, Pin={conn_dest_pin}. Simplificando a lista.")
instruction_repr['inputs'][conn_dest_pin] = input_sources_repr
# else: no sources found for this input pin (podría ser normal si no está conectado)
# Procesar Salidas (igual que antes)
# Busca todas las conexiones que *salen* de esta instrucción y van a un Access (asignación)
for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items():
for source_uid, source_pin in sources_list: for source_uid, source_pin in sources_list:
if source_uid == part_uid and conn_dest_uid in access_map: # Si la fuente es esta instrucción y el destino es un Access if source_uid == part_uid and conn_dest_uid in access_map:
# print(f"DEBUG: Part UID={part_uid}, Pin Salida='{source_pin}', Destino Access UID={conn_dest_uid}") if source_pin not in instruction_repr['outputs']: instruction_repr['outputs'][source_pin] = []
# Agrega el Access de destino a la lista de salidas de este pin
if source_pin not in instruction_repr['outputs']:
instruction_repr['outputs'][source_pin] = []
# Evita añadir duplicados si el XML es redundante
if access_map[conn_dest_uid] not in instruction_repr['outputs'][source_pin]: if access_map[conn_dest_uid] not in instruction_repr['outputs'][source_pin]:
instruction_repr['outputs'][source_pin].append(access_map[conn_dest_uid]) instruction_repr['outputs'][source_pin].append(access_map[conn_dest_uid])
# Almacena la representación JSON de la instrucción
all_logic_steps[part_uid] = instruction_repr all_logic_steps[part_uid] = instruction_repr
# 4. Ordena las instrucciones por UID (intenta mantener un orden lógico si los UIDs son numéricos)
# Esto es una heurística, el orden real de ejecución depende del PLC. # *** NUEVO: Procesar y añadir lógica ENO "interesante" ***
for source_instr_uid, eno_destinations in eno_outputs.items():
if source_instr_uid not in all_logic_steps: continue # Seguridad
interesting_eno_logic = []
for dest_uid, dest_pin in eno_destinations:
# Determinar si es una conexión directa a EN de otra instrucción
is_direct_en_connection = (dest_uid in parts_map and dest_pin == 'en')
if not is_direct_en_connection:
# Si NO es directa a EN, es "interesante"
target_info = {'target_pin': dest_pin}
if dest_uid in parts_map:
target_info['target_type'] = 'instruction'
target_info['target_uid'] = dest_uid
target_info['target_name'] = parts_map[dest_uid]['name']
elif dest_uid in access_map:
# El destino es una variable o constante
target_info['target_type'] = 'operand'
target_info['target_details'] = access_map[dest_uid] # Incluye toda la info del Access
else:
target_info['target_type'] = 'unknown'
target_info['target_uid'] = dest_uid
interesting_eno_logic.append(target_info)
# print(f"DEBUG: Red {network_id} - ENO de {source_instr_uid}: Lógica interesante -> {target_info}")
# Añadir la lista de lógica ENO interesante a la instrucción fuente, si existe
if interesting_eno_logic:
all_logic_steps[source_instr_uid]['eno_logic'] = interesting_eno_logic
# 4. Ordenar y finalizar
try: try:
# Intenta convertir UIDs a enteros para ordenar numéricamente,
# poniendo los no numéricos al final.
sorted_uids = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) sorted_uids = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf'))
except ValueError: except ValueError:
# Si falla la conversión a int (UIDs no numéricos complejos), ordena alfabéticamente.
print(f"Advertencia: UIDs no puramente numéricos en red {network_id}. Ordenando alfabéticamente.") print(f"Advertencia: UIDs no puramente numéricos en red {network_id}. Ordenando alfabéticamente.")
sorted_uids = sorted(all_logic_steps.keys()) sorted_uids = sorted(all_logic_steps.keys())
# Construye la lista final de lógica ordenada
network_logic = [all_logic_steps[uid] for uid in sorted_uids if uid in all_logic_steps] network_logic = [all_logic_steps[uid] for uid in sorted_uids if uid in all_logic_steps]
# print(f"--- Fin Parseo Red ID={network_id} ---") # Devolver estructura de red con ID, título, comentario y lógica
return {'id': network_id, 'title': network_title, 'logic': network_logic} return {'id': network_id, 'title': network_title, 'comment': network_comment, 'logic': network_logic}
def convert_xml_to_json(xml_filepath, json_filepath): def convert_xml_to_json(xml_filepath, json_filepath):
""" """
Función principal que orquesta la conversión del archivo XML de Openness Función principal que orquesta la conversión del archivo XML de Openness
a un archivo JSON simplificado que representa la estructura del bloque FC. a un archivo JSON simplificado que representa la estructura del bloque FC,
incluyendo comentarios y lógica ENO no trivial.
""" """
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...") print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
if not os.path.exists(xml_filepath): if not os.path.exists(xml_filepath):
print(f"Error Crítico: Archivo XML no encontrado en '{xml_filepath}'") print(f"Error Crítico: Archivo XML no encontrado en '{xml_filepath}'")
return # Termina si el archivo no existe return
try: try:
# Parsea el archivo XML
print("Paso 1: Parseando archivo XML...") print("Paso 1: Parseando archivo XML...")
parser = etree.XMLParser(remove_blank_text=True) # Intenta limpiar espacios irrelevantes parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(xml_filepath, parser) tree = etree.parse(xml_filepath, parser)
root = tree.getroot() root = tree.getroot()
print("Paso 1: Parseo XML completado.") print("Paso 1: Parseo XML completado.")
# Encuentra el bloque FC principal usando local-name() para evitar problemas de namespace
# // busca en todo el árbol
print("Paso 2: Buscando el bloque SW.Blocks.FC...") print("Paso 2: Buscando el bloque SW.Blocks.FC...")
fc_block_list = root.xpath("//*[local-name()='SW.Blocks.FC']") fc_block_list = root.xpath("//*[local-name()='SW.Blocks.FC']")
if not fc_block_list: if not fc_block_list:
print("Error Crítico: No se encontró el elemento <SW.Blocks.FC> en el archivo.") print("Error Crítico: No se encontró el elemento <SW.Blocks.FC> en el archivo.")
return # Termina si no encuentra el bloque FC return
fc_block = fc_block_list[0] # Asume que solo hay un bloque FC principal en este archivo fc_block = fc_block_list[0]
print(f"Paso 2: Bloque SW.Blocks.FC encontrado (ID={fc_block.get('ID')}).") print(f"Paso 2: Bloque SW.Blocks.FC encontrado (ID={fc_block.get('ID')}).")
print("Paso 3: Extrayendo atributos del bloque...")
# Extrae los atributos básicos del bloque desde su AttributeList
print("Paso 3: Extrayendo atributos del bloque (Nombre, Número, Lenguaje)...")
# Busca AttributeList como hijo directo de fc_block
attribute_list_node = fc_block.xpath("./*[local-name()='AttributeList']") attribute_list_node = fc_block.xpath("./*[local-name()='AttributeList']")
block_name_val = "Unknown" block_name_val = "Unknown"
block_number_val = None block_number_val = None
block_lang_val = "Unknown" block_lang_val = "Unknown"
if attribute_list_node: if attribute_list_node:
attr_list = attribute_list_node[0] attr_list = attribute_list_node[0]
# Extrae Nombre
name_node = attr_list.xpath("./*[local-name()='Name']/text()") name_node = attr_list.xpath("./*[local-name()='Name']/text()")
if name_node: block_name_val = name_node[0].strip() if name_node: block_name_val = name_node[0].strip()
# Extrae Número
num_node = attr_list.xpath("./*[local-name()='Number']/text()") num_node = attr_list.xpath("./*[local-name()='Number']/text()")
if num_node and num_node[0].isdigit(): block_number_val = int(num_node[0]) if num_node and num_node[0].isdigit(): block_number_val = int(num_node[0])
# Extrae Lenguaje
lang_node = attr_list.xpath("./*[local-name()='ProgrammingLanguage']/text()") lang_node = attr_list.xpath("./*[local-name()='ProgrammingLanguage']/text()")
if lang_node: block_lang_val = lang_node[0].strip() if lang_node: block_lang_val = lang_node[0].strip()
print(f"Paso 3: Atributos extraídos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'") print(f"Paso 3: Atributos extraídos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'")
else: else: print("Advertencia: No se encontró AttributeList para el bloque FC.")
print("Advertencia: No se encontró AttributeList para el bloque FC.")
# *** NUEVO: Extraer comentario del bloque ***
block_comment_val = ""
# El comentario del bloque suele estar en ObjectList > MultilingualText[@CompositionName='Comment']
comment_node_list = fc_block.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']")
if comment_node_list:
block_comment_val = get_multilingual_text(comment_node_list[0])
print(f"Paso 3b: Comentario del bloque extraído: '{block_comment_val[:50]}...'")
# Inicializa la estructura del resultado JSON
result = { result = {
"block_name": block_name_val, "block_name": block_name_val,
"block_number": block_number_val, "block_number": block_number_val,
"language": block_lang_val, "language": block_lang_val,
"interface": {}, # Diccionario para las secciones de la interfaz "block_comment": block_comment_val, # Añadido comentario del bloque
"networks": [] # Lista para las redes lógicas "interface": {},
"networks": []
} }
# Extrae la Interfaz del bloque
print("Paso 4: Extrayendo la interfaz del bloque...") print("Paso 4: Extrayendo la interfaz del bloque...")
interface_found = False interface_found = False
# Busca Interface DENTRO de AttributeList (corrección clave) if attribute_list_node:
if attribute_list_node: # Solo busca si encontramos AttributeList
interface_node_list = attribute_list_node[0].xpath("./*[local-name()='Interface']") interface_node_list = attribute_list_node[0].xpath("./*[local-name()='Interface']")
if interface_node_list: if interface_node_list:
interface_node = interface_node_list[0] interface_node = interface_node_list[0]
interface_found = True interface_found = True
print("Paso 4: Nodo Interface encontrado dentro de AttributeList.") print("Paso 4: Nodo Interface encontrado dentro de AttributeList.")
# Ahora busca las secciones DENTRO de Interface usando el namespace 'iface'
section_count = 0
# Usa './/' para buscar Sections en cualquier nivel dentro de Interface
for section in interface_node.xpath(".//iface:Section", namespaces=ns): for section in interface_node.xpath(".//iface:Section", namespaces=ns):
section_count += 1
section_name = section.get('Name') section_name = section.get('Name')
members = [] members = []
member_count = 0
# Busca Members como hijos directos de Section usando 'iface'
for member in section.xpath("./iface:Member", namespaces=ns): for member in section.xpath("./iface:Member", namespaces=ns):
member_count +=1
member_name = member.get('Name') member_name = member.get('Name')
member_dtype = member.get('Datatype') member_dtype = member.get('Datatype')
if member_name and member_dtype: if member_name and member_dtype: members.append({"name": member_name, "datatype": member_dtype})
members.append({"name": member_name, "datatype": member_dtype}) if members: result["interface"][section_name] = members
else: if not result["interface"]: print("Advertencia: Nodo Interface encontrado, pero no contenía secciones iface:Section válidas.")
print(f"Advertencia: Miembro inválido encontrado en sección '{section_name}' (sin nombre o tipo).") else: print("Advertencia: No se encontró el nodo <Interface> DENTRO de <AttributeList>.")
if members: if not interface_found and not result["interface"]: print("Advertencia: No se pudo extraer ninguna información de la interfaz.")
print(f"DEBUG: Interfaz - Sección '{section_name}' encontrada con {member_count} miembros.")
result["interface"][section_name] = members
# else: print(f"DEBUG: Interfaz - Sección '{section_name}' encontrada pero sin miembros válidos.")
if section_count == 0:
print("Advertencia: Nodo Interface encontrado, pero no contenía secciones iface:Section válidas.")
else:
print("Advertencia: No se encontró el nodo <Interface> DENTRO de <AttributeList>.")
# else: # Ya se advirtió que no se encontró AttributeList
if not interface_found and not result["interface"]:
print("Advertencia: No se pudo extraer ninguna información de la interfaz.")
# Extrae la lógica de las Redes (CompileUnits)
print("Paso 5: Extrayendo la lógica de las redes (CompileUnits)...") print("Paso 5: Extrayendo la lógica de las redes (CompileUnits)...")
networks_processed_count = 0 networks_processed_count = 0
# Busca ObjectList como hijo directo de fc_block
object_list_node = fc_block.xpath("./*[local-name()='ObjectList']") object_list_node = fc_block.xpath("./*[local-name()='ObjectList']")
if object_list_node: if object_list_node:
# Busca CompileUnits dentro de ObjectList
compile_units = object_list_node[0].xpath("./*[local-name()='SW.Blocks.CompileUnit']") compile_units = object_list_node[0].xpath("./*[local-name()='SW.Blocks.CompileUnit']")
print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.") print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.")
for network_elem in compile_units: for network_elem in compile_units:
networks_processed_count += 1 networks_processed_count += 1
print(f"DEBUG: Procesando red #{networks_processed_count} (ID={network_elem.get('ID')})...") print(f"DEBUG: Procesando red #{networks_processed_count} (ID={network_elem.get('ID')})...")
parsed_network = parse_network(network_elem) parsed_network = parse_network(network_elem) # Ahora parse_network incluye comentario
# Solo añade la red si el parseo no devolvió un error grave if parsed_network and parsed_network.get('error') is None: result["networks"].append(parsed_network)
if parsed_network and parsed_network.get('error') is None:
result["networks"].append(parsed_network)
elif parsed_network: elif parsed_network:
print(f"Error: Falló el parseo de la red ID={parsed_network.get('id')}: {parsed_network.get('error')}") print(f"Error: Falló el parseo de la red ID={parsed_network.get('id')}: {parsed_network.get('error')}")
result["networks"].append(parsed_network) # Añadir incluso con error para depurar result["networks"].append(parsed_network)
else: else: print(f"Error: parse_network devolvió None para un CompileUnit (ID={network_elem.get('ID')}).")
print(f"Error: parse_network devolvió None para un CompileUnit (ID={network_elem.get('ID')}).") if networks_processed_count == 0: print("Advertencia: ObjectList encontrado, pero no contenía SW.Blocks.CompileUnit.")
else: print("Advertencia: No se encontró ObjectList para el bloque FC.")
if networks_processed_count == 0:
print("Advertencia: ObjectList encontrado, pero no contenía SW.Blocks.CompileUnit.")
else:
print("Advertencia: No se encontró ObjectList para el bloque FC.")
# Escribe el resultado al archivo JSON
print("Paso 6: Escribiendo el resultado en el archivo JSON...") print("Paso 6: Escribiendo el resultado en el archivo JSON...")
# Realiza chequeos finales antes de escribir # Chequeos finales
if not result["interface"]: if not result["interface"]: print("ADVERTENCIA FINAL: La sección 'interface' está vacía.")
print("ADVERTENCIA FINAL: La sección 'interface' está vacía en el JSON resultante.") if not result["networks"]: print("ADVERTENCIA FINAL: La sección 'networks' está vacía.")
if not result["networks"]:
print("ADVERTENCIA FINAL: La sección 'networks' está vacía en el JSON resultante.")
else: else:
# Verifica si se extrajeron nombres de variables en las redes # Chequea si alguna instrucción tiene lógica ENO interesante
variable_names_found = any( eno_logic_found = any(instr.get('eno_logic') for net in result.get('networks', []) if net.get('error') is None for instr in net.get('logic', []))
acc.get('type') == 'variable' and acc.get('name') is not None if eno_logic_found: print("INFO FINAL: Se detectó lógica ENO interesante en al menos una instrucción.")
for net in result.get('networks', []) if net.get('error') is None # Solo redes válidas else: print("INFO FINAL: No se detectó lógica ENO interesante (solo conexiones directas ENO->EN o ENO no conectado).")
for instr in net.get('logic', [])
for pin_data in instr.get('inputs', {}).values()
# Maneja entradas que son listas (OR) o diccionarios
for acc in (pin_data if isinstance(pin_data, list) else [pin_data] if isinstance(pin_data, dict) else [])
if isinstance(acc, dict) # Asegura que acc sea un diccionario parseado
) or any(
acc.get('type') == 'variable' and acc.get('name') is not None
for net in result.get('networks', []) if net.get('error') is None # Solo redes válidas
for instr in net.get('logic', [])
for pin_data_list in instr.get('outputs', {}).values() if isinstance(pin_data_list, list)
for acc in pin_data_list if isinstance(acc, dict) # Asegura que acc sea un diccionario
)
if not variable_names_found:
print("ADVERTENCIA FINAL: No parece haberse extraído ningún nombre de variable válido en las redes procesadas.")
else:
print("INFO FINAL: Se detectaron nombres de variables en las redes procesadas.")
# Escribe el archivo
try: try:
with open(json_filepath, 'w', encoding='utf-8') as f: with open(json_filepath, 'w', encoding='utf-8') as f:
# Usa indent=4 para formato legible, ensure_ascii=False para caracteres UTF-8
json.dump(result, f, indent=4, ensure_ascii=False) json.dump(result, f, indent=4, ensure_ascii=False)
print(f"Paso 6: Escritura completada.") print(f"Paso 6: Escritura completada.")
print(f"Conversión finalizada con éxito. Archivo JSON guardado en: '{json_filepath}'") print(f"Conversión finalizada con éxito. Archivo JSON guardado en: '{json_filepath}'")
except IOError as e: except IOError as e: print(f"Error Crítico: No se pudo escribir el archivo JSON en '{json_filepath}'. Error: {e}")
print(f"Error Crítico: No se pudo escribir el archivo JSON en '{json_filepath}'. Error: {e}") except TypeError as e: print(f"Error Crítico: Problema al serializar datos a JSON. Error: {e}")
except TypeError as e:
print(f"Error Crítico: Problema al serializar datos a JSON (posiblemente datos no serializables). Error: {e}")
except etree.XMLSyntaxError as e: except etree.XMLSyntaxError as e:
print(f"Error Crítico: Error de sintaxis en el archivo XML '{xml_filepath}'. Detalles: {e}") print(f"Error Crítico: Error de sintaxis en el archivo XML '{xml_filepath}'. Detalles: {e}")
except Exception as e: except Exception as e:
print(f"Error Crítico: Ocurrió un error inesperado durante el procesamiento: {e}") print(f"Error Crítico: Ocurrió un error inesperado durante el procesamiento: {e}")
print("--- Traceback ---") print("--- Traceback ---"); traceback.print_exc(); print("--- Fin Traceback ---")
traceback.print_exc() # Imprime la traza completa de la excepción
print("--- Fin Traceback ---")
# --- Punto de Entrada Principal --- # --- Punto de Entrada Principal ---
if __name__ == "__main__": if __name__ == "__main__":
# Define los nombres de los archivos de entrada y salida
xml_file = 'BlenderRun_ProdTime.xml' xml_file = 'BlenderRun_ProdTime.xml'
json_file = 'BlenderRun_ProdTime_simplified.json' json_file = 'BlenderRun_ProdTime_simplified.json'
# Llama a la función principal de conversión
convert_xml_to_json(xml_file, json_file) convert_xml_to_json(xml_file, json_file)