Add XML parser script and example SCL function block for HMI interlock
- Created a new script to convert XML data to SCL format. - Added a readme file with instructions for generating SCL from XML. - Introduced an example SCL function block "FB_HMI_Interlock" with logic for managing HMI requests.
This commit is contained in:
parent
c3088e9957
commit
75cdf080f5
|
@ -1,56 +0,0 @@
|
||||||
"""
|
|
||||||
Test script para verificar la función is_block_exportable
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# Mock class para simular un bloque de TIA Portal
|
|
||||||
class MockBlock:
|
|
||||||
def __init__(self, programming_language):
|
|
||||||
self.programming_language = programming_language
|
|
||||||
|
|
||||||
def get_property(self, name):
|
|
||||||
if name == "ProgrammingLanguage":
|
|
||||||
return self.programming_language
|
|
||||||
raise Exception(f"Property {name} not found")
|
|
||||||
|
|
||||||
|
|
||||||
# Importar la función desde x1.py
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(__file__))
|
|
||||||
from x1 import is_block_exportable
|
|
||||||
|
|
||||||
# Testear diferentes tipos de bloques
|
|
||||||
test_cases = [
|
|
||||||
("LAD", True, "LAD blocks should be exportable"),
|
|
||||||
("FBD", True, "FBD blocks should be exportable"),
|
|
||||||
("STL", True, "STL blocks should be exportable"),
|
|
||||||
("SCL", True, "SCL blocks should be exportable"),
|
|
||||||
("ProDiag_OB", False, "ProDiag_OB blocks should not be exportable"),
|
|
||||||
("ProDiag", False, "ProDiag blocks should not be exportable"),
|
|
||||||
("GRAPH", False, "GRAPH blocks should not be exportable"),
|
|
||||||
]
|
|
||||||
|
|
||||||
print("=== Test de validación de bloques ===")
|
|
||||||
for prog_lang, expected_exportable, description in test_cases:
|
|
||||||
block = MockBlock(prog_lang)
|
|
||||||
is_exportable, detected_lang, reason = is_block_exportable(block)
|
|
||||||
|
|
||||||
status = "✓ PASS" if is_exportable == expected_exportable else "✗ FAIL"
|
|
||||||
print(f"{status} - {description}")
|
|
||||||
print(f" Lenguaje: {detected_lang}, Exportable: {is_exportable}, Razón: {reason}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
# Test con bloque que genera excepción
|
|
||||||
class MockBlockError:
|
|
||||||
def get_property(self, name):
|
|
||||||
raise Exception("Cannot access property")
|
|
||||||
|
|
||||||
|
|
||||||
print("=== Test de manejo de errores ===")
|
|
||||||
error_block = MockBlockError()
|
|
||||||
is_exportable, detected_lang, reason = is_block_exportable(error_block)
|
|
||||||
print(f"Bloque con error - Exportable: {is_exportable}, Lenguaje: {detected_lang}")
|
|
||||||
print(f"Razón: {reason}")
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
Para generar un resultado directo desde un xml a 1 scl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
C:/Users/migue/miniconda3/envs/tia_scripting/python.exe "d:/Proyectos/Scripts/ParamManagerScripts/backend/script_groups/XML Parser to SCL/x0_main.py" --plc-dir "D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia" --source-xml "D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia\PLC_TL27_Q1\ProgramBlocks_XML\FB HMI Interlock.xml" --dest-scl "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\XML Parser to SCL\.example\FB_HMI_Interlock.scl"
|
||||||
|
```
|
|
@ -0,0 +1,80 @@
|
||||||
|
// FB10
|
||||||
|
// Block Type: FB
|
||||||
|
// Block Name (Original): FB HMI Interlock
|
||||||
|
// Block Number: 10
|
||||||
|
// Original Network Languages: SCL
|
||||||
|
|
||||||
|
FUNCTION_BLOCK "FB_HMI_Interlock"
|
||||||
|
{ S7_Optimized_Access := 'TRUE' }
|
||||||
|
VERSION : 0.1
|
||||||
|
|
||||||
|
VAR_STAT
|
||||||
|
Status : STRUCT
|
||||||
|
HMI_InUse : Bool;
|
||||||
|
HMI_CanBeUsedBy : Int;
|
||||||
|
HMI_Max_Count : Int;
|
||||||
|
i_Request : Array[0..7] of Bool;
|
||||||
|
o_HMI_CanBeUsedBy : Array[0..7] of Bool;
|
||||||
|
END_STRUCT;
|
||||||
|
|
||||||
|
AUX : STRUCT
|
||||||
|
Cycle_Count : Int;
|
||||||
|
Time_OUT : STRUCT
|
||||||
|
PT : Time;
|
||||||
|
ET : Time;
|
||||||
|
IN : Bool;
|
||||||
|
Q : Bool;
|
||||||
|
END_STRUCT;
|
||||||
|
|
||||||
|
END_STRUCT;
|
||||||
|
|
||||||
|
END_VAR
|
||||||
|
|
||||||
|
VAR_TEMP
|
||||||
|
i : Int;
|
||||||
|
END_VAR
|
||||||
|
|
||||||
|
CONSTANT
|
||||||
|
HMI_REQUEST_TRANSPORT : Int := 1;
|
||||||
|
HMI_REQUEST_ELECTRIC_GUIDES : Int := 2;
|
||||||
|
END_CONSTANT
|
||||||
|
|
||||||
|
#_5s : Bool; // Auto-generated temporary
|
||||||
|
#AUX : Bool; // Auto-generated temporary
|
||||||
|
#Status : Bool; // Auto-generated temporary
|
||||||
|
#i : Bool; // Auto-generated temporary
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// Network 1: Number of logics that can use the HMI at the same time (Original Language: SCL)
|
||||||
|
|
||||||
|
#Status.HMI_Max_Count := 2; // Only Transport & Electric Guides
|
||||||
|
// Timeout after the HMI is not more requested by the area
|
||||||
|
#AUX.Time_OUT(IN :=NOT (#Status.i_Request[#Status.HMI_CanBeUsedBy]), PT :=T#5s);
|
||||||
|
IF #AUX.Time_OUT.Q THEN
|
||||||
|
// Cancel the actual use after timeout
|
||||||
|
#Status.HMI_InUse := FALSE;
|
||||||
|
FOR #i:=0 TO 7 DO
|
||||||
|
#Status.o_HMI_CanBeUsedBy[] := FALSE;
|
||||||
|
END_FOR;
|
||||||
|
END_IF;
|
||||||
|
IF NOT #Status.HMI_InUse THEN
|
||||||
|
#AUX.Cycle_Count := #AUX.Cycle_Count + "DINT_TO_INT"("DB ScanTime_OB1".SCAN_TIME_ms);
|
||||||
|
IF #AUX.Cycle_Count> 1000 THEN
|
||||||
|
#Status.HMI_CanBeUsedBy := (#Status.HMI_CanBeUsedBy MOD #Status.HMI_Max_Count) + 1;
|
||||||
|
#AUX.Cycle_Count := #AUX.Cycle_Count - 1000;
|
||||||
|
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.HMI_InUse := TRUE;
|
||||||
|
END_IF;
|
||||||
|
END_IF;
|
||||||
|
// Every cycle we reset all request. When some area need the HMI need to
|
||||||
|
// request on every cycle until is granted.
|
||||||
|
//
|
||||||
|
FOR #i:=0 TO 7 DO
|
||||||
|
#Status.i_Request[] := FALSE;
|
||||||
|
END_FOR;
|
||||||
|
#Status.o_HMI_CanBeUsedBy[0] := NOT #Status.HMI_InUse;
|
||||||
|
|
||||||
|
END_FUNCTION_BLOCK
|
|
@ -5,7 +5,7 @@
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../../../../Trabajo/VM/45 - HENKEL - VM Auto Changeover/ExportTia/PLC_TL25_Q1"
|
"path": "../../../../../../Trabajo/VM/45 - HENKEL - VM Auto Changeover/ExportTia"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +0,0 @@
|
||||||
--- Log de Ejecución: x1_to_json.py ---
|
|
||||||
Grupo: XML Parser to SCL
|
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
|
||||||
Inicio: 2025-05-03 20:08:18
|
|
||||||
Fin: 2025-05-03 20:08:22
|
|
||||||
Duración: 0:00:03.850097
|
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
|
||||||
Por favor, selecciona el archivo XML de entrada...
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
No se seleccionó ningún archivo. Saliendo.
|
|
||||||
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -1,34 +0,0 @@
|
||||||
--- Log de Ejecución: x4_cross_reference.py ---
|
|
||||||
Grupo: XML Parser to SCL
|
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
|
||||||
Inicio: 2025-05-05 16:34:28
|
|
||||||
Fin: 2025-05-05 16:34:30
|
|
||||||
Duración: 0:00:01.642768
|
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
|
||||||
(x4 - Standalone) Ejecutando generación de referencias cruzadas...
|
|
||||||
--- Iniciando Generación de Referencias Cruzadas y Fuentes MD (x4) ---
|
|
||||||
Buscando archivos JSON procesados en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC
|
|
||||||
Directorio de salida XRef: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\xref_output
|
|
||||||
Directorio fuente SCL/MD (para análisis DB/Tag y copia): scl_output
|
|
||||||
Subdirectorio fuentes MD para XRef: source
|
|
||||||
Copiando y preparando archivos fuente para Obsidian en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\xref_output\source
|
|
||||||
Archivos fuente preparados: 378 SCL convertidos, 30 MD copiados.
|
|
||||||
Buscando archivos XML XRef en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\ProgramBlocks_CR
|
|
||||||
Archivos JSON encontrados: 342
|
|
||||||
Datos cargados para 342 bloques.
|
|
||||||
Mapa InstanciaDB -> FB creado con 0 entradas.
|
|
||||||
Datos cargados para 342 bloques (1793 PLC Tags globales).
|
|
||||||
Construyendo grafo de llamadas desde archivos XML XRef...
|
|
||||||
Archivos XML XRef encontrados: 138
|
|
||||||
Generando ÁRBOL XRef de llamadas en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\xref_output\xref_calls_tree.md
|
|
||||||
Generando RESUMEN XRef de uso de DBs en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\xref_output\xref_db_usage_summary.md
|
|
||||||
Generando RESUMEN XRef de uso de PLC Tags en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\xref_output\xref_plc_tags_summary.md
|
|
||||||
--- Generación de Referencias Cruzadas y Fuentes MD (x4) Completada ---
|
|
||||||
|
|
||||||
(x4 - Standalone) Proceso completado exitosamente.
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
Ninguno
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -1,34 +0,0 @@
|
||||||
--- Log de Ejecución: x7_clear.py ---
|
|
||||||
Grupo: XML Parser to SCL
|
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\13 - E5.007560 - Modifica O&U - SAE235\Reporte\ExportTia
|
|
||||||
Inicio: 2025-06-20 18:53:46
|
|
||||||
Fin: 2025-06-20 18:53:47
|
|
||||||
Duración: 0:00:01.131243
|
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
|
||||||
INFO: format_variable_name importado desde generators.generator_utils
|
|
||||||
|
|
||||||
=== Limpiando PLC: PLC ===
|
|
||||||
- Eliminado directorio de parsing: PLC\PlcDataTypes\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\PlcDataTypes_CR\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\PlcTags\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\PlcTags\IO Not in Hardware\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\ProgramBlocks_CR\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\ProgramBlocks_CR\40_10_GNS_PLCdia Main\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\ProgramBlocks_XML\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\ProgramBlocks_XML\40_10_GNS_PLCdia Main\parsing
|
|
||||||
- Eliminado directorio de parsing: PLC\SystemBlocks_CR\parsing
|
|
||||||
- Eliminado directorio 'scl_output': PLC\scl_output
|
|
||||||
- Eliminado directorio 'xref_output': PLC\xref_output
|
|
||||||
- Eliminado archivo agregado: PLC\full_project_representation.md
|
|
||||||
- Eliminado log: log_PLC.txt
|
|
||||||
|
|
||||||
--- Resumen de limpieza ---
|
|
||||||
Directorios eliminados: 11
|
|
||||||
Archivos eliminados: 2
|
|
||||||
Limpieza completada.
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
Ninguno
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -124,9 +124,15 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
has_quotes = has_quotes_comp or has_quotes_access
|
has_quotes = has_quotes_comp or has_quotes_access
|
||||||
is_temp = name.startswith("#")
|
is_temp = name.startswith("#")
|
||||||
|
|
||||||
|
# Para variables locales, usar prefijo # en lugar de comillas
|
||||||
|
if scope == "LocalVariable" and i == 0 and not is_temp:
|
||||||
|
symbol_text_parts.append(f"#{name}")
|
||||||
# Apply quotes based on HasQuotes or if it's the first component and not temp
|
# Apply quotes based on HasQuotes or if it's the first component and not temp
|
||||||
if has_quotes or (
|
elif has_quotes or (
|
||||||
i == 0 and not is_temp and '"' not in name
|
i == 0
|
||||||
|
and not is_temp
|
||||||
|
and '"' not in name
|
||||||
|
and scope != "LocalVariable"
|
||||||
): # Avoid double quotes
|
): # Avoid double quotes
|
||||||
symbol_text_parts.append(f'"{name}"')
|
symbol_text_parts.append(f'"{name}"')
|
||||||
else:
|
else:
|
||||||
|
@ -198,12 +204,72 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
val_nodes[0].text.strip()
|
val_nodes[0].text.strip()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Para otros tipos de acceso, usar la función recursiva
|
# Para otros tipos de acceso, procesar manualmente en lugar de recursión
|
||||||
idx_result = reconstruct_scl_from_tokens(
|
if (
|
||||||
middle_child
|
middle_child.get("Scope")
|
||||||
)
|
== "LocalVariable"
|
||||||
if idx_result and idx_result.strip():
|
):
|
||||||
indices_parts.append(idx_result.strip())
|
# Procesar LocalVariable manualmente
|
||||||
|
symbol_elem = middle_child.xpath(
|
||||||
|
"./st:Symbol", namespaces=ns
|
||||||
|
)
|
||||||
|
if not symbol_elem:
|
||||||
|
symbol_elem = middle_child.xpath(
|
||||||
|
"./Symbol"
|
||||||
|
)
|
||||||
|
|
||||||
|
if symbol_elem:
|
||||||
|
components = symbol_elem[0].xpath(
|
||||||
|
"./st:Component", namespaces=ns
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
indices_parts.append(
|
||||||
|
"/*_ERR_EMPTY_VAR_*/"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
indices_parts.append(
|
||||||
|
"/*_ERR_NO_SYMBOL_*/"
|
||||||
|
)
|
||||||
|
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":
|
elif child_tag == "Token":
|
||||||
# Token de separación (como ",")
|
# Token de separación (como ",")
|
||||||
token_text = middle_child.get("Text", "")
|
token_text = middle_child.get("Text", "")
|
||||||
|
@ -259,6 +325,28 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
else:
|
else:
|
||||||
access_str = f"/*_ERR_NO_SYMBOL_IN_{scope}_*/"
|
access_str = f"/*_ERR_NO_SYMBOL_IN_{scope}_*/"
|
||||||
|
|
||||||
|
# --- Constantes Tipadas (TypedConstant) ---
|
||||||
|
elif scope == "TypedConstant":
|
||||||
|
constant_elem = elem.xpath("./st:Constant", namespaces=ns)
|
||||||
|
if not constant_elem:
|
||||||
|
constant_elem = elem.xpath("./Constant")
|
||||||
|
|
||||||
|
if constant_elem:
|
||||||
|
const_value_elem = constant_elem[0].xpath(
|
||||||
|
"./st:ConstantValue", namespaces=ns
|
||||||
|
)
|
||||||
|
if not const_value_elem:
|
||||||
|
const_value_elem = constant_elem[0].xpath("./ConstantValue")
|
||||||
|
|
||||||
|
if const_value_elem and const_value_elem[0].text:
|
||||||
|
const_val = const_value_elem[0].text.strip()
|
||||||
|
# Para constantes tipadas, usar el valor directamente (ya incluye el tipo como T#5s)
|
||||||
|
access_str = const_val
|
||||||
|
else:
|
||||||
|
access_str = "/*_ERR_NO_CONST_VALUE_*/"
|
||||||
|
else:
|
||||||
|
access_str = "/*_ERR_NO_CONST_ELEM_*/"
|
||||||
|
|
||||||
# --- Constantes Literales ---
|
# --- Constantes Literales ---
|
||||||
elif scope == "LiteralConstant":
|
elif scope == "LiteralConstant":
|
||||||
# Buscar nodos Constant tanto con namespace st: como sin namespace
|
# Buscar nodos Constant tanto con namespace st: como sin namespace
|
||||||
|
@ -406,6 +494,7 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
|
|
||||||
# --- Llamadas a Funciones/Bloques (Scope=Call) ---
|
# --- Llamadas a Funciones/Bloques (Scope=Call) ---
|
||||||
elif scope == "Call":
|
elif scope == "Call":
|
||||||
|
# Primero intentar con CallInfo (estructura tradicional)
|
||||||
call_info_node = elem.xpath("./st:CallInfo", namespaces=ns)
|
call_info_node = elem.xpath("./st:CallInfo", namespaces=ns)
|
||||||
if call_info_node:
|
if call_info_node:
|
||||||
ci = call_info_node[0]
|
ci = call_info_node[0]
|
||||||
|
@ -445,8 +534,56 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
access_str = f'"{call_name}"({", ".join(param_parts)})'
|
access_str = f'"{call_name}"({", ".join(param_parts)})'
|
||||||
else: # Otros tipos de llamada
|
else: # Otros tipos de llamada
|
||||||
access_str = f'"{call_name}"({", ".join(param_parts)}) (* Tipo: {call_type} *)'
|
access_str = f'"{call_name}"({", ".join(param_parts)}) (* Tipo: {call_type} *)'
|
||||||
|
|
||||||
|
# Si no hay CallInfo, intentar con Instruction (estructura de SCL nativo)
|
||||||
else:
|
else:
|
||||||
access_str = "/*_ERR_NO_CALLINFO_*/"
|
instruction_node = elem.xpath("./st:Instruction", namespaces=ns)
|
||||||
|
if instruction_node:
|
||||||
|
instr = instruction_node[0]
|
||||||
|
instr_name = instr.get(
|
||||||
|
"Name"
|
||||||
|
) # Puede ser None para llamadas sin nombre específico
|
||||||
|
|
||||||
|
# Parámetros con nombre y sin nombre
|
||||||
|
named_params = instr.xpath("./st:Parameter", namespaces=ns)
|
||||||
|
nameless_params = instr.xpath(
|
||||||
|
"./st:NamelessParameter", namespaces=ns
|
||||||
|
)
|
||||||
|
|
||||||
|
param_parts = []
|
||||||
|
|
||||||
|
# Procesar parámetros con nombre
|
||||||
|
for p in named_params:
|
||||||
|
p_name = p.get("Name", "_ERR_PARAMNAME_")
|
||||||
|
# Reconstruir el valor del parámetro
|
||||||
|
p_value_scl = reconstruct_scl_from_tokens(p)
|
||||||
|
p_value_scl = p_value_scl.replace("\n", "").strip()
|
||||||
|
|
||||||
|
# Si el valor ya contiene ":=", no lo duplicar
|
||||||
|
if p_value_scl.startswith(":="):
|
||||||
|
param_parts.append(f"{p_name} {p_value_scl}")
|
||||||
|
elif p_value_scl:
|
||||||
|
param_parts.append(f"{p_name} := {p_value_scl}")
|
||||||
|
else:
|
||||||
|
param_parts.append(f"{p_name} := /*_ERR_PARAM_VALUE_*/")
|
||||||
|
|
||||||
|
# Procesar parámetros sin nombre
|
||||||
|
for p in nameless_params:
|
||||||
|
p_value_scl = reconstruct_scl_from_tokens(p)
|
||||||
|
p_value_scl = p_value_scl.replace("\n", "").strip()
|
||||||
|
if p_value_scl:
|
||||||
|
param_parts.append(p_value_scl)
|
||||||
|
else:
|
||||||
|
param_parts.append("/*_ERR_NAMELESS_PARAM_*/")
|
||||||
|
|
||||||
|
# Construir la llamada
|
||||||
|
if instr_name:
|
||||||
|
access_str = f'"{instr_name}"({", ".join(param_parts)})'
|
||||||
|
else:
|
||||||
|
# Llamada sin nombre específico, probablemente un FB instance call
|
||||||
|
access_str = f'({", ".join(param_parts)})'
|
||||||
|
else:
|
||||||
|
access_str = "/*_ERR_NO_CALLINFO_*/"
|
||||||
|
|
||||||
# Añadir más scopes si son necesarios (e.g., Address, Label, Reference)
|
# Añadir más scopes si son necesarios (e.g., Address, Label, Reference)
|
||||||
|
|
||||||
|
|
|
@ -516,11 +516,18 @@ def parse_interface_members(member_elements):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Procesar miembros anidados (Struct)
|
# Procesar miembros anidados (Struct)
|
||||||
nested_sections = member.xpath(
|
# Primero buscar miembros directos (estructura común)
|
||||||
"./iface:Sections/iface:Section[@Name='None']/iface:Member", namespaces=ns
|
direct_nested_members = member.xpath("./iface:Member", namespaces=ns)
|
||||||
)
|
if direct_nested_members:
|
||||||
if nested_sections:
|
member_info["children"] = parse_interface_members(direct_nested_members)
|
||||||
member_info["children"] = parse_interface_members(nested_sections)
|
else:
|
||||||
|
# Si no hay miembros directos, buscar en Sections (para tipos como TON_TIME)
|
||||||
|
nested_sections = member.xpath(
|
||||||
|
"./iface:Sections/iface:Section[@Name='None']/iface:Member",
|
||||||
|
namespaces=ns,
|
||||||
|
)
|
||||||
|
if nested_sections:
|
||||||
|
member_info["children"] = parse_interface_members(nested_sections)
|
||||||
|
|
||||||
# Procesar valores iniciales de Array
|
# Procesar valores iniciales de Array
|
||||||
if isinstance(member_dtype, str) and member_dtype.lower().startswith("array["):
|
if isinstance(member_dtype, str) and member_dtype.lower().startswith("array["):
|
||||||
|
|
79417
data/log.txt
79417
data/log.txt
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue