diff --git a/backend/script_groups/XML Parser to SCL/.example/FB_HMI_Interlock.scl b/backend/script_groups/XML Parser to SCL/.example/FB_HMI_Interlock.scl index 1d58efd..ec5a092 100644 --- a/backend/script_groups/XML Parser to SCL/.example/FB_HMI_Interlock.scl +++ b/backend/script_groups/XML Parser to SCL/.example/FB_HMI_Interlock.scl @@ -54,7 +54,7 @@ BEGIN // Cancel the actual use after timeout #Status.HMI_InUse := FALSE; FOR #i:=0 TO 7 DO - #Status.o_HMI_CanBeUsedBy[] := FALSE; + #Status.o_HMI_CanBeUsedBy[#i] := FALSE; END_FOR; END_IF; IF NOT #Status.HMI_InUse THEN @@ -65,7 +65,7 @@ BEGIN END_IF; IF #Status.HMI_CanBeUsedBy > 0 AND #Status.i_Request[#Status.HMI_CanBeUsedBy] THEN // Return that the actual request is active and can use the HMI - #Status.o_HMI_CanBeUsedBy[] := TRUE; + #Status.o_HMI_CanBeUsedBy[#Status.HMI_CanBeUsedBy] := TRUE; #Status.HMI_InUse := TRUE; END_IF; END_IF; @@ -73,7 +73,7 @@ BEGIN // request on every cycle until is granted. // FOR #i:=0 TO 7 DO - #Status.i_Request[] := FALSE; + #Status.i_Request[#i] := FALSE; END_FOR; #Status.o_HMI_CanBeUsedBy[0] := NOT #Status.HMI_InUse; diff --git a/backend/script_groups/XML Parser to SCL/debug_array_parsing.py b/backend/script_groups/XML Parser to SCL/debug_array_parsing.py new file mode 100644 index 0000000..899aabe --- /dev/null +++ b/backend/script_groups/XML Parser to SCL/debug_array_parsing.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Script de debugging para entender por qué los índices de array simples como [#i] no funcionan +""" + +import os +import sys +from lxml import etree + +# Agregar el directorio del script al path +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(script_dir) + +from parsers.parse_scl import reconstruct_scl_from_tokens +from parsers.parser_utils import ns + + +def debug_array_parsing(): + """Función para debug del parsing de arrays""" + + # Cargar el archivo XML problemático + xml_path = r"D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia\PLC_TL27_Q1\ProgramBlocks_XML\FB HMI Interlock.xml" + + print(f"Cargando XML: {xml_path}") + + # Cargar y parsear el XML + with open(xml_path, "r", encoding="utf-8") as f: + content = f.read() + + root = etree.fromstring(content) + + # Buscar todas las instancias de i_Request + print("\n=== Buscando todas las instancias de i_Request ===") + + # Buscar elementos Component con Name="i_Request" + components = root.xpath("//Component[@Name='i_Request']") + + print(f"Encontrados {len(components)} componentes i_Request") + + # Si no encontramos nada, buscar de manera más amplia + if len(components) == 0: + print("No se encontraron con XPath directo, buscando de manera recursiva...") + components = [] + for elem in root.iter(): + if elem.tag.endswith("Component") and elem.get("Name") == "i_Request": + components.append(elem) + print( + f"Encontrados {len(components)} componentes i_Request con búsqueda recursiva" + ) + + for i, comp in enumerate(components): + print(f"\n--- Componente {i+1} (UId={comp.get('UId')}) ---") + + # Obtener todos los hijos + children = comp.xpath("./*") + print(f"Número de hijos: {len(children)}") + + for j, child in enumerate(children): + tag = etree.QName(child.tag).localname + print(f" Hijo {j}: {tag}") + if tag == "Token": + print(f" Text: '{child.get('Text')}'") + elif tag == "Access": + print(f" Scope: '{child.get('Scope')}'") + # Buscar Symbol dentro del Access + symbols = child.xpath(".//Symbol") + if symbols: + symbol_components = symbols[0].xpath(".//Component") + for sc in symbol_components: + print(f" Component: '{sc.get('Name')}'") + + # Verificar si este componente tiene patrón de array + has_array_pattern = False + bracket_start_idx = -1 + bracket_end_idx = -1 + + # Buscar los tokens [ y ] + for idx, child in enumerate(children): + tag = etree.QName(child.tag).localname + if tag == "Token": + text = child.get("Text") + if text == "[" and bracket_start_idx == -1: + bracket_start_idx = idx + elif text == "]" and bracket_start_idx != -1: + bracket_end_idx = idx + break + + if bracket_start_idx != -1 and bracket_end_idx != -1: + has_array_pattern = True + print( + f" Corchetes encontrados en índices: {bracket_start_idx} y {bracket_end_idx}" + ) + + print(f" Tiene patrón de array: {has_array_pattern}") + + if has_array_pattern: + print(" >>> Este componente debería ser procesado como array <<<") + + # Simular el procesamiento manual del Access del medio + for middle_idx in range(bracket_start_idx + 1, bracket_end_idx): + middle_child = children[middle_idx] + child_tag = etree.QName(middle_child.tag).localname + print(f" Procesando elemento medio {middle_idx}: {child_tag}") + if child_tag == "Access": + scope = middle_child.get("Scope") + print(f" Scope: {scope}") + + if scope == "LocalVariable": + print(" >>> Es LocalVariable, procesando manualmente <<<") + + # Debug: mostrar toda la estructura del Access + print(" Estructura completa del Access:") + + def print_xml_structure(elem, indent=" "): + tag = etree.QName(elem.tag).localname + attrs = dict(elem.attrib) + print(f"{indent}{tag}: {attrs}") + for child in elem: + print_xml_structure(child, indent + " ") + + print_xml_structure(middle_child) + + # Buscar Symbol con diferentes métodos + symbol_elem_ns = middle_child.xpath( + "./st:Symbol", namespaces=ns + ) + symbol_elem_no_ns = middle_child.xpath("./Symbol") + symbol_elem_recursive = [] + for child in middle_child: + if etree.QName(child.tag).localname == "Symbol": + symbol_elem_recursive.append(child) + + print(f" Symbol con namespace: {len(symbol_elem_ns)}") + print(f" Symbol sin namespace: {len(symbol_elem_no_ns)}") + print( + f" Symbol recursivo manual: {len(symbol_elem_recursive)}" + ) + + # Usar el método que funcione + symbol_elem = None + if symbol_elem_ns: + symbol_elem = symbol_elem_ns + print(" Usando Symbol con namespace") + elif symbol_elem_no_ns: + symbol_elem = symbol_elem_no_ns + print(" Usando Symbol sin namespace") + elif symbol_elem_recursive: + symbol_elem = symbol_elem_recursive + print(" Usando Symbol recursivo manual") + + if symbol_elem: + print( + f" Procesando Symbol (total: {len(symbol_elem)})" + ) + + # Buscar componentes dentro del Symbol + components_inner_ns = symbol_elem[0].xpath( + "./st:Component", namespaces=ns + ) + components_inner_no_ns = symbol_elem[0].xpath("./Component") + components_inner_manual = [] + for child in symbol_elem[0]: + if etree.QName(child.tag).localname == "Component": + components_inner_manual.append(child) + + print( + f" Componentes con namespace: {len(components_inner_ns)}" + ) + print( + f" Componentes sin namespace: {len(components_inner_no_ns)}" + ) + print( + f" Componentes manual: {len(components_inner_manual)}" + ) + + # Usar el método que funcione + components_inner = None + if components_inner_ns: + components_inner = components_inner_ns + print(" Usando componentes con namespace") + elif components_inner_no_ns: + components_inner = components_inner_no_ns + print(" Usando componentes sin namespace") + elif components_inner_manual: + components_inner = components_inner_manual + print(" Usando componentes manual") + + if components_inner: + print( + f" Componentes internos encontrados: {len(components_inner)}" + ) + result_parts = [] + for k, comp_inner in enumerate(components_inner): + name = comp_inner.get("Name", "_ERR_COMP_") + print(f" Componente {k}: '{name}'") + if k == 0: + result_parts.append(f"#{name}") + print(f" -> Se convertirá en: #{name}") + else: + result_parts.append(f".{name}") + print(f" -> Se convertirá en: .{name}") + final_result = "".join(result_parts) + print( + f" >>> RESULTADO FINAL: '{final_result}' <<<" + ) + else: + print(" ERROR: No se encontraron componentes") + else: + print( + " ERROR: No se encontró Symbol dentro del Access" + ) + + print("\n=== Fin del análisis ===") + + +if __name__ == "__main__": + debug_array_parsing() diff --git a/backend/script_groups/XML Parser to SCL/parsers/parse_scl.py b/backend/script_groups/XML Parser to SCL/parsers/parse_scl.py index 19c9239..e69a580 100644 --- a/backend/script_groups/XML Parser to SCL/parsers/parse_scl.py +++ b/backend/script_groups/XML Parser to SCL/parsers/parse_scl.py @@ -140,146 +140,139 @@ def reconstruct_scl_from_tokens(st_node): # --- Array Index Access --- # Verificar si este componente tiene hijos que indican acceso de array - # Buscar estructura: + # Buscar estructura: [algún contenido] children = comp.xpath("./*") # Todos los hijos directos - if len(children) >= 3: - # Verificar patrón: primer hijo es Token "[", último es Token "]" - first_child = children[0] - last_child = children[-1] - first_is_open_bracket = ( - etree.QName(first_child.tag).localname == "Token" - and first_child.get("Text") == "[" - ) - last_is_close_bracket = ( - etree.QName(last_child.tag).localname == "Token" - and last_child.get("Text") == "]" - ) + # Buscar los tokens [ y ] en cualquier posición + bracket_start_idx = -1 + bracket_end_idx = -1 - if first_is_open_bracket and last_is_close_bracket: - # Hay acceso de array - procesar los elementos entre los corchetes - indices_parts = [] + for idx, child in enumerate(children): + child_tag = etree.QName(child.tag).localname + if child_tag == "Token": + text = child.get("Text") + if text == "[" and bracket_start_idx == -1: + bracket_start_idx = idx + elif text == "]" and bracket_start_idx != -1: + bracket_end_idx = idx + break - # Mark the bracket tokens and middle elements as processed - first_uid = first_child.get("UId") - last_uid = last_child.get("UId") - if first_uid: - processed_elements.add(first_uid) - if last_uid: - processed_elements.add(last_uid) + if bracket_start_idx != -1 and bracket_end_idx != -1: + # Hay acceso de array - procesar los elementos entre los corchetes + indices_parts = [] - for middle_child in children[ - 1:-1 - ]: # Todo excepto primer y último hijo - middle_uid = middle_child.get("UId") - if middle_uid: - processed_elements.add(middle_uid) + # Mark the bracket tokens and middle elements as processed + start_token = children[bracket_start_idx] + end_token = children[bracket_end_idx] + start_uid = start_token.get("UId") + end_uid = end_token.get("UId") + if start_uid: + processed_elements.add(start_uid) + if end_uid: + processed_elements.add(end_uid) - child_tag = etree.QName(middle_child.tag).localname - if child_tag == "Access": - # Procesar el Access para obtener el índice - scope = middle_child.get("Scope") - if scope == "LiteralConstant": - # Buscar el valor de la constante - tanto con namespace como sin namespace + for middle_idx in range( + bracket_start_idx + 1, bracket_end_idx + ): + middle_child = children[middle_idx] + middle_uid = middle_child.get("UId") + if middle_uid: + processed_elements.add(middle_uid) + + child_tag = etree.QName(middle_child.tag).localname + if child_tag == "Access": + # Procesar el Access para obtener el índice + scope = middle_child.get("Scope") + if scope == "LiteralConstant": + # Buscar el valor de la constante - tanto con namespace como sin namespace + constant_elem = middle_child.xpath( + "./st:Constant", namespaces=ns + ) + if not constant_elem: constant_elem = middle_child.xpath( - "./st:Constant", namespaces=ns + "./Constant" ) - if not constant_elem: - constant_elem = middle_child.xpath( - "./Constant" - ) - if constant_elem: - # Buscar ConstantValue tanto con namespace como sin namespace + if constant_elem: + # Buscar ConstantValue tanto con namespace como sin namespace + val_nodes = constant_elem[0].xpath( + "./st:ConstantValue", namespaces=ns + ) + if not val_nodes: val_nodes = constant_elem[0].xpath( - "./st:ConstantValue", namespaces=ns + "./ConstantValue" ) - if not val_nodes: - val_nodes = constant_elem[0].xpath( - "./ConstantValue" - ) - if val_nodes and val_nodes[0].text: - indices_parts.append( - val_nodes[0].text.strip() - ) - else: - # Para otros tipos de acceso, procesar manualmente en lugar de recursión - if ( - middle_child.get("Scope") - == "LocalVariable" - ): - # Procesar LocalVariable manualmente + if val_nodes and val_nodes[0].text: + indices_parts.append( + val_nodes[0].text.strip() + ) + else: + # Para otros tipos de acceso, procesar manualmente en lugar de recursión + if middle_child.get("Scope") == "LocalVariable": + # Procesar LocalVariable manualmente + symbol_elem = middle_child.xpath( + "./st:Symbol", namespaces=ns + ) + if not symbol_elem: symbol_elem = middle_child.xpath( - "./st:Symbol", namespaces=ns + "./Symbol" ) - if not symbol_elem: - symbol_elem = middle_child.xpath( - "./Symbol" - ) - if symbol_elem: + if symbol_elem: + components = symbol_elem[0].xpath( + "./st:Component", namespaces=ns + ) + if not components: components = symbol_elem[0].xpath( - "./st:Component", namespaces=ns + "./Component" ) - if not components: - components = symbol_elem[ - 0 - ].xpath("./Component") - # Construir la variable manualmente - var_parts = [] - for i, comp in enumerate( - components - ): - name = comp.get( - "Name", "_ERR_COMP_" - ) - if i == 0: - var_parts.append( - f"#{name}" - ) # Primer componente con # - else: - var_parts.append( - f".{name}" - ) # Componentes subsecuentes con . - - idx_result = "".join(var_parts) - if idx_result: - indices_parts.append(idx_result) + # Construir la variable manualmente + var_parts = [] + for i, comp in enumerate(components): + name = comp.get( + "Name", "_ERR_COMP_" + ) + if i == 0: + var_parts.append( + f"#{name}" + ) # Primer componente con # else: - indices_parts.append( - "/*_ERR_EMPTY_VAR_*/" - ) + var_parts.append( + f".{name}" + ) # Componentes subsecuentes con . + + idx_result = "".join(var_parts) + if idx_result: + indices_parts.append(idx_result) else: indices_parts.append( - "/*_ERR_NO_SYMBOL_*/" + "/*_ERR_EMPTY_VAR_*/" ) else: - # Para otros scopes, usar recursión como fallback - idx_result = ( - reconstruct_scl_from_tokens( - middle_child - ) + indices_parts.append( + "/*_ERR_NO_SYMBOL_*/" ) - if idx_result and idx_result.strip(): - indices_parts.append( - idx_result.strip() - ) - else: - indices_parts.append( - "/*_ERR_RECURSIVE_EMPTY_*/" - ) - elif child_tag == "Token": - # Token de separación (como ",") - token_text = middle_child.get("Text", "") - if token_text.strip(): - indices_parts.append(token_text) + else: + # Para otros scopes, usar recursión como fallback + idx_result = reconstruct_scl_from_tokens( + middle_child + ) + if idx_result and idx_result.strip(): + indices_parts.append(idx_result.strip()) + else: + indices_parts.append( + "/*_ERR_RECURSIVE_EMPTY_*/" + ) + elif child_tag == "Token": + # Token de separación (como ",") + token_text = middle_child.get("Text", "") + if token_text.strip(): + indices_parts.append(token_text) - if indices_parts: - symbol_text_parts.append( - f"[{','.join(indices_parts)}]" - ) + if indices_parts: + symbol_text_parts.append(f"[{','.join(indices_parts)}]") else: # No es acceso de array, buscar Access anidados de la forma tradicional index_access_nodes = comp.xpath(