fix: Correct array index access in FB_HMI_Interlock and enhance debugging for array parsing

This commit is contained in:
Miguel 2025-08-24 10:10:14 +02:00
parent 75cdf080f5
commit 24ae05cc75
3 changed files with 329 additions and 118 deletions

View File

@ -54,7 +54,7 @@ BEGIN
// Cancel the actual use after timeout // Cancel the actual use after timeout
#Status.HMI_InUse := FALSE; #Status.HMI_InUse := FALSE;
FOR #i:=0 TO 7 DO FOR #i:=0 TO 7 DO
#Status.o_HMI_CanBeUsedBy[] := FALSE; #Status.o_HMI_CanBeUsedBy[#i] := FALSE;
END_FOR; END_FOR;
END_IF; END_IF;
IF NOT #Status.HMI_InUse THEN IF NOT #Status.HMI_InUse THEN
@ -65,7 +65,7 @@ BEGIN
END_IF; END_IF;
IF #Status.HMI_CanBeUsedBy > 0 AND #Status.i_Request[#Status.HMI_CanBeUsedBy] THEN 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 // 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; #Status.HMI_InUse := TRUE;
END_IF; END_IF;
END_IF; END_IF;
@ -73,7 +73,7 @@ BEGIN
// request on every cycle until is granted. // request on every cycle until is granted.
// //
FOR #i:=0 TO 7 DO FOR #i:=0 TO 7 DO
#Status.i_Request[] := FALSE; #Status.i_Request[#i] := FALSE;
END_FOR; END_FOR;
#Status.o_HMI_CanBeUsedBy[0] := NOT #Status.HMI_InUse; #Status.o_HMI_CanBeUsedBy[0] := NOT #Status.HMI_InUse;

View File

@ -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()

View File

@ -140,146 +140,139 @@ def reconstruct_scl_from_tokens(st_node):
# --- Array Index Access --- # --- Array Index Access ---
# Verificar si este componente tiene hijos que indican acceso de array # Verificar si este componente tiene hijos que indican acceso de array
# Buscar estructura: <Token Text="["/> <Access.../> <Token Text="]"/> # Buscar estructura: [algún contenido] <Token Text="["/> <Access.../> <Token Text="]"/>
children = comp.xpath("./*") # Todos los hijos directos children = comp.xpath("./*") # Todos los hijos directos
if len(children) >= 3: # Buscar los tokens [ y ] en cualquier posición
# Verificar patrón: primer hijo es Token "[", último es Token "]" bracket_start_idx = -1
first_child = children[0] bracket_end_idx = -1
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") == "]"
)
if first_is_open_bracket and last_is_close_bracket: for idx, child in enumerate(children):
# Hay acceso de array - procesar los elementos entre los corchetes child_tag = etree.QName(child.tag).localname
indices_parts = [] 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 if bracket_start_idx != -1 and bracket_end_idx != -1:
first_uid = first_child.get("UId") # Hay acceso de array - procesar los elementos entre los corchetes
last_uid = last_child.get("UId") indices_parts = []
if first_uid:
processed_elements.add(first_uid)
if last_uid:
processed_elements.add(last_uid)
for middle_child in children[ # Mark the bracket tokens and middle elements as processed
1:-1 start_token = children[bracket_start_idx]
]: # Todo excepto primer y último hijo end_token = children[bracket_end_idx]
middle_uid = middle_child.get("UId") start_uid = start_token.get("UId")
if middle_uid: end_uid = end_token.get("UId")
processed_elements.add(middle_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 for middle_idx in range(
if child_tag == "Access": bracket_start_idx + 1, bracket_end_idx
# Procesar el Access para obtener el índice ):
scope = middle_child.get("Scope") middle_child = children[middle_idx]
if scope == "LiteralConstant": middle_uid = middle_child.get("UId")
# Buscar el valor de la constante - tanto con namespace como sin namespace 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( constant_elem = middle_child.xpath(
"./st:Constant", namespaces=ns "./Constant"
) )
if not constant_elem:
constant_elem = middle_child.xpath(
"./Constant"
)
if constant_elem: if constant_elem:
# Buscar ConstantValue tanto con namespace como sin namespace # 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( 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: if val_nodes and val_nodes[0].text:
indices_parts.append( indices_parts.append(
val_nodes[0].text.strip() val_nodes[0].text.strip()
) )
else: else:
# Para otros tipos de acceso, procesar manualmente en lugar de recursión # Para otros tipos de acceso, procesar manualmente en lugar de recursión
if ( if middle_child.get("Scope") == "LocalVariable":
middle_child.get("Scope") # Procesar LocalVariable manualmente
== "LocalVariable" symbol_elem = middle_child.xpath(
): "./st:Symbol", namespaces=ns
# Procesar LocalVariable manualmente )
if not symbol_elem:
symbol_elem = middle_child.xpath( 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( components = symbol_elem[0].xpath(
"./st:Component", namespaces=ns "./Component"
) )
if not components:
components = symbol_elem[
0
].xpath("./Component")
# Construir la variable manualmente # Construir la variable manualmente
var_parts = [] var_parts = []
for i, comp in enumerate( for i, comp in enumerate(components):
components name = comp.get(
): "Name", "_ERR_COMP_"
name = comp.get( )
"Name", "_ERR_COMP_" if i == 0:
) var_parts.append(
if i == 0: f"#{name}"
var_parts.append( ) # Primer componente con #
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)
else: else:
indices_parts.append( var_parts.append(
"/*_ERR_EMPTY_VAR_*/" f".{name}"
) ) # Componentes subsecuentes con .
idx_result = "".join(var_parts)
if idx_result:
indices_parts.append(idx_result)
else: else:
indices_parts.append( indices_parts.append(
"/*_ERR_NO_SYMBOL_*/" "/*_ERR_EMPTY_VAR_*/"
) )
else: else:
# Para otros scopes, usar recursión como fallback indices_parts.append(
idx_result = ( "/*_ERR_NO_SYMBOL_*/"
reconstruct_scl_from_tokens(
middle_child
)
) )
if idx_result and idx_result.strip(): else:
indices_parts.append( # Para otros scopes, usar recursión como fallback
idx_result.strip() idx_result = reconstruct_scl_from_tokens(
) middle_child
else: )
indices_parts.append( if idx_result and idx_result.strip():
"/*_ERR_RECURSIVE_EMPTY_*/" indices_parts.append(idx_result.strip())
) else:
elif child_tag == "Token": indices_parts.append(
# Token de separación (como ",") "/*_ERR_RECURSIVE_EMPTY_*/"
token_text = middle_child.get("Text", "") )
if token_text.strip(): elif child_tag == "Token":
indices_parts.append(token_text) # Token de separación (como ",")
token_text = middle_child.get("Text", "")
if token_text.strip():
indices_parts.append(token_text)
if indices_parts: if indices_parts:
symbol_text_parts.append( symbol_text_parts.append(f"[{','.join(indices_parts)}]")
f"[{','.join(indices_parts)}]"
)
else: else:
# No es acceso de array, buscar Access anidados de la forma tradicional # No es acceso de array, buscar Access anidados de la forma tradicional
index_access_nodes = comp.xpath( index_access_nodes = comp.xpath(