From 694df87089ad796b843b4c6936cfeb2c02e9be40 Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 18 Apr 2025 10:44:29 +0200 Subject: [PATCH] Segunda version --- BlenderRun_ProdTime_simplified.json | 309 ++++++++++++++++++++-------- to_jason.py | 283 ------------------------- to_json.py | 297 ++++++++++++++++++++++++++ 3 files changed, 519 insertions(+), 370 deletions(-) delete mode 100644 to_jason.py create mode 100644 to_json.py diff --git a/BlenderRun_ProdTime_simplified.json b/BlenderRun_ProdTime_simplified.json index 715cb3c..2b8c642 100644 --- a/BlenderRun_ProdTime_simplified.json +++ b/BlenderRun_ProdTime_simplified.json @@ -2,11 +2,52 @@ "block_name": "BlenderRun_ProdTime", "block_number": 2040, "language": "LAD", - "interface": {}, + "interface": { + "Temp": [ + { + "name": "m1MinONS", + "datatype": "Bool" + }, + { + "name": "m1HourONS", + "datatype": "Bool" + }, + { + "name": "Buffer", + "datatype": "Bool" + }, + { + "name": "mRunMin", + "datatype": "Bool" + }, + { + "name": "mRunHr", + "datatype": "Bool" + }, + { + "name": "I_DIRunning_sec", + "datatype": "DInt" + }, + { + "name": "I_DIRunning_min", + "datatype": "DInt" + }, + { + "name": "MOD60", + "datatype": "DInt" + } + ], + "Return": [ + { + "name": "Ret_Val", + "datatype": "Void" + } + ] + }, "networks": [ { "id": "9", - "title": "", + "title": "Seconds", "logic": [ { "instruction_uid": "26", @@ -15,7 +56,8 @@ "operand": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" }, "in": { "type": "powerrail" @@ -30,7 +72,8 @@ "operand": { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"CLK_1.0S\"" }, "in": { "type": "connection", @@ -48,12 +91,15 @@ "in1": { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gSLIM_Sec\"" }, "in2": { "uid": "24", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 }, "en": { "type": "connection", @@ -67,7 +113,8 @@ { "uid": "25", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gSLIM_Sec\"" } ] } @@ -76,7 +123,7 @@ }, { "id": "1A", - "title": "", + "title": "Reset Hours", "logic": [ { "instruction_uid": "24", @@ -85,7 +132,8 @@ "operand": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"SLIM_Variables\".\"ResetHour\"" }, "in": { "type": "powerrail" @@ -100,7 +148,9 @@ "in": { "uid": "22", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 }, "en": { "type": "connection", @@ -114,7 +164,8 @@ { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gSLIM_Sec\"" } ] } @@ -123,7 +174,7 @@ }, { "id": "2B", - "title": "", + "title": "Seconds Counter", "logic": [ { "instruction_uid": "26", @@ -132,7 +183,8 @@ "operand": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"gBlenderBlending\"" }, "in": { "type": "powerrail" @@ -147,7 +199,8 @@ "operand": { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"CLK_1.0S\"" }, "in": { "type": "connection", @@ -165,12 +218,15 @@ "in1": { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" }, "in2": { "uid": "24", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 }, "en": { "type": "connection", @@ -184,7 +240,8 @@ { "uid": "25", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" } ] } @@ -193,7 +250,7 @@ }, { "id": "3C", - "title": "", + "title": "Minute", "logic": [ { "instruction_uid": "24", @@ -202,12 +259,15 @@ "in1": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" }, "in2": { "uid": "22", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 60 }, "pre": { "type": "powerrail" @@ -228,7 +288,8 @@ "operand": { "uid": "23", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"m1MinONS\"" } }, "outputs": {} @@ -237,7 +298,7 @@ }, { "id": "4D", - "title": "", + "title": "Minute Counter", "logic": [ { "instruction_uid": "27", @@ -246,7 +307,8 @@ "operand": { "uid": "21", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"m1MinONS\"" }, "in": { "type": "powerrail" @@ -261,7 +323,9 @@ "in": { "uid": "22", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 }, "en": { "type": "connection", @@ -275,7 +339,8 @@ { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" } ] } @@ -287,12 +352,15 @@ "in1": { "uid": "24", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdMin\"" }, "in2": { "uid": "25", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 } }, "outputs": { @@ -300,7 +368,8 @@ { "uid": "26", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdMin\"" } ] } @@ -309,7 +378,7 @@ }, { "id": "5E", - "title": "", + "title": "Hour", "logic": [ { "instruction_uid": "24", @@ -318,12 +387,15 @@ "in1": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdMin\"" }, "in2": { "uid": "22", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 60 }, "pre": { "type": "powerrail" @@ -344,7 +416,8 @@ "operand": { "uid": "23", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"m1HourONS\"" } }, "outputs": {} @@ -353,7 +426,7 @@ }, { "id": "6F", - "title": "", + "title": "Hour Counter", "logic": [ { "instruction_uid": "30", @@ -362,7 +435,8 @@ "operand": { "uid": "21", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"m1HourONS\"" }, "in": { "type": "powerrail" @@ -377,7 +451,9 @@ "in": { "uid": "22", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 }, "en": { "type": "connection", @@ -391,7 +467,8 @@ { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdMin\"" } ] } @@ -403,12 +480,15 @@ "in1": { "uid": "24", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdHour\"" }, "in2": { "uid": "25", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 } }, "outputs": { @@ -416,7 +496,8 @@ { "uid": "26", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdHour\"" } ] } @@ -428,12 +509,15 @@ "in1": { "uid": "27", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gBlendingMaintHour\"" }, "in2": { "uid": "28", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 } }, "outputs": { @@ -441,7 +525,8 @@ { "uid": "29", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gBlendingMaintHour\"" } ] } @@ -450,7 +535,7 @@ }, { "id": "80", - "title": "", + "title": "Counter reset", "logic": [ { "instruction_uid": "29", @@ -459,7 +544,8 @@ "operand": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"gBlenderCIPMode\"" }, "in": { "type": "powerrail" @@ -474,7 +560,8 @@ "operand": { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"gBlenderRinseMode\"" } }, "outputs": {} @@ -505,7 +592,9 @@ "in": { "uid": "23", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 }, "en": { "type": "connection", @@ -519,7 +608,8 @@ { "uid": "24", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdSec\"" } ] } @@ -531,7 +621,9 @@ "in": { "uid": "25", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 } }, "outputs": { @@ -539,7 +631,8 @@ { "uid": "26", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdMin\"" } ] } @@ -551,7 +644,9 @@ "in": { "uid": "27", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 0 } }, "outputs": { @@ -559,7 +654,8 @@ { "uid": "28", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gProdHour\"" } ] } @@ -568,7 +664,7 @@ }, { "id": "91", - "title": "", + "title": "Running Seconds", "logic": [ { "instruction_uid": "26", @@ -577,7 +673,8 @@ "operand": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" }, "in": { "type": "powerrail" @@ -592,7 +689,8 @@ "operand": { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"CLK_1.0S\"" }, "in": { "type": "connection", @@ -610,12 +708,15 @@ "in1": { "uid": "23", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningSeconds\"" }, "in2": { "uid": "24", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 }, "en": { "type": "connection", @@ -629,7 +730,8 @@ { "uid": "25", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningSeconds\"" } ] } @@ -638,7 +740,7 @@ }, { "id": "A2", - "title": "", + "title": "Running Minutes", "logic": [ { "instruction_uid": "35", @@ -647,7 +749,8 @@ "in": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningSeconds\"" }, "en": { "type": "powerrail" @@ -658,7 +761,8 @@ { "uid": "22", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"I_DIRunning_sec\"" } ] } @@ -676,12 +780,15 @@ "in1": { "uid": "23", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"I_DIRunning_sec\"" }, "in2": { "uid": "24", "scope": "TypedConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Unknown", + "value": "DINT#60" } }, "outputs": { @@ -689,7 +796,8 @@ { "uid": "25", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"MOD60\"" } ] } @@ -707,12 +815,15 @@ "in1": { "uid": "26", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"MOD60\"" }, "in2": { "uid": "27", "scope": "TypedConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Unknown", + "value": "DINT#0" } }, "outputs": {} @@ -730,7 +841,8 @@ "operand": { "uid": "28", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" } }, "outputs": {} @@ -742,7 +854,8 @@ "operand": { "uid": "29", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"CLK_1.0S\"" }, "in": { "type": "connection", @@ -760,12 +873,15 @@ "in1": { "uid": "30", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" }, "in2": { "uid": "31", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 }, "en": { "type": "connection", @@ -779,7 +895,8 @@ { "uid": "32", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" } ] } @@ -791,7 +908,8 @@ "bit": { "uid": "33", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"M19012\"" } }, "outputs": {} @@ -809,7 +927,8 @@ "operand": { "uid": "34", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"mRunMin\"" } }, "outputs": {} @@ -818,7 +937,7 @@ }, { "id": "B3", - "title": "", + "title": "Running Hours for Maintenance", "logic": [ { "instruction_uid": "32", @@ -827,7 +946,8 @@ "operand": { "uid": "21", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"mRunMin\"" }, "in": { "type": "powerrail" @@ -842,7 +962,8 @@ "in": { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMinutes\"" }, "en": { "type": "connection", @@ -856,7 +977,8 @@ { "uid": "23", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"I_DIRunning_min\"" } ] } @@ -874,12 +996,15 @@ "in1": { "uid": "24", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"I_DIRunning_min\"" }, "in2": { "uid": "25", "scope": "TypedConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Unknown", + "value": "DINT#60" } }, "outputs": { @@ -887,7 +1012,8 @@ { "uid": "26", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"MOD60\"" } ] } @@ -905,12 +1031,15 @@ "in1": { "uid": "27", "scope": "LocalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"MOD60\"" }, "in2": { "uid": "28", "scope": "TypedConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Unknown", + "value": "DINT#0" } }, "outputs": {} @@ -928,12 +1057,15 @@ "in1": { "uid": "29", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMaintHour\"" }, "in2": { "uid": "30", "scope": "LiteralConstant", - "type": "unknown_access" + "type": "constant", + "datatype": "Int", + "value": 1 } }, "outputs": { @@ -941,7 +1073,8 @@ { "uid": "31", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMaintHour\"" } ] } @@ -950,7 +1083,7 @@ }, { "id": "C4", - "title": "", + "title": "Running Hours for Maintenance", "logic": [ { "instruction_uid": "23", @@ -959,7 +1092,8 @@ "in": { "uid": "21", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"Blender_Variables_Pers\".\"gRunningMaintHour\"" }, "en": { "type": "powerrail" @@ -970,7 +1104,8 @@ { "uid": "22", "scope": "GlobalVariable", - "type": "unknown_access" + "type": "variable", + "name": "\"HMI_Variables_Status\".\"System\".\"BlendingMaintHour\"" } ] } diff --git a/to_jason.py b/to_jason.py deleted file mode 100644 index 9d72d43..0000000 --- a/to_jason.py +++ /dev/null @@ -1,283 +0,0 @@ -import json -import os -from lxml import etree - -# --- Namespaces --- -# Define los namespaces con prefijos explícitos. Quitamos 'default'. -ns = { - 'sw': 'http://www.siemens.com/automation/Openness/SW/Interface/v5', - 'flg': 'http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4' - # No incluimos un prefijo para elementos sin namespace o en el namespace por defecto -} - -# --- Helper Functions --- - -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: - # Accedemos a los elementos sin prefijo directamente - text_item = element.xpath(f".//MultilingualTextItem[AttributeList/Culture='{default_lang}']/AttributeList/Text", namespaces=ns) - if text_item: - return text_item[0].text.strip() if text_item[0].text else "" - - text_item = element.xpath(f".//MultilingualTextItem[AttributeList/Culture='{fallback_lang}']/AttributeList/Text", namespaces=ns) - if text_item: - return text_item[0].text.strip() if text_item[0].text else "" - - text_item = element.xpath(".//MultilingualTextItem/AttributeList/Text", namespaces=ns) - if text_item: - return text_item[0].text.strip() if text_item[0].text else "" - - except Exception as e: - print(f"Advertencia: Error extrayendo MultilingualText: {e}") - pass - 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 - # Accedemos a Component sin prefijo - components = symbol_element.xpath("./Component/@Name") - return ".".join(f'"{c}"' if ' ' in c else c for c in components) - -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') - } - # Accedemos a Symbol y Constant sin prefijo - symbol = access_element.xpath("./Symbol", namespaces=ns) - constant = access_element.xpath("./Constant", namespaces=ns) - - if symbol: - info['type'] = 'variable' - info['name'] = get_symbol_name(symbol[0]) - elif constant: - # Los hijos de Constant tampoco tienen prefijo aparente - const_type_elem = constant[0].xpath("./ConstantType", namespaces=ns) - const_val_elem = constant[0].xpath("./ConstantValue", namespaces=ns) - info['datatype'] = const_type_elem[0].text if const_type_elem else 'Unknown' - info['value_str'] = const_val_elem[0].text if const_val_elem else None # Guardamos original - info['value'] = info['value_str'] # Valor procesado - info['type'] = 'constant' - - # Intenta convertir el valor si es numérico o booleano - if info['value'] is not None: - if info['datatype'].lower() in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword']: - # Manejar DINT#60, etc. - val_str = info['value'].split('#')[-1] if '#' in info['value'] else info['value'] - try: - info['value'] = int(val_str) - except (ValueError, TypeError): - info['value'] = info['value_str'] # Mantener como string si falla - elif info['datatype'].lower() == 'bool': - info['value'] = info['value'].lower() == 'true' or info['value'] == '1' - elif info['datatype'].lower() in ['real', 'lreal']: - try: - info['value'] = float(info['value']) - except (ValueError, TypeError): - info['value'] = info['value_str'] # Mantener como string si falla - # Añadir más conversiones de tipos si es necesario - else: - # Podría ser TypedConstant, que también tiene un dentro - # Si llegamos aquí, es algo inesperado - info['type'] = 'unknown_access' - - return info - - -def parse_part(part_element): - """Parsea un elemento Part (instrucción).""" - return { - 'uid': part_element.get('UId'), - 'name': part_element.get('Name'), - # TemplateValue no parece tener prefijo - 'template_values': {tv.get('Name'): tv.get('Type') - for tv in part_element.xpath("./TemplateValue", namespaces=ns)} - } - -# --- Main Parsing Logic --- - -def parse_network(network_element): - """Parsea una red (CompileUnit) y extrae su lógica simplificada.""" - network_logic = [] - network_id = network_element.get('ID') - # Accedemos a ObjectList y MultilingualText sin prefijo - title_element = network_element.xpath("./ObjectList/MultilingualText[@CompositionName='Title']", namespaces=ns) - network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" - - # Usamos el prefijo 'flg' para FlgNet - 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] - - # 1. Mapear todos los Access y Parts por su UId - # Usamos prefijo flg: para Access y Part dentro de FlgNet - access_map = {} - for acc in flgnet.xpath(".//flg:Access", namespaces=ns): - acc_info = parse_access(acc) - access_map[acc_info['uid']] = acc_info - - parts_map = {} - for part in flgnet.xpath(".//flg:Part", namespaces=ns): - part_info = parse_part(part) - parts_map[part_info['uid']] = part_info - - # 2. Construir mapa de conexiones (destino -> fuente) - wire_connections = {} - # Usamos prefijo flg: para Wire y sus hijos - for wire in flgnet.xpath(".//flg:Wire", namespaces=ns): - source_uid, source_pin = None, None - dest_uid, dest_pin = None, None - - children = wire.getchildren() - if not children: continue # Ignorar wires vacíos si los hubiera - - source_elem = children[0] - dest_elem = children[1] if len(children) > 1 else None - - # Usamos QName para comparar tags con namespace correctamente - flg_ns_uri = ns['flg'] - 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_elem.get('UId') - source_pin = 'value' # Pin implícito para Access - elif source_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): - source_uid = source_elem.get('UId') - source_pin = source_elem.get('Name') - - if dest_elem is not None: - if dest_elem.tag == etree.QName(flg_ns_uri, 'IdentCon'): - dest_uid = dest_elem.get('UId') - dest_pin = 'value' - elif dest_elem.tag == etree.QName(flg_ns_uri, 'NameCon'): - dest_uid = dest_elem.get('UId') - dest_pin = dest_elem.get('Name') - - if dest_uid and dest_pin and (source_uid is not None or source_pin == 'out'): - wire_connections[(dest_uid, dest_pin)] = (source_uid, source_pin) - - - # 3. Iterar sobre las instrucciones (Parts) y encontrar sus conexiones - for part_uid, part_info in parts_map.items(): - instruction_repr = { - 'instruction_uid': part_uid, - 'type': part_info['name'], - 'inputs': {}, - 'outputs': {} - } - - connected_inputs = {k[1]: v for k, v in wire_connections.items() if k[0] == part_uid} - - for dest_pin, (source_uid, source_pin) in connected_inputs.items(): - if source_uid == 'POWERRAIL': - instruction_repr['inputs'][dest_pin] = {'type': 'powerrail'} - elif source_uid in access_map: - instruction_repr['inputs'][dest_pin] = access_map[source_uid] - elif source_uid in parts_map: - instruction_repr['inputs'][dest_pin] = { - 'type': 'connection', - 'source_instruction_uid': source_uid, - 'source_instruction_type': parts_map[source_uid]['name'], - 'source_pin': source_pin - } - else: - # Podría ser una conexión rota o no encontrada - instruction_repr['inputs'][dest_pin] = {'type': 'unknown_source', 'uid': source_uid} - - # Encontrar salidas conectadas a Access - for (conn_dest_uid, conn_dest_pin), (conn_source_uid, conn_source_pin) in wire_connections.items(): - if conn_source_uid == part_uid and conn_dest_uid in access_map: - if conn_source_pin not in instruction_repr['outputs']: - instruction_repr['outputs'][conn_source_pin] = [] - # Añadimos la info del Access que es el destino - instruction_repr['outputs'][conn_source_pin].append(access_map[conn_dest_uid]) - - network_logic.append(instruction_repr) - - 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 SW.Blocks.FC usando local-name() para robustez inicial - # O asumir que no tiene namespace si está bajo Document/Engineering - fc_block = root.xpath("//*[local-name()='SW.Blocks.FC']") - if not fc_block: - print("Error: No se encontró el elemento ") - return - fc_block = fc_block[0] - - # --- Extraer Info General (sin prefijos) --- - block_name = fc_block.xpath("./AttributeList/Name/text()") - block_number = fc_block.xpath("./AttributeList/Number/text()") - block_lang = fc_block.xpath("./AttributeList/ProgrammingLanguage/text()") - - 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": [] - } - - # --- Extraer Interfaz (con prefijo sw:) --- - interface_sections = fc_block.xpath(".//sw:Interface/sw:Sections/sw:Section", namespaces=ns) - for section in interface_sections: - section_name = section.get('Name') - members = [] - for member in section.xpath("./sw:Member", namespaces=ns): - members.append({ - "name": member.get('Name'), - "datatype": member.get('Datatype') - }) - result["interface"][section_name] = members - - # --- Extraer Lógica de Redes (CompileUnit sin prefijo, contenido con flg:) --- - # Buscamos SW.Blocks.CompileUnit sin prefijo, asumiendo que es hijo directo de ObjectList - networks = fc_block.xpath("./ObjectList/SW.Blocks.CompileUnit", namespaces=ns) - 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). Verifica la estructura del XML.") - - - # --- Escribir resultado a JSON --- - 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) \ No newline at end of file diff --git a/to_json.py b/to_json.py new file mode 100644 index 0000000..d22e004 --- /dev/null +++ b/to_json.py @@ -0,0 +1,297 @@ +# -*- 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) \ No newline at end of file