# -*- coding: utf-8 -*- import json import os from lxml import etree # --- Namespaces --- ns = { 'iface': 'http://www.siemens.com/automation/Openness/SW/Interface/v5', 'flg': 'http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4' } # --- Helper Functions --- # (get_multilingual_text, get_symbol_name, parse_access, parse_part sin cambios respecto a la última versión) def get_multilingual_text(element, default_lang='en-US', fallback_lang='it-IT'): """Intenta extraer texto de un MultilingualText, priorizando idiomas.""" if element is None: return "" try: xpath_expr = f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture']='{default_lang}']/*[local-name()='AttributeList']/*[local-name()='Text']" text_item = element.xpath(xpath_expr) if text_item and text_item[0].text: return text_item[0].text.strip() xpath_expr = f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture']='{fallback_lang}']/*[local-name()='AttributeList']/*[local-name()='Text']" text_item = element.xpath(xpath_expr) if text_item and text_item[0].text: return text_item[0].text.strip() xpath_expr = f".//*[local-name()='MultilingualTextItem']/*[local-name()='AttributeList']/*[local-name()='Text']" text_item = element.xpath(xpath_expr) if text_item and text_item[0].text: return text_item[0].text.strip() except Exception as e: print(f"Advertencia: Error extrayendo MultilingualText: {e}") return "" def get_symbol_name(symbol_element): """Construye el nombre completo del símbolo a partir de sus componentes.""" if symbol_element is None: return None try: components = symbol_element.xpath("./*[local-name()='Component']/@Name") if components: return ".".join(f'"{c}"' for c in components) else: # print(f"DEBUG: No se encontraron 'Component' en: {etree.tostring(symbol_element).decode()}") return None except Exception as e: # print(f"DEBUG: Excepción en get_symbol_name: {e}") return None def parse_access(access_element): """Parsea un elemento Access para obtener información de variable o constante.""" info = {'uid': access_element.get('UId'), 'scope': access_element.get('Scope'), 'type': 'unknown'} symbol = access_element.xpath("./*[local-name()='Symbol']") constant = access_element.xpath("./*[local-name()='Constant']") if symbol: info['type'] = 'variable'; info['name'] = get_symbol_name(symbol[0]) if info['name'] is None: info['type'] = 'error_parsing_symbol' elif constant: info['type'] = 'constant' const_type_elem = constant[0].xpath("./*[local-name()='ConstantType']") const_val_elem = constant[0].xpath("./*[local-name()='ConstantValue']") info['datatype'] = const_type_elem[0].text if const_type_elem and const_type_elem[0].text is not None else 'Unknown' info['value_str'] = const_val_elem[0].text if const_val_elem and const_val_elem[0].text is not None else None if info['datatype'] == 'Unknown' and info['value_str'] is not None: if info['value_str'].lower() in ['true', 'false']: info['datatype'] = 'Bool' elif info['value_str'].isdigit() or (info['value_str'].startswith('-') and info['value_str'][1:].isdigit()): info['datatype'] = 'Int' elif '.' in info['value_str']: try: float(info['value_str']); info['datatype'] = 'Real' except ValueError: pass if info['value_str'] is not None: info['value'] = info['value_str'] dtype_lower = info['datatype'].lower() val_str_processed = info['value_str'].split('#')[-1] if '#' in info['value_str'] else info['value_str'] try: if dtype_lower in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword', 'byte']: info['value'] = int(val_str_processed) elif dtype_lower == 'bool': info['value'] = val_str_processed.lower() == 'true' or val_str_processed == '1' elif dtype_lower in ['real', 'lreal']: info['value'] = float(val_str_processed) except (ValueError, TypeError): info['value'] = info['value_str'] if 'value_str' in info: del info['value_str'] else: info['type'] = 'error_parsing_constant'; info['value'] = None else: info['type'] = 'unknown_structure' # Verifica si realmente se pudo parsear el nombre para variables if info['type'] == 'variable' and info.get('name') is None: # Si get_symbol_name falló y marcó como None, mantenemos error_parsing_symbol if info.get('type') != 'error_parsing_symbol': print(f"Advertencia: parse_access terminó con tipo 'variable' pero sin nombre para UID {info['uid']}.") info['type'] = 'error_no_name' # Nuevo estado de error return info def parse_part(part_element): """Parsea un elemento Part (instrucción).""" return { 'uid': part_element.get('UId'), 'name': part_element.get('Name'), 'template_values': {tv.get('Name'): tv.get('Type') for tv in part_element.xpath("./*[local-name()='TemplateValue']")} } # --- Main Parsing Logic --- def parse_network(network_element): """Parsea una red (CompileUnit) y extrae su lógica simplificada.""" # (parse_network sin cambios respecto a la última versión) network_logic = [] network_id = network_element.get('ID') 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}" flgnet = network_element.xpath(".//flg:FlgNet", namespaces=ns) if not flgnet: return {'id': network_id, 'title': network_title, 'logic': [], 'error': 'FlgNet not found'} flgnet = flgnet[0] access_map = {acc_info['uid']: acc_info for acc in flgnet.xpath(".//flg:Access", namespaces=ns) if (acc_info := parse_access(acc))} parts_map = {part_info['uid']: part_info for part in flgnet.xpath(".//flg:Part", namespaces=ns) if (part_info := parse_part(part))} wire_connections = {} flg_ns_uri = ns['flg'] for wire in flgnet.xpath(".//flg:Wire", namespaces=ns): source_uid, source_pin, dest_uid, dest_pin = None, None, None, None children = wire.getchildren() if not children: continue source_elem, dest_elem = children[0], children[1] if len(children) > 1 else None if source_elem.tag == etree.QName(flg_ns_uri, 'Powerrail'): source_uid, source_pin = 'POWERRAIL', 'out' elif source_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): source_uid, source_pin = source_elem.get('UId'), 'value' elif source_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): source_uid, source_pin = source_elem.get('UId'), source_elem.get('Name') if dest_elem is not None: if dest_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): dest_uid, dest_pin = dest_elem.get('UId'), 'value' elif dest_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): dest_uid, dest_pin = dest_elem.get('UId'), dest_elem.get('Name') if dest_uid and dest_pin and (source_uid is not None or source_pin == 'out'): dest_key = (dest_uid, dest_pin) source_info = (source_uid, source_pin) if dest_key not in wire_connections: wire_connections[dest_key] = [] wire_connections[dest_key].append(source_info) all_logic_steps = {} for part_uid, part_info in parts_map.items(): instruction_repr = {'instruction_uid': part_uid, 'type': part_info['name'], 'inputs': {}, 'outputs': {}} for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): if conn_dest_uid == part_uid: input_sources = [] for source_uid, source_pin in sources_list: if source_uid == 'POWERRAIL': input_sources.append({'type': 'powerrail'}) elif source_uid in access_map: input_sources.append(access_map[source_uid]) elif source_uid in parts_map: input_sources.append({'type': 'connection', 'source_instruction_uid': source_uid, 'source_instruction_type': parts_map[source_uid]['name'], 'source_pin': source_pin}) else: input_sources.append({'type': 'unknown_source', 'uid': source_uid}) if len(input_sources) == 1: instruction_repr['inputs'][conn_dest_pin] = input_sources[0] else: instruction_repr['inputs'][conn_dest_pin] = {'type': 'OR_Branch', 'sources': input_sources} for (conn_dest_uid, conn_dest_pin), sources_list in wire_connections.items(): for source_uid, source_pin in sources_list: if source_uid == part_uid and conn_dest_uid in access_map: if source_pin not in instruction_repr['outputs']: 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]) all_logic_steps[part_uid] = instruction_repr sorted_uids = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) network_logic = [all_logic_steps[uid] for uid in sorted_uids if uid in all_logic_steps] return {'id': network_id, 'title': network_title, 'logic': network_logic} def convert_xml_to_json(xml_filepath, json_filepath): """Función principal para convertir el XML a JSON.""" if not os.path.exists(xml_filepath): print(f"Error: Archivo XML no encontrado en {xml_filepath}") return try: tree = etree.parse(xml_filepath) root = tree.getroot() # Buscar FC usando local-name() fc_block_list = root.xpath("//*[local-name()='SW.Blocks.FC']") if not fc_block_list: print("Error: No se encontró el elemento ") return fc_block = fc_block_list[0] # Obtener el namespace real de fc_block (si tiene uno) fc_block_ns_uri = fc_block.tag.split('}')[0][1:] if '}' in fc_block.tag else None temp_ns = ns.copy() # Copiar namespaces base fc_prefix = 'fcns' # Prefijo temporal para el namespace de fc_block if fc_block_ns_uri: temp_ns[fc_prefix] = fc_block_ns_uri # Construir el tag con prefijo para búsquedas relativas si hay namespace interface_tag = f"{fc_prefix}:Interface" attribute_list_tag = f"{fc_prefix}:AttributeList" object_list_tag = f"{fc_prefix}:ObjectList" else: # Si fc_block no tiene namespace, buscar hijos sin prefijo interface_tag = "Interface" attribute_list_tag = "AttributeList" object_list_tag = "ObjectList" print(f"DEBUG: Buscando hijos de {fc_block.tag} (Namespace URI: {fc_block_ns_uri})") print(f"DEBUG: Usando tag para Interface: {interface_tag}") print(f"DEBUG: Usando tag para AttributeList: {attribute_list_tag}") print(f"DEBUG: Usando tag para ObjectList: {object_list_tag}") # Usar el tag construido (con o sin prefijo) para buscar AttributeList y sus hijos block_name = fc_block.xpath(f"./{attribute_list_tag}/*[local-name()='Name']/text()", namespaces=temp_ns) block_number = fc_block.xpath(f"./{attribute_list_tag}/*[local-name()='Number']/text()", namespaces=temp_ns) block_lang = fc_block.xpath(f"./{attribute_list_tag}/*[local-name()='ProgrammingLanguage']/text()", namespaces=temp_ns) result = { "block_name": block_name[0].strip() if block_name else "Unknown", "block_number": int(block_number[0]) if block_number and block_number[0].isdigit() else None, "language": block_lang[0].strip() if block_lang else "Unknown", "interface": {}, "networks": [] } # --- CORREGIDA NUEVAMENTE: Extracción de Interfaz --- # Buscar Interface DENTRO de AttributeList # Usamos local-name() para robustez interface_node_list = fc_block.xpath(f"./*[local-name()='AttributeList']/*[local-name()='Interface']") if interface_node_list: interface_node = interface_node_list[0] # Tomar el primer nodo Interface encontrado print(f"DEBUG: Nodo Interface encontrado DENTRO de AttributeList!") # Dentro de Interface, Sections/Section/Member usan el namespace 'iface' interface_sections = interface_node.xpath(".//iface:Section", namespaces=ns) # Usar ns original aquí if not interface_sections: print("Advertencia: Nodo Interface encontrado, pero no se encontraron iface:Section dentro.") section_count = 0 for section in interface_sections: section_count += 1 section_name = section.get('Name') members = [] member_count = 0 for member in section.xpath("./iface:Member", namespaces=ns): # Usar ns original member_count += 1 members.append({ "name": member.get('Name'), "datatype": member.get('Datatype') }) if members: print(f"DEBUG: Sección '{section_name}' encontrada con {member_count} miembros.") result["interface"][section_name] = members else: print(f"DEBUG: Sección '{section_name}' encontrada pero sin miembros iface:Member.") if section_count == 0 and not result["interface"]: print("Advertencia: Nodo Interface encontrado, pero sin secciones iface:Section válidas.") else: # Si no se encontró Interface DENTRO de AttributeList print("Advertencia: No se encontró el nodo DENTRO de .") # Si no se encontró Interface DENTRO de AttributeList print("Advertencia: No se encontró el nodo DENTRO de .") # Sacar la expresión XPath fuera de la f-string para evitar SyntaxError attribute_list_nodes = fc_block.xpath("./*[local-name()='AttributeList']") if attribute_list_nodes: attribute_list_content = etree.tostring(attribute_list_nodes[0], pretty_print=True).decode() else: attribute_list_content = 'AttributeList no encontrado' print(f"DEBUG: Contenido de AttributeList: {attribute_list_content}") # --- Extracción Lógica de Redes --- # Buscar ObjectList usando tag construido, luego CompileUnit sin prefijo object_list_node = fc_block.xpath(f"./{object_list_tag}", namespaces=temp_ns) networks = [] if object_list_node: networks = object_list_node[0].xpath("./*[local-name()='SW.Blocks.CompileUnit']") # Buscar CompileUnit por local-name network_count = 0 for network_elem in networks: network_count += 1 parsed_network = parse_network(network_elem) result["networks"].append(parsed_network) if network_count == 0: print("Advertencia: No se encontraron redes (SW.Blocks.CompileUnit).") # --- Escribir resultado a JSON --- # Añadir un chequeo final antes de escribir if not result["interface"]: print("ADVERTENCIA FINAL: La sección 'interface' está vacía en el JSON resultante.") variable_names_found = any( acc.get('type') == 'variable' and acc.get('name') is not None for net in result.get('networks', []) for instr in net.get('logic', []) for pin_data in instr.get('inputs', {}).values() if isinstance(pin_data, dict) for acc in ([pin_data] if pin_data.get('type') != 'OR_Branch' else pin_data.get('sources', [])) ) or any( acc.get('type') == 'variable' and acc.get('name') is not None for net in result.get('networks', []) 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) ) if not variable_names_found: print("ADVERTENCIA FINAL: Parece que no se extrajeron nombres de variables en las redes.") with open(json_filepath, 'w', encoding='utf-8') as f: json.dump(result, f, indent=4, ensure_ascii=False) print(f"Conversión completada. Archivo JSON guardado en: {json_filepath}") except etree.XMLSyntaxError as e: print(f"Error de sintaxis XML: {e}") except Exception as e: print(f"Ocurrió un error inesperado durante el procesamiento: {e}") import traceback traceback.print_exc() # --- Ejecución --- if __name__ == "__main__": xml_file = 'BlenderRun_ProdTime.xml' json_file = 'BlenderRun_ProdTime_simplified.json' convert_xml_to_json(xml_file, json_file)