Adicion del procesamiento de OBs
This commit is contained in:
parent
6c604176a1
commit
66c5a076ab
|
@ -5,18 +5,21 @@ import sys
|
||||||
import locale
|
import locale
|
||||||
import glob # <--- Importar glob para buscar archivos
|
import glob # <--- Importar glob para buscar archivos
|
||||||
|
|
||||||
|
|
||||||
# (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior)
|
# (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior)
|
||||||
def get_console_encoding():
|
def get_console_encoding():
|
||||||
"""Obtiene la codificación preferida de la consola, con fallback."""
|
"""Obtiene la codificación preferida de la consola, con fallback."""
|
||||||
try:
|
try:
|
||||||
return locale.getpreferredencoding(False)
|
return locale.getpreferredencoding(False)
|
||||||
except Exception:
|
except Exception:
|
||||||
return 'cp1252'
|
return "cp1252"
|
||||||
|
|
||||||
|
|
||||||
CONSOLE_ENCODING = get_console_encoding()
|
CONSOLE_ENCODING = get_console_encoding()
|
||||||
# Descomenta la siguiente línea si quieres ver la codificación detectada:
|
# Descomenta la siguiente línea si quieres ver la codificación detectada:
|
||||||
# print(f"Detected console encoding: {CONSOLE_ENCODING}")
|
# print(f"Detected console encoding: {CONSOLE_ENCODING}")
|
||||||
|
|
||||||
|
|
||||||
# (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING)
|
# (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING)
|
||||||
def run_script(script_name, xml_arg):
|
def run_script(script_name, xml_arg):
|
||||||
"""Runs a given script with the specified XML file argument."""
|
"""Runs a given script with the specified XML file argument."""
|
||||||
|
@ -24,12 +27,14 @@ def run_script(script_name, xml_arg):
|
||||||
command = [sys.executable, script_path, xml_arg]
|
command = [sys.executable, script_path, xml_arg]
|
||||||
print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
|
print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(command,
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
encoding=CONSOLE_ENCODING,
|
encoding=CONSOLE_ENCODING,
|
||||||
errors='replace') # 'replace' para evitar errores
|
errors="replace",
|
||||||
|
) # 'replace' para evitar errores
|
||||||
|
|
||||||
# Imprimir stdout y stderr
|
# Imprimir stdout y stderr
|
||||||
# Eliminar saltos de línea extra al final si existen
|
# Eliminar saltos de línea extra al final si existen
|
||||||
|
@ -49,8 +54,16 @@ def run_script(script_name, xml_arg):
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Error running {script_name}:")
|
print(f"Error running {script_name}:")
|
||||||
print(f"Return code: {e.returncode}")
|
print(f"Return code: {e.returncode}")
|
||||||
stdout_decoded = e.stdout.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stdout, bytes) else (e.stdout or "").strip()
|
stdout_decoded = (
|
||||||
stderr_decoded = e.stderr.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stderr, bytes) else (e.stderr or "").strip()
|
e.stdout.decode(CONSOLE_ENCODING, errors="replace").strip()
|
||||||
|
if isinstance(e.stdout, bytes)
|
||||||
|
else (e.stdout or "").strip()
|
||||||
|
)
|
||||||
|
stderr_decoded = (
|
||||||
|
e.stderr.decode(CONSOLE_ENCODING, errors="replace").strip()
|
||||||
|
if isinstance(e.stderr, bytes)
|
||||||
|
else (e.stderr or "").strip()
|
||||||
|
)
|
||||||
if stdout_decoded:
|
if stdout_decoded:
|
||||||
print("--- Stdout ---")
|
print("--- Stdout ---")
|
||||||
print(stdout_decoded)
|
print(stdout_decoded)
|
||||||
|
@ -63,12 +76,13 @@ def run_script(script_name, xml_arg):
|
||||||
print(f"An unexpected error occurred while running {script_name}: {e}")
|
print(f"An unexpected error occurred while running {script_name}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO ---
|
# --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO ---
|
||||||
def select_xml_file():
|
def select_xml_file():
|
||||||
"""Busca archivos .xml, los lista y pide al usuario que elija uno."""
|
"""Busca archivos .xml, los lista y pide al usuario que elija uno."""
|
||||||
print("No XML file specified. Searching for XML files in current directory...")
|
print("No XML file specified. Searching for XML files in current directory...")
|
||||||
# Buscar archivos .xml en el directorio actual (.)
|
# Buscar archivos .xml en el directorio actual (.)
|
||||||
xml_files = sorted(glob.glob('*.xml')) # sorted para orden alfabético
|
xml_files = sorted(glob.glob("*.xml")) # sorted para orden alfabético
|
||||||
|
|
||||||
if not xml_files:
|
if not xml_files:
|
||||||
print("Error: No .xml files found in the current directory.")
|
print("Error: No .xml files found in the current directory.")
|
||||||
|
@ -80,7 +94,9 @@ def select_xml_file():
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
choice = input(f"Enter the number of the file to process (1-{len(xml_files)}): ")
|
choice = input(
|
||||||
|
f"Enter the number of the file to process (1-{len(xml_files)}): "
|
||||||
|
)
|
||||||
choice_num = int(choice)
|
choice_num = int(choice)
|
||||||
if 1 <= choice_num <= len(xml_files):
|
if 1 <= choice_num <= len(xml_files):
|
||||||
selected_file = xml_files[choice_num - 1]
|
selected_file = xml_files[choice_num - 1]
|
||||||
|
@ -93,6 +109,8 @@ def select_xml_file():
|
||||||
except EOFError: # Manejar si la entrada se cierra inesperadamente
|
except EOFError: # Manejar si la entrada se cierra inesperadamente
|
||||||
print("\nSelection cancelled.")
|
print("\nSelection cancelled.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# --- FIN NUEVA FUNCIÓN ---
|
# --- FIN NUEVA FUNCIÓN ---
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,18 +129,24 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Verificar si el directorio 'XML Project' existe
|
# Verificar si el directorio 'XML Project' existe
|
||||||
if not os.path.isdir(xml_project_dir):
|
if not os.path.isdir(xml_project_dir):
|
||||||
print(f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio.")
|
print(
|
||||||
print("Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro.")
|
f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio."
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Buscar todos los archivos .xml recursivamente dentro de xml_project_dir
|
# Buscar todos los archivos .xml recursivamente dentro de xml_project_dir
|
||||||
# Usamos os.path.join para construir la ruta de búsqueda correctamente
|
# Usamos os.path.join para construir la ruta de búsqueda correctamente
|
||||||
# y '**/*.xml' para la recursividad con glob
|
# y '**/*.xml' para la recursividad con glob
|
||||||
search_pattern = os.path.join(xml_project_dir, '**', '*.xml')
|
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
||||||
xml_files_found = glob.glob(search_pattern, recursive=True)
|
xml_files_found = glob.glob(search_pattern, recursive=True)
|
||||||
|
|
||||||
if not xml_files_found:
|
if not xml_files_found:
|
||||||
print(f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios.")
|
print(
|
||||||
|
f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios."
|
||||||
|
)
|
||||||
sys.exit(0) # Salir limpiamente si no hay archivos
|
sys.exit(0) # Salir limpiamente si no hay archivos
|
||||||
|
|
||||||
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
|
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
|
||||||
|
@ -141,7 +165,9 @@ if __name__ == "__main__":
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
failed_count = 0
|
failed_count = 0
|
||||||
for xml_filepath in xml_files_found:
|
for xml_filepath in xml_files_found:
|
||||||
print(f"\n--- Iniciando pipeline para: {os.path.relpath(xml_filepath, script_dir)} ---")
|
print(
|
||||||
|
f"\n--- Iniciando pipeline para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
||||||
|
)
|
||||||
|
|
||||||
# Usar la ruta absoluta para evitar problemas si los scripts cambian de directorio
|
# Usar la ruta absoluta para evitar problemas si los scripts cambian de directorio
|
||||||
absolute_xml_filepath = os.path.abspath(xml_filepath)
|
absolute_xml_filepath = os.path.abspath(xml_filepath)
|
||||||
|
@ -150,26 +176,37 @@ if __name__ == "__main__":
|
||||||
# La función run_script ya está definida en tu script x0_main.py
|
# La función run_script ya está definida en tu script x0_main.py
|
||||||
success = True
|
success = True
|
||||||
if not run_script(script1, absolute_xml_filepath):
|
if not run_script(script1, absolute_xml_filepath):
|
||||||
print(f"\nPipeline falló en el script '{script1}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}")
|
print(
|
||||||
|
f"\nPipeline falló en el script '{script1}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
||||||
|
)
|
||||||
success = False
|
success = False
|
||||||
elif not run_script(script2, absolute_xml_filepath):
|
elif not run_script(script2, absolute_xml_filepath):
|
||||||
print(f"\nPipeline falló en el script '{script2}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}")
|
print(
|
||||||
|
f"\nPipeline falló en el script '{script2}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
||||||
|
)
|
||||||
success = False
|
success = False
|
||||||
elif not run_script(script3, absolute_xml_filepath):
|
elif not run_script(script3, absolute_xml_filepath):
|
||||||
print(f"\nPipeline falló en el script '{script3}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}")
|
print(
|
||||||
|
f"\nPipeline falló en el script '{script3}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
||||||
|
)
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print(f"--- Pipeline completado exitosamente para: {os.path.relpath(xml_filepath, script_dir)} ---")
|
print(
|
||||||
|
f"--- Pipeline completado exitosamente para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
||||||
|
)
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
else:
|
else:
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
print(f"--- Pipeline falló para: {os.path.relpath(xml_filepath, script_dir)} ---")
|
print(
|
||||||
|
f"--- Pipeline falló para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
||||||
|
)
|
||||||
|
|
||||||
print("\n--- Resumen Final del Procesamiento ---")
|
print("\n--- Resumen Final del Procesamiento ---")
|
||||||
print(f"Total de archivos XML encontrados: {len(xml_files_found)}")
|
print(f"Total de archivos XML encontrados: {len(xml_files_found)}")
|
||||||
print(f"Archivos procesados exitosamente por el pipeline completo: {processed_count}")
|
print(
|
||||||
|
f"Archivos procesados exitosamente por el pipeline completo: {processed_count}"
|
||||||
|
)
|
||||||
print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}")
|
print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}")
|
||||||
print("---------------------------------------")
|
print("---------------------------------------")
|
||||||
xml_filename = None
|
xml_filename = None
|
||||||
|
|
|
@ -46,6 +46,7 @@ def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"):
|
||||||
print(f"Advertencia: Error extrayendo MultilingualText: {e}")
|
print(f"Advertencia: Error extrayendo MultilingualText: {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def get_symbol_name(symbol_element):
|
def get_symbol_name(symbol_element):
|
||||||
# (Sin cambios respecto a la versión anterior)
|
# (Sin cambios respecto a la versión anterior)
|
||||||
if symbol_element is None:
|
if symbol_element is None:
|
||||||
|
@ -57,6 +58,7 @@ def get_symbol_name(symbol_element):
|
||||||
print(f"Advertencia: Excepción en get_symbol_name: {e}")
|
print(f"Advertencia: Excepción en get_symbol_name: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_access(access_element):
|
def parse_access(access_element):
|
||||||
# (Sin cambios respecto a la versión anterior)
|
# (Sin cambios respecto a la versión anterior)
|
||||||
if access_element is None:
|
if access_element is None:
|
||||||
|
@ -149,6 +151,7 @@ def parse_access(access_element):
|
||||||
return info
|
return info
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
def parse_part(part_element):
|
def parse_part(part_element):
|
||||||
# (Sin cambios respecto a la versión anterior)
|
# (Sin cambios respecto a la versión anterior)
|
||||||
if part_element is None:
|
if part_element is None:
|
||||||
|
@ -184,6 +187,7 @@ def parse_part(part_element):
|
||||||
"negated_pins": negated_pins,
|
"negated_pins": negated_pins,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_call(call_element):
|
def parse_call(call_element):
|
||||||
# (Mantiene la corrección para DB de instancia)
|
# (Mantiene la corrección para DB de instancia)
|
||||||
if call_element is None:
|
if call_element is None:
|
||||||
|
@ -243,8 +247,10 @@ def parse_call(call_element):
|
||||||
call_data["instance_scope"] = instance_scope
|
call_data["instance_scope"] = instance_scope
|
||||||
return call_data
|
return call_data
|
||||||
|
|
||||||
|
|
||||||
# SCL (Structured Text) Parser
|
# SCL (Structured Text) Parser
|
||||||
|
|
||||||
|
|
||||||
def reconstruct_scl_from_tokens(st_node):
|
def reconstruct_scl_from_tokens(st_node):
|
||||||
"""
|
"""
|
||||||
Reconstruye SCL desde <StructuredText>, mejorando el manejo de
|
Reconstruye SCL desde <StructuredText>, mejorando el manejo de
|
||||||
|
@ -263,10 +269,10 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
scl_parts.append(elem.get("Text", ""))
|
scl_parts.append(elem.get("Text", ""))
|
||||||
elif tag == "Blank":
|
elif tag == "Blank":
|
||||||
# Añadir espacios simples, evitar múltiples si ya hay uno antes/después
|
# Añadir espacios simples, evitar múltiples si ya hay uno antes/después
|
||||||
if not scl_parts or not scl_parts[-1].endswith(' '):
|
if not scl_parts or not scl_parts[-1].endswith(" "):
|
||||||
scl_parts.append(" " * int(elem.get("Num", 1)))
|
scl_parts.append(" " * int(elem.get("Num", 1)))
|
||||||
elif int(elem.get("Num", 1)) > 1: # Añadir extras si son más de 1
|
elif int(elem.get("Num", 1)) > 1: # Añadir extras si son más de 1
|
||||||
scl_parts.append(" " * (int(elem.get("Num", 1))-1))
|
scl_parts.append(" " * (int(elem.get("Num", 1)) - 1))
|
||||||
elif tag == "NewLine":
|
elif tag == "NewLine":
|
||||||
# Limpiar espacios antes del salto de línea real
|
# Limpiar espacios antes del salto de línea real
|
||||||
if scl_parts:
|
if scl_parts:
|
||||||
|
@ -276,7 +282,15 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
scope = elem.get("Scope")
|
scope = elem.get("Scope")
|
||||||
access_str = f"/*_ERR_Scope_{scope}_*/" # Fallback más informativo
|
access_str = f"/*_ERR_Scope_{scope}_*/" # Fallback más informativo
|
||||||
|
|
||||||
if scope in ["GlobalVariable", "LocalVariable", "TempVariable", "InOutVariable", "InputVariable", "OutputVariable", "ConstantVariable"]: # Tipos comunes de variables
|
if scope in [
|
||||||
|
"GlobalVariable",
|
||||||
|
"LocalVariable",
|
||||||
|
"TempVariable",
|
||||||
|
"InOutVariable",
|
||||||
|
"InputVariable",
|
||||||
|
"OutputVariable",
|
||||||
|
"ConstantVariable",
|
||||||
|
]: # Tipos comunes de variables
|
||||||
symbol_elem = elem.xpath("./st:Symbol", namespaces=ns)
|
symbol_elem = elem.xpath("./st:Symbol", namespaces=ns)
|
||||||
if symbol_elem:
|
if symbol_elem:
|
||||||
components = symbol_elem[0].xpath("./st:Component", namespaces=ns)
|
components = symbol_elem[0].xpath("./st:Component", namespaces=ns)
|
||||||
|
@ -288,11 +302,18 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
symbol_text_parts.append(".")
|
symbol_text_parts.append(".")
|
||||||
|
|
||||||
# Reconstrucción de comillas (heurística)
|
# Reconstrucción de comillas (heurística)
|
||||||
has_quotes_elem = comp.xpath("../st:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns)
|
has_quotes_elem = comp.xpath(
|
||||||
has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == "true"
|
"../st:BooleanAttribute[@Name='HasQuotes']/text()",
|
||||||
is_temp = name.startswith('#')
|
namespaces=ns,
|
||||||
|
)
|
||||||
|
has_quotes = (
|
||||||
|
has_quotes_elem and has_quotes_elem[0].lower() == "true"
|
||||||
|
)
|
||||||
|
is_temp = name.startswith("#")
|
||||||
|
|
||||||
if has_quotes or (i == 0 and not is_temp): # Comillas si HasQuotes o primer componente (no temp)
|
if has_quotes or (
|
||||||
|
i == 0 and not is_temp
|
||||||
|
): # Comillas si HasQuotes o primer componente (no temp)
|
||||||
symbol_text_parts.append(f'"{name}"')
|
symbol_text_parts.append(f'"{name}"')
|
||||||
else:
|
else:
|
||||||
symbol_text_parts.append(name)
|
symbol_text_parts.append(name)
|
||||||
|
@ -301,7 +322,10 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
index_access = comp.xpath("./st:Access", namespaces=ns)
|
index_access = comp.xpath("./st:Access", namespaces=ns)
|
||||||
if index_access:
|
if index_access:
|
||||||
# Llama recursivamente para obtener el texto de cada índice
|
# Llama recursivamente para obtener el texto de cada índice
|
||||||
indices_text = [reconstruct_scl_from_tokens(idx_node) for idx_node in index_access]
|
indices_text = [
|
||||||
|
reconstruct_scl_from_tokens(idx_node)
|
||||||
|
for idx_node in index_access
|
||||||
|
]
|
||||||
symbol_text_parts.append(f"[{','.join(indices_text)}]")
|
symbol_text_parts.append(f"[{','.join(indices_text)}]")
|
||||||
|
|
||||||
access_str = "".join(symbol_text_parts)
|
access_str = "".join(symbol_text_parts)
|
||||||
|
@ -309,8 +333,12 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
elif scope == "LiteralConstant":
|
elif scope == "LiteralConstant":
|
||||||
constant_elem = elem.xpath("./st:Constant", namespaces=ns)
|
constant_elem = elem.xpath("./st:Constant", namespaces=ns)
|
||||||
if constant_elem:
|
if constant_elem:
|
||||||
val_elem = constant_elem[0].xpath("./st:ConstantValue/text()", namespaces=ns)
|
val_elem = constant_elem[0].xpath(
|
||||||
type_elem = constant_elem[0].xpath("./st:ConstantType/text()", namespaces=ns)
|
"./st:ConstantValue/text()", namespaces=ns
|
||||||
|
)
|
||||||
|
type_elem = constant_elem[0].xpath(
|
||||||
|
"./st:ConstantType/text()", namespaces=ns
|
||||||
|
)
|
||||||
const_type = type_elem[0] if type_elem else ""
|
const_type = type_elem[0] if type_elem else ""
|
||||||
const_val = val_elem[0] if val_elem else "_ERR_CONSTVAL_"
|
const_val = val_elem[0] if val_elem else "_ERR_CONSTVAL_"
|
||||||
|
|
||||||
|
@ -340,29 +368,38 @@ def reconstruct_scl_from_tokens(st_node):
|
||||||
# Unir partes, limpiar espacios extra alrededor de operadores y saltos de línea
|
# Unir partes, limpiar espacios extra alrededor de operadores y saltos de línea
|
||||||
full_scl = "".join(scl_parts)
|
full_scl = "".join(scl_parts)
|
||||||
|
|
||||||
|
|
||||||
# Re-indentar líneas después de IF/THEN, etc. (Simplificado)
|
# Re-indentar líneas después de IF/THEN, etc. (Simplificado)
|
||||||
output_lines = []
|
output_lines = []
|
||||||
indent_level = 0
|
indent_level = 0
|
||||||
for line in full_scl.split('\n'):
|
for line in full_scl.split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line: continue # Saltar líneas vacías
|
if not line:
|
||||||
|
continue # Saltar líneas vacías
|
||||||
|
|
||||||
# Reducir indentación antes de procesar END_IF, ELSE, etc. (simplificado)
|
# Reducir indentación antes de procesar END_IF, ELSE, etc. (simplificado)
|
||||||
if line.startswith(('END_IF', 'END_WHILE', 'END_FOR', 'END_CASE', 'ELSE', 'ELSIF')):
|
if line.startswith(
|
||||||
|
("END_IF", "END_WHILE", "END_FOR", "END_CASE", "ELSE", "ELSIF")
|
||||||
|
):
|
||||||
indent_level = max(0, indent_level - 1)
|
indent_level = max(0, indent_level - 1)
|
||||||
|
|
||||||
output_lines.append(" " * indent_level + line) # Aplicar indentación
|
output_lines.append(" " * indent_level + line) # Aplicar indentación
|
||||||
|
|
||||||
# Aumentar indentación después de IF, WHILE, FOR, CASE, ELSE, ELSIF (simplificado)
|
# Aumentar indentación después de IF, WHILE, FOR, CASE, ELSE, ELSIF (simplificado)
|
||||||
if line.endswith('THEN') or line.endswith('DO') or line.endswith('OF') or line == 'ELSE':
|
if (
|
||||||
|
line.endswith("THEN")
|
||||||
|
or line.endswith("DO")
|
||||||
|
or line.endswith("OF")
|
||||||
|
or line == "ELSE"
|
||||||
|
):
|
||||||
indent_level += 1
|
indent_level += 1
|
||||||
# Nota: Esto no maneja bloques BEGIN/END dentro de SCL
|
# Nota: Esto no maneja bloques BEGIN/END dentro de SCL
|
||||||
|
|
||||||
return "\n".join(output_lines)
|
return "\n".join(output_lines)
|
||||||
|
|
||||||
|
|
||||||
# STL (Statement List) Parser
|
# STL (Statement List) Parser
|
||||||
|
|
||||||
|
|
||||||
def get_access_text(access_element):
|
def get_access_text(access_element):
|
||||||
"""Reconstruye una representación textual simple de un Access en STL."""
|
"""Reconstruye una representación textual simple de un Access en STL."""
|
||||||
if access_element is None:
|
if access_element is None:
|
||||||
|
@ -379,7 +416,9 @@ def get_access_text(access_element):
|
||||||
for comp in components:
|
for comp in components:
|
||||||
name = comp.get("Name", "_ERR_COMP_")
|
name = comp.get("Name", "_ERR_COMP_")
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
has_quotes_elem = comp.xpath("../stl:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns)
|
has_quotes_elem = comp.xpath(
|
||||||
|
"../stl:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns
|
||||||
|
)
|
||||||
has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == "true"
|
has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == "true"
|
||||||
|
|
||||||
# Usar nombre tal cual por ahora
|
# Usar nombre tal cual por ahora
|
||||||
|
@ -400,12 +439,16 @@ def get_access_text(access_element):
|
||||||
if constant_elem:
|
if constant_elem:
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
val_elem = constant_elem[0].xpath("./stl:ConstantValue/text()", namespaces=ns)
|
val_elem = constant_elem[0].xpath("./stl:ConstantValue/text()", namespaces=ns)
|
||||||
type_elem = constant_elem[0].xpath("./stl:ConstantType/text()", namespaces=ns) # Obtener tipo para mejor formato
|
type_elem = constant_elem[0].xpath(
|
||||||
|
"./stl:ConstantType/text()", namespaces=ns
|
||||||
|
) # Obtener tipo para mejor formato
|
||||||
const_type = type_elem[0] if type_elem else ""
|
const_type = type_elem[0] if type_elem else ""
|
||||||
const_val = val_elem[0] if val_elem else "_ERR_CONST_"
|
const_val = val_elem[0] if val_elem else "_ERR_CONST_"
|
||||||
# Añadir prefijo de tipo si es necesario (ej. T# , L#) - Simplificado
|
# Añadir prefijo de tipo si es necesario (ej. T# , L#) - Simplificado
|
||||||
if const_type == "Time": return f"T#{const_val}"
|
if const_type == "Time":
|
||||||
if const_type == "ARef": return f"{const_val}" # No necesita prefijo
|
return f"T#{const_val}"
|
||||||
|
if const_type == "ARef":
|
||||||
|
return f"{const_val}" # No necesita prefijo
|
||||||
# Añadir más tipos si es necesario
|
# Añadir más tipos si es necesario
|
||||||
return const_val # Valor directo para otros tipos
|
return const_val # Valor directo para otros tipos
|
||||||
|
|
||||||
|
@ -436,7 +479,9 @@ def get_access_text(access_element):
|
||||||
|
|
||||||
# Formatear ancho
|
# Formatear ancho
|
||||||
width_map = {"Bit": "X", "Byte": "B", "Word": "W", "Double": "D"}
|
width_map = {"Bit": "X", "Byte": "B", "Word": "W", "Double": "D"}
|
||||||
width_char = width_map.get(width, width[0] if width else "?") # Usa primera letra si no mapeado
|
width_char = width_map.get(
|
||||||
|
width, width[0] if width else "?"
|
||||||
|
) # Usa primera letra si no mapeado
|
||||||
|
|
||||||
return f"{area}{width_char}[{reg},{p_format_offset}]"
|
return f"{area}{width_char}[{reg},{p_format_offset}]"
|
||||||
|
|
||||||
|
@ -453,16 +498,27 @@ def get_access_text(access_element):
|
||||||
bit_in_byte = bit_offset % 8
|
bit_in_byte = bit_offset % 8
|
||||||
# Determinar ancho basado en tipo (simplificación)
|
# Determinar ancho basado en tipo (simplificación)
|
||||||
addr_width = "X" # Default a Bit
|
addr_width = "X" # Default a Bit
|
||||||
if addr_type_str == "Byte": addr_width = "B"
|
if addr_type_str == "Byte":
|
||||||
elif addr_type_str == "Word": addr_width = "W"
|
addr_width = "B"
|
||||||
elif addr_type_str in ["DWord", "DInt"]: addr_width = "D"
|
elif addr_type_str == "Word":
|
||||||
|
addr_width = "W"
|
||||||
|
elif addr_type_str in ["DWord", "DInt"]:
|
||||||
|
addr_width = "D"
|
||||||
# Añadir más tipos si es necesario (Real, etc.)
|
# Añadir más tipos si es necesario (Real, etc.)
|
||||||
|
|
||||||
# Mapear Area para STL estándar
|
# Mapear Area para STL estándar
|
||||||
area_map = { "Input": "I", "Output": "Q", "Memory": "M",
|
area_map = {
|
||||||
"PeripheryInput": "PI", "PeripheryOutput": "PQ",
|
"Input": "I",
|
||||||
"DB": "DB", "DI": "DI", "Local": "L", # L no siempre válido aquí
|
"Output": "Q",
|
||||||
"Timer": "T", "Counter": "C" }
|
"Memory": "M",
|
||||||
|
"PeripheryInput": "PI",
|
||||||
|
"PeripheryOutput": "PQ",
|
||||||
|
"DB": "DB",
|
||||||
|
"DI": "DI",
|
||||||
|
"Local": "L", # L no siempre válido aquí
|
||||||
|
"Timer": "T",
|
||||||
|
"Counter": "C",
|
||||||
|
}
|
||||||
stl_area = area_map.get(area, area)
|
stl_area = area_map.get(area, area)
|
||||||
|
|
||||||
# Manejar DB/DI que necesitan número de bloque
|
# Manejar DB/DI que necesitan número de bloque
|
||||||
|
@ -482,13 +538,19 @@ def get_access_text(access_element):
|
||||||
|
|
||||||
return f"_{scope}_?" # Fallback
|
return f"_{scope}_?" # Fallback
|
||||||
|
|
||||||
|
|
||||||
def get_comment_text(comment_element):
|
def get_comment_text(comment_element):
|
||||||
"""Extrae texto de un LineComment o Comment."""
|
"""Extrae texto de un LineComment o Comment."""
|
||||||
if comment_element is None: return ""
|
if comment_element is None:
|
||||||
|
return ""
|
||||||
# Usar get_multilingual_text si los comentarios son multilingües
|
# Usar get_multilingual_text si los comentarios son multilingües
|
||||||
# Si no, extraer texto directamente
|
# Si no, extraer texto directamente
|
||||||
ml_texts = comment_element.xpath(".//mlt:MultilingualTextItem/mlt:AttributeList/mlt:Text/text()",
|
ml_texts = comment_element.xpath(
|
||||||
namespaces={'mlt': "http://www.siemens.com/automation/Openness/SW/Interface/v5"}) # Asumiendo ns
|
".//mlt:MultilingualTextItem/mlt:AttributeList/mlt:Text/text()",
|
||||||
|
namespaces={
|
||||||
|
"mlt": "http://www.siemens.com/automation/Openness/SW/Interface/v5"
|
||||||
|
},
|
||||||
|
) # Asumiendo ns
|
||||||
if ml_texts:
|
if ml_texts:
|
||||||
# Podrías intentar obtener un idioma específico o simplemente el primero
|
# Podrías intentar obtener un idioma específico o simplemente el primero
|
||||||
return ml_texts[0].strip() if ml_texts else ""
|
return ml_texts[0].strip() if ml_texts else ""
|
||||||
|
@ -497,6 +559,7 @@ def get_comment_text(comment_element):
|
||||||
text_nodes = comment_element.xpath("./text()")
|
text_nodes = comment_element.xpath("./text()")
|
||||||
return "".join(text_nodes).strip()
|
return "".join(text_nodes).strip()
|
||||||
|
|
||||||
|
|
||||||
def reconstruct_stl_from_statementlist(statement_list_node):
|
def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
"""Reconstruye el código STL como una cadena de texto desde <StatementList>."""
|
"""Reconstruye el código STL como una cadena de texto desde <StatementList>."""
|
||||||
if statement_list_node is None:
|
if statement_list_node is None:
|
||||||
|
@ -512,7 +575,9 @@ def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
|
|
||||||
# 1. Comentarios al inicio de la línea (como líneas separadas //)
|
# 1. Comentarios al inicio de la línea (como líneas separadas //)
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
initial_comments = stmt.xpath("child::stl:Comment | child::stl:LineComment", namespaces=ns)
|
initial_comments = stmt.xpath(
|
||||||
|
"child::stl:Comment | child::stl:LineComment", namespaces=ns
|
||||||
|
)
|
||||||
for comm in initial_comments:
|
for comm in initial_comments:
|
||||||
comment_text = get_comment_text(comm)
|
comment_text = get_comment_text(comm)
|
||||||
if comment_text:
|
if comment_text:
|
||||||
|
@ -531,10 +596,15 @@ def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
label_str = f"{label_name_nodes[0]}:"
|
label_str = f"{label_name_nodes[0]}:"
|
||||||
# Buscar comentarios DENTRO de LabelDeclaration pero después de Label
|
# Buscar comentarios DENTRO de LabelDeclaration pero después de Label
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
label_comments = label_decl[0].xpath("./stl:Comment | ./stl:LineComment", namespaces=ns)
|
label_comments = label_decl[0].xpath(
|
||||||
|
"./stl:Comment | ./stl:LineComment", namespaces=ns
|
||||||
|
)
|
||||||
for lcomm in label_comments:
|
for lcomm in label_comments:
|
||||||
comment_text = get_comment_text(lcomm)
|
comment_text = get_comment_text(lcomm)
|
||||||
if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea
|
if comment_text:
|
||||||
|
line_comment += (
|
||||||
|
f" // {comment_text}" # Añadir al comentario de línea
|
||||||
|
)
|
||||||
|
|
||||||
# 3. Token de Instrucción STL
|
# 3. Token de Instrucción STL
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
|
@ -545,10 +615,15 @@ def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
instruction_str = token_text
|
instruction_str = token_text
|
||||||
# Comentarios asociados directamente al token
|
# Comentarios asociados directamente al token
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
token_comments = instruction_token[0].xpath("./stl:Comment | ./stl:LineComment", namespaces=ns)
|
token_comments = instruction_token[0].xpath(
|
||||||
|
"./stl:Comment | ./stl:LineComment", namespaces=ns
|
||||||
|
)
|
||||||
for tcomm in token_comments:
|
for tcomm in token_comments:
|
||||||
comment_text = get_comment_text(tcomm)
|
comment_text = get_comment_text(tcomm)
|
||||||
if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea
|
if comment_text:
|
||||||
|
line_comment += (
|
||||||
|
f" // {comment_text}" # Añadir al comentario de línea
|
||||||
|
)
|
||||||
|
|
||||||
# 4. Acceso/Operando STL
|
# 4. Acceso/Operando STL
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
|
@ -559,10 +634,15 @@ def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
access_str = access_text
|
access_str = access_text
|
||||||
# Comentarios DENTRO del Access (pueden ser de línea o bloque)
|
# Comentarios DENTRO del Access (pueden ser de línea o bloque)
|
||||||
# CORREGIDO: Añadido namespaces=ns
|
# CORREGIDO: Añadido namespaces=ns
|
||||||
access_comments = access_elem[0].xpath("child::stl:LineComment | child::stl:Comment", namespaces=ns)
|
access_comments = access_elem[0].xpath(
|
||||||
|
"child::stl:LineComment | child::stl:Comment", namespaces=ns
|
||||||
|
)
|
||||||
for acc_comm in access_comments:
|
for acc_comm in access_comments:
|
||||||
comment_text = get_comment_text(acc_comm)
|
comment_text = get_comment_text(acc_comm)
|
||||||
if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea
|
if comment_text:
|
||||||
|
line_comment += (
|
||||||
|
f" // {comment_text}" # Añadir al comentario de línea
|
||||||
|
)
|
||||||
|
|
||||||
# Construir la línea: Etiqueta (si hay) + Tab + Instrucción + Espacio + Operando (si hay) + Comentario(s)
|
# Construir la línea: Etiqueta (si hay) + Tab + Instrucción + Espacio + Operando (si hay) + Comentario(s)
|
||||||
current_line = ""
|
current_line = ""
|
||||||
|
@ -589,12 +669,112 @@ def reconstruct_stl_from_statementlist(statement_list_node):
|
||||||
|
|
||||||
return "\n".join(stl_lines)
|
return "\n".join(stl_lines)
|
||||||
|
|
||||||
|
|
||||||
|
# DB Parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse_interface_members(member_elements):
|
||||||
|
"""
|
||||||
|
Parsea recursivamente una lista de elementos <Member> de una interfaz o estructura.
|
||||||
|
Maneja miembros simples, structs anidados y arrays con valores iniciales.
|
||||||
|
"""
|
||||||
|
members_data = []
|
||||||
|
if not member_elements:
|
||||||
|
return members_data
|
||||||
|
|
||||||
|
for member in member_elements:
|
||||||
|
member_name = member.get("Name")
|
||||||
|
member_dtype = member.get("Datatype")
|
||||||
|
member_remanence = member.get("Remanence", "NonRetain") # Default si no existe
|
||||||
|
member_accessibility = member.get("Accessibility", "Public") # Default
|
||||||
|
|
||||||
|
if not member_name or not member_dtype:
|
||||||
|
print(
|
||||||
|
f"Advertencia: Miembro sin nombre o tipo de dato encontrado. Saltando."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
member_info = {
|
||||||
|
"name": member_name,
|
||||||
|
"datatype": member_dtype,
|
||||||
|
"remanence": member_remanence,
|
||||||
|
"accessibility": member_accessibility,
|
||||||
|
"start_value": None, # Para valores simples o structs/arrays inicializados globalmente
|
||||||
|
"comment": None,
|
||||||
|
"children": [], # Para structs
|
||||||
|
"array_elements": {}, # Para arrays (índice -> valor)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extraer comentario del miembro
|
||||||
|
# Usar namespace iface
|
||||||
|
comment_node = member.xpath("./iface:Comment", namespaces=ns)
|
||||||
|
if comment_node:
|
||||||
|
# Llama a get_multilingual_text que ya maneja el namespace iface internamente
|
||||||
|
member_info["comment"] = get_multilingual_text(comment_node[0])
|
||||||
|
|
||||||
|
# Extraer valor inicial (para tipos simples)
|
||||||
|
# Usar namespace iface
|
||||||
|
start_value_node = member.xpath("./iface:StartValue", namespaces=ns)
|
||||||
|
if start_value_node:
|
||||||
|
constant_name = start_value_node[0].get("ConstantName")
|
||||||
|
if constant_name:
|
||||||
|
member_info["start_value"] = constant_name
|
||||||
|
else:
|
||||||
|
member_info["start_value"] = (
|
||||||
|
start_value_node[0].text
|
||||||
|
if start_value_node[0].text is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Manejar Structs Anidados ---
|
||||||
|
# Usar namespace iface
|
||||||
|
nested_sections = member.xpath(
|
||||||
|
"./iface:Sections/iface:Section/iface:Member", namespaces=ns
|
||||||
|
)
|
||||||
|
if nested_sections:
|
||||||
|
member_info["children"] = parse_interface_members(
|
||||||
|
nested_sections
|
||||||
|
) # Llamada recursiva
|
||||||
|
|
||||||
|
# --- Manejar Arrays ---
|
||||||
|
if member_dtype.lower().startswith("array["):
|
||||||
|
# Usar namespace iface
|
||||||
|
subelements = member.xpath("./iface:Subelement", namespaces=ns)
|
||||||
|
for sub in subelements:
|
||||||
|
path = sub.get("Path")
|
||||||
|
# Usar namespace iface
|
||||||
|
sub_start_value_node = sub.xpath("./iface:StartValue", namespaces=ns)
|
||||||
|
if path and sub_start_value_node:
|
||||||
|
constant_name = sub_start_value_node[0].get("ConstantName")
|
||||||
|
value = (
|
||||||
|
constant_name
|
||||||
|
if constant_name
|
||||||
|
else (
|
||||||
|
sub_start_value_node[0].text
|
||||||
|
if sub_start_value_node[0].text is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
member_info["array_elements"][path] = value
|
||||||
|
else:
|
||||||
|
# Usar namespace iface
|
||||||
|
sub_comment_node = sub.xpath("./iface:Comment", namespaces=ns)
|
||||||
|
if path and sub_comment_node:
|
||||||
|
# member_info["array_comments"][path] = get_multilingual_text(sub_comment_node[0])
|
||||||
|
pass
|
||||||
|
|
||||||
|
members_data.append(member_info)
|
||||||
|
|
||||||
|
return members_data
|
||||||
|
|
||||||
|
|
||||||
# --- Main Parsing Function ---
|
# --- Main Parsing Function ---
|
||||||
|
|
||||||
|
|
||||||
def parse_network(network_element):
|
def parse_network(network_element):
|
||||||
"""
|
"""
|
||||||
Parsea una red, extrae lógica y añade conexiones EN implícitas.
|
Parsea una red, extrae lógica y añade conexiones EN implícitas.
|
||||||
Maneja wires con múltiples destinos.
|
Maneja wires con múltiples destinos. (Función original adaptada para namespaces)
|
||||||
"""
|
"""
|
||||||
if network_element is None:
|
if network_element is None:
|
||||||
return {
|
return {
|
||||||
|
@ -607,15 +787,16 @@ def parse_network(network_element):
|
||||||
|
|
||||||
network_id = network_element.get("ID")
|
network_id = network_element.get("ID")
|
||||||
|
|
||||||
# --- Extracción Título/Comentario (sin cambios respecto a la última versión) ---
|
# Extracción Título/Comentario (usar namespace iface para MultilingualText)
|
||||||
title_element = network_element.xpath(
|
title_element = network_element.xpath(
|
||||||
".//*[local-name()='MultilingualText'][@CompositionName='Title']"
|
".//iface:MultilingualText[@CompositionName='Title']", namespaces=ns
|
||||||
)
|
)
|
||||||
network_title = (
|
network_title = (
|
||||||
get_multilingual_text(title_element[0])
|
get_multilingual_text(title_element[0])
|
||||||
if title_element
|
if title_element
|
||||||
else f"Network {network_id}"
|
else f"Network {network_id}"
|
||||||
)
|
)
|
||||||
|
# Asume que el comentario está en ObjectList dentro de CompileUnit
|
||||||
comment_element = network_element.xpath(
|
comment_element = network_element.xpath(
|
||||||
"./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']"
|
"./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']"
|
||||||
)
|
)
|
||||||
|
@ -623,31 +804,31 @@ def parse_network(network_element):
|
||||||
get_multilingual_text(comment_element[0]) if comment_element else ""
|
get_multilingual_text(comment_element[0]) if comment_element else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Buscar FlgNet usando namespace flg
|
||||||
flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns)
|
flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns)
|
||||||
if not flgnet_list:
|
if not flgnet_list:
|
||||||
# print(f"Advertencia: FlgNet no encontrado en Red ID={network_id}. Puede estar vacía o ser comentario.")
|
|
||||||
return {
|
return {
|
||||||
"id": network_id,
|
"id": network_id,
|
||||||
"title": network_title,
|
"title": network_title,
|
||||||
"comment": network_comment,
|
"comment": network_comment,
|
||||||
"logic": [],
|
"logic": [],
|
||||||
|
"language": "Unknown",
|
||||||
"error": "FlgNet not found",
|
"error": "FlgNet not found",
|
||||||
}
|
}
|
||||||
flgnet = flgnet_list[0]
|
flgnet = flgnet_list[0]
|
||||||
|
|
||||||
# 1. Parsear Access, Parts y Calls (sin cambios)
|
# 1. Parsear Access, Parts y Calls (llaman a funciones que ya usan ns)
|
||||||
access_map = {
|
access_map = {
|
||||||
acc_info["uid"]: acc_info
|
acc_info["uid"]: acc_info
|
||||||
for acc in flgnet.xpath(".//flg:Access", namespaces=ns)
|
for acc in flgnet.xpath(".//flg:Access", namespaces=ns) # Usa ns
|
||||||
if (acc_info := parse_access(acc)) and acc_info["type"] != "unknown"
|
if (acc_info := parse_access(acc)) and acc_info["type"] != "unknown"
|
||||||
}
|
}
|
||||||
parts_and_calls_map = {}
|
parts_and_calls_map = {}
|
||||||
|
# Usa ns
|
||||||
instruction_elements = flgnet.xpath(".//flg:Part | .//flg:Call", namespaces=ns)
|
instruction_elements = flgnet.xpath(".//flg:Part | .//flg:Call", namespaces=ns)
|
||||||
for element in instruction_elements:
|
for element in instruction_elements:
|
||||||
parsed_info = None
|
parsed_info = None
|
||||||
tag_name = etree.QName(
|
tag_name = etree.QName(element.tag).localname
|
||||||
element.tag
|
|
||||||
).localname # Obtener nombre local de la etiqueta
|
|
||||||
if tag_name == "Part":
|
if tag_name == "Part":
|
||||||
parsed_info = parse_part(element)
|
parsed_info = parse_part(element)
|
||||||
elif tag_name == "Call":
|
elif tag_name == "Call":
|
||||||
|
@ -659,85 +840,50 @@ def parse_network(network_element):
|
||||||
f"Advertencia: Se ignoró un Part/Call inválido en la red {network_id}"
|
f"Advertencia: Se ignoró un Part/Call inválido en la red {network_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- 2. Parsear Wires (MODIFICADO para multi-destino) ---
|
# 2. Parsear Wires (con namespaces)
|
||||||
wire_connections = defaultdict(
|
wire_connections = defaultdict(list)
|
||||||
list
|
source_connections = defaultdict(list)
|
||||||
) # (dest_uid, dest_pin) -> [(src_uid, src_pin), ...]
|
eno_outputs = defaultdict(list)
|
||||||
source_connections = defaultdict(
|
# Cachear QNames con namespace flg
|
||||||
list
|
flg_ns_uri = ns["flg"]
|
||||||
) # (src_uid, src_pin) -> [(dest_uid, dest_pin), ...]
|
|
||||||
eno_outputs = defaultdict(
|
|
||||||
list
|
|
||||||
) # src_uid -> [(dest_uid, dest_pin), ...] (conexiones DESDE eno)
|
|
||||||
|
|
||||||
flg_ns_uri = ns["flg"] # Cache namespace URI
|
|
||||||
qname_powerrail = etree.QName(flg_ns_uri, "Powerrail")
|
qname_powerrail = etree.QName(flg_ns_uri, "Powerrail")
|
||||||
qname_identcon = etree.QName(flg_ns_uri, "IdentCon")
|
qname_identcon = etree.QName(flg_ns_uri, "IdentCon")
|
||||||
qname_namecon = etree.QName(flg_ns_uri, "NameCon")
|
qname_namecon = etree.QName(flg_ns_uri, "NameCon")
|
||||||
|
# Usa ns
|
||||||
for wire in flgnet.xpath(".//flg:Wire", namespaces=ns):
|
for wire in flgnet.xpath(".//flg:Wire", namespaces=ns):
|
||||||
children = wire.getchildren()
|
children = wire.getchildren()
|
||||||
if len(children) < 2:
|
if len(children) < 2:
|
||||||
continue # Ignorar wires sin fuente y al menos un destino
|
continue
|
||||||
|
|
||||||
source_elem = children[0]
|
source_elem = children[0]
|
||||||
source_uid, source_pin = None, None
|
source_uid, source_pin = None, None
|
||||||
|
|
||||||
# Determinar fuente
|
|
||||||
if source_elem.tag == qname_powerrail:
|
if source_elem.tag == qname_powerrail:
|
||||||
source_uid, source_pin = "POWERRAIL", "out"
|
source_uid, source_pin = "POWERRAIL", "out"
|
||||||
elif source_elem.tag == qname_identcon:
|
elif source_elem.tag == qname_identcon:
|
||||||
source_uid, source_pin = (
|
source_uid, source_pin = source_elem.get("UId"), "value"
|
||||||
source_elem.get("UId"),
|
|
||||||
"value",
|
|
||||||
) # Acceso a variable/constante
|
|
||||||
elif source_elem.tag == qname_namecon:
|
elif source_elem.tag == qname_namecon:
|
||||||
source_uid, source_pin = source_elem.get("UId"), source_elem.get(
|
source_uid, source_pin = source_elem.get("UId"), source_elem.get("Name")
|
||||||
"Name"
|
|
||||||
) # Salida de instrucción
|
|
||||||
|
|
||||||
if source_uid is None:
|
if source_uid is None:
|
||||||
continue # No se pudo determinar la fuente
|
continue
|
||||||
|
source_info = (source_uid, source_pin)
|
||||||
source_info = (source_uid, source_pin) # Par de fuente
|
|
||||||
|
|
||||||
# Iterar sobre TODOS los posibles destinos (desde el segundo hijo en adelante)
|
|
||||||
for dest_elem in children[1:]:
|
for dest_elem in children[1:]:
|
||||||
dest_uid, dest_pin = None, None
|
dest_uid, dest_pin = None, None
|
||||||
|
|
||||||
# Determinar destino
|
|
||||||
if dest_elem.tag == qname_identcon:
|
if dest_elem.tag == qname_identcon:
|
||||||
dest_uid, dest_pin = (
|
dest_uid, dest_pin = dest_elem.get("UId"), "value"
|
||||||
dest_elem.get("UId"),
|
|
||||||
"value",
|
|
||||||
) # Entrada a variable/constante (Coil, etc.)
|
|
||||||
elif dest_elem.tag == qname_namecon:
|
elif dest_elem.tag == qname_namecon:
|
||||||
dest_uid, dest_pin = dest_elem.get("UId"), dest_elem.get(
|
dest_uid, dest_pin = dest_elem.get("UId"), dest_elem.get("Name")
|
||||||
"Name"
|
|
||||||
) # Entrada a instrucción
|
|
||||||
|
|
||||||
# Guardar conexiones si son válidas
|
|
||||||
if dest_uid is not None and dest_pin is not None:
|
if dest_uid is not None and dest_pin is not None:
|
||||||
# Mapa de Conexiones (Destino -> [Fuentes])
|
|
||||||
dest_key = (dest_uid, dest_pin)
|
dest_key = (dest_uid, dest_pin)
|
||||||
if source_info not in wire_connections[dest_key]:
|
if source_info not in wire_connections[dest_key]:
|
||||||
wire_connections[dest_key].append(source_info)
|
wire_connections[dest_key].append(source_info)
|
||||||
|
|
||||||
# Mapa de Fuentes (Fuente -> [Destinos])
|
|
||||||
source_key = (source_uid, source_pin)
|
source_key = (source_uid, source_pin)
|
||||||
dest_info = (dest_uid, dest_pin)
|
dest_info = (dest_uid, dest_pin)
|
||||||
if dest_info not in source_connections[source_key]:
|
if dest_info not in source_connections[source_key]:
|
||||||
source_connections[source_key].append(dest_info)
|
source_connections[source_key].append(dest_info)
|
||||||
|
|
||||||
# Registrar conexiones que SALEN de un pin 'eno'
|
|
||||||
if source_pin == "eno" and source_uid in parts_and_calls_map:
|
if source_pin == "eno" and source_uid in parts_and_calls_map:
|
||||||
if dest_info not in eno_outputs[source_uid]:
|
if dest_info not in eno_outputs[source_uid]:
|
||||||
eno_outputs[source_uid].append(dest_info)
|
eno_outputs[source_uid].append(dest_info)
|
||||||
# else: # Debug opcional si un elemento no es destino válido
|
|
||||||
# print(f"Advertencia: Elemento en Wire {wire.get('UId')} no es destino válido: {etree.tostring(dest_elem)}")
|
|
||||||
# --- FIN MODIFICACIÓN Wire ---
|
|
||||||
|
|
||||||
# 3. Construcción Lógica Inicial (sin cambios)
|
# 3. Construcción Lógica Inicial (sin cambios en lógica, pero verificar llamadas)
|
||||||
all_logic_steps = {}
|
all_logic_steps = {}
|
||||||
functional_block_types = [
|
functional_block_types = [
|
||||||
"Move",
|
"Move",
|
||||||
|
@ -751,7 +897,13 @@ def parse_network(network_element):
|
||||||
"Se",
|
"Se",
|
||||||
"Sd",
|
"Sd",
|
||||||
"BLKMOV",
|
"BLKMOV",
|
||||||
]
|
"TON",
|
||||||
|
"TOF",
|
||||||
|
"TP",
|
||||||
|
"CTU",
|
||||||
|
"CTD",
|
||||||
|
"CTUD",
|
||||||
|
] # Añadidos timers/counters SCL
|
||||||
rlo_generators = [
|
rlo_generators = [
|
||||||
"Contact",
|
"Contact",
|
||||||
"O",
|
"O",
|
||||||
|
@ -765,46 +917,42 @@ def parse_network(network_element):
|
||||||
"Xor",
|
"Xor",
|
||||||
"PBox",
|
"PBox",
|
||||||
"NBox",
|
"NBox",
|
||||||
]
|
"Not",
|
||||||
|
] # Añadido Not
|
||||||
for instruction_uid, instruction_info in parts_and_calls_map.items():
|
for instruction_uid, instruction_info in parts_and_calls_map.items():
|
||||||
# Copiar info básica
|
|
||||||
instruction_repr = {"instruction_uid": instruction_uid, **instruction_info}
|
instruction_repr = {"instruction_uid": instruction_uid, **instruction_info}
|
||||||
instruction_repr["inputs"] = {}
|
instruction_repr["inputs"] = {}
|
||||||
instruction_repr["outputs"] = {}
|
instruction_repr["outputs"] = {}
|
||||||
|
|
||||||
# --- INICIO: Manejo Especial SdCoil y otros Timers ---
|
|
||||||
original_type = instruction_info["type"]
|
original_type = instruction_info["type"]
|
||||||
current_type = original_type
|
current_type = original_type
|
||||||
input_pin_mapping = {} # Mapa XML pin -> JSON pin
|
input_pin_mapping = {}
|
||||||
output_pin_mapping = {} # Mapa XML pin -> JSON pin
|
output_pin_mapping = {}
|
||||||
|
# --- Manejo Especial Tipos ---
|
||||||
if original_type == "SdCoil":
|
if original_type == "SdCoil":
|
||||||
print(
|
current_type = "Se"
|
||||||
f" Advertencia: Reinterpretando 'SdCoil' (UID: {instruction_uid}) como 'Se' (Pulse Timer)."
|
input_pin_mapping = {"in": "s", "operand": "timer", "value": "tv"}
|
||||||
)
|
output_pin_mapping = {"out": "q"}
|
||||||
current_type = "Se" # Tratarlo como Se (TP)
|
elif original_type in ["Se", "Sd", "TON", "TOF", "TP"]:
|
||||||
input_pin_mapping = {
|
input_pin_mapping = {
|
||||||
"in": "s", # Pin XML 'in' mapea a JSON 's' (Start)
|
"s": "s",
|
||||||
"operand": "timer", # Pin XML 'operand' mapea a JSON 'timer' (Instance)
|
"in": "in",
|
||||||
"value": "tv", # Pin XML 'value' mapea a JSON 'tv' (Time Value)
|
"tv": "tv",
|
||||||
|
"pt": "pt",
|
||||||
|
"r": "r",
|
||||||
|
"timer": "timer",
|
||||||
}
|
}
|
||||||
output_pin_mapping = {"out": "q"} # Pin XML 'out' mapea a JSON 'q' (Output)
|
output_pin_mapping = {"q": "q", "Q": "Q", "rt": "rt", "ET": "ET"}
|
||||||
elif original_type in ["Se", "Sd"]:
|
elif original_type in ["CTU", "CTD", "CTUD"]:
|
||||||
# Mapear pines estándar de Se/Sd para consistencia con TP/TON
|
input_pin_mapping = {
|
||||||
input_pin_mapping = {"s": "s", "tv": "tv", "r": "r", "timer": "timer"}
|
"cu": "CU",
|
||||||
output_pin_mapping = {
|
"cd": "CD",
|
||||||
"q": "q",
|
"r": "R",
|
||||||
"rt": "rt", # "rtbcd": "rtbcd" (ignorar BCD)
|
"ld": "LD",
|
||||||
|
"pv": "PV",
|
||||||
|
"counter": "counter",
|
||||||
}
|
}
|
||||||
# Añadir otros mapeos si son necesarios para otros bloques (ej. Contadores)
|
output_pin_mapping = {"qu": "QU", "qd": "QD", "cv": "CV"}
|
||||||
# elif original_type == "CTU":
|
instruction_repr["type"] = current_type
|
||||||
# input_pin_mapping = {"cu": "cu", "r": "r", "pv": "pv", "counter": "counter"} # 'counter' es inventado para instancia
|
|
||||||
# output_pin_mapping = {"qu": "qu", "cv": "cv"}
|
|
||||||
# --- FIN Manejo Especial ---
|
|
||||||
|
|
||||||
instruction_repr["type"] = current_type # Actualizar el tipo si se cambió
|
|
||||||
|
|
||||||
# Mapear Entradas usando el mapeo de pines
|
|
||||||
possible_input_pins = set(
|
possible_input_pins = set(
|
||||||
[
|
[
|
||||||
"en",
|
"en",
|
||||||
|
@ -825,14 +973,14 @@ def parse_network(network_element):
|
||||||
"ld",
|
"ld",
|
||||||
"pre",
|
"pre",
|
||||||
"SRCBLK",
|
"SRCBLK",
|
||||||
|
"PT",
|
||||||
]
|
]
|
||||||
) # Ampliar con pines conocidos
|
) # Añadido PT
|
||||||
for xml_pin_name in possible_input_pins:
|
for xml_pin_name in possible_input_pins:
|
||||||
dest_key = (instruction_uid, xml_pin_name)
|
dest_key = (instruction_uid, xml_pin_name)
|
||||||
if dest_key in wire_connections:
|
if dest_key in wire_connections:
|
||||||
sources_list = wire_connections[dest_key]
|
sources_list = wire_connections[dest_key]
|
||||||
input_sources_repr = []
|
input_sources_repr = []
|
||||||
# ... (lógica existente para obtener input_sources_repr de sources_list) ...
|
|
||||||
for source_uid, source_pin in sources_list:
|
for source_uid, source_pin in sources_list:
|
||||||
if source_uid == "POWERRAIL":
|
if source_uid == "POWERRAIL":
|
||||||
input_sources_repr.append({"type": "powerrail"})
|
input_sources_repr.append({"type": "powerrail"})
|
||||||
|
@ -841,36 +989,38 @@ def parse_network(network_element):
|
||||||
elif source_uid in parts_and_calls_map:
|
elif source_uid in parts_and_calls_map:
|
||||||
source_instr_info = parts_and_calls_map[source_uid]
|
source_instr_info = parts_and_calls_map[source_uid]
|
||||||
source_original_type = source_instr_info["type"]
|
source_original_type = source_instr_info["type"]
|
||||||
# Obtener el mapeo de salida para el tipo de la fuente (si existe)
|
|
||||||
source_output_mapping = {}
|
source_output_mapping = {}
|
||||||
if source_original_type == "SdCoil":
|
if source_original_type == "SdCoil":
|
||||||
source_output_mapping = {"out": "q"}
|
source_output_mapping = {"out": "q"}
|
||||||
elif source_original_type in ["Se", "Sd"]:
|
elif source_original_type in ["Se", "Sd", "TON", "TOF", "TP"]:
|
||||||
source_output_mapping = {"q": "q", "rt": "rt"}
|
source_output_mapping = {
|
||||||
|
"q": "q",
|
||||||
# Usar el pin mapeado si existe, sino el original
|
"Q": "Q",
|
||||||
mapped_source_pin = source_output_mapping.get(source_pin, source_pin)
|
"rt": "rt",
|
||||||
|
"ET": "ET",
|
||||||
input_sources_repr.append({
|
}
|
||||||
|
elif source_original_type in ["CTU", "CTD", "CTUD"]:
|
||||||
|
source_output_mapping = {"qu": "QU", "qd": "QD", "cv": "CV"}
|
||||||
|
mapped_source_pin = source_output_mapping.get(
|
||||||
|
source_pin, source_pin
|
||||||
|
)
|
||||||
|
input_sources_repr.append(
|
||||||
|
{
|
||||||
"type": "connection",
|
"type": "connection",
|
||||||
"source_instruction_type": source_original_type, # Guardar tipo original puede ser útil
|
"source_instruction_type": source_original_type,
|
||||||
"source_instruction_uid": source_uid,
|
"source_instruction_uid": source_uid,
|
||||||
"source_pin": mapped_source_pin # <-- USAR PIN MAPEADO
|
"source_pin": mapped_source_pin,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
input_sources_repr.append(
|
input_sources_repr.append(
|
||||||
{"type": "unknown_source", "uid": source_uid}
|
{"type": "unknown_source", "uid": source_uid}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Usar el nombre de pin mapeado para el JSON
|
|
||||||
json_pin_name = input_pin_mapping.get(xml_pin_name, xml_pin_name)
|
json_pin_name = input_pin_mapping.get(xml_pin_name, xml_pin_name)
|
||||||
|
|
||||||
if len(input_sources_repr) == 1:
|
if len(input_sources_repr) == 1:
|
||||||
instruction_repr["inputs"][json_pin_name] = input_sources_repr[0]
|
instruction_repr["inputs"][json_pin_name] = input_sources_repr[0]
|
||||||
elif len(input_sources_repr) > 1:
|
elif len(input_sources_repr) > 1:
|
||||||
instruction_repr["inputs"][json_pin_name] = input_sources_repr
|
instruction_repr["inputs"][json_pin_name] = input_sources_repr
|
||||||
|
|
||||||
# Mapear Salidas usando el mapeo de pines
|
|
||||||
possible_output_pins = set(
|
possible_output_pins = set(
|
||||||
[
|
[
|
||||||
"out",
|
"out",
|
||||||
|
@ -886,17 +1036,15 @@ def parse_network(network_element):
|
||||||
"cvbcd",
|
"cvbcd",
|
||||||
"QU",
|
"QU",
|
||||||
"QD",
|
"QD",
|
||||||
|
"ET",
|
||||||
]
|
]
|
||||||
)
|
) # Añadido ET
|
||||||
for xml_pin_name in possible_output_pins:
|
for xml_pin_name in possible_output_pins:
|
||||||
source_key = (instruction_uid, xml_pin_name)
|
source_key = (instruction_uid, xml_pin_name)
|
||||||
if source_key in source_connections:
|
if source_key in source_connections:
|
||||||
# Usar el nombre de pin mapeado para el JSON
|
|
||||||
json_pin_name = output_pin_mapping.get(xml_pin_name, xml_pin_name)
|
json_pin_name = output_pin_mapping.get(xml_pin_name, xml_pin_name)
|
||||||
|
|
||||||
if json_pin_name not in instruction_repr["outputs"]:
|
if json_pin_name not in instruction_repr["outputs"]:
|
||||||
instruction_repr["outputs"][json_pin_name] = []
|
instruction_repr["outputs"][json_pin_name] = []
|
||||||
|
|
||||||
for dest_uid, dest_pin in source_connections[source_key]:
|
for dest_uid, dest_pin in source_connections[source_key]:
|
||||||
if dest_uid in access_map:
|
if dest_uid in access_map:
|
||||||
if (
|
if (
|
||||||
|
@ -906,10 +1054,9 @@ def parse_network(network_element):
|
||||||
instruction_repr["outputs"][json_pin_name].append(
|
instruction_repr["outputs"][json_pin_name].append(
|
||||||
access_map[dest_uid]
|
access_map[dest_uid]
|
||||||
)
|
)
|
||||||
|
|
||||||
all_logic_steps[instruction_uid] = instruction_repr
|
all_logic_steps[instruction_uid] = instruction_repr
|
||||||
|
|
||||||
# 4. Inferencia EN (sin cambios)
|
# 4. Inferencia EN (sin cambios en lógica)
|
||||||
processed_blocks_en_inference = set()
|
processed_blocks_en_inference = set()
|
||||||
something_changed = True
|
something_changed = True
|
||||||
inference_passes = 0
|
inference_passes = 0
|
||||||
|
@ -930,8 +1077,8 @@ def parse_network(network_element):
|
||||||
for i, instruction in enumerate(ordered_logic_list_for_en):
|
for i, instruction in enumerate(ordered_logic_list_for_en):
|
||||||
part_uid = instruction["instruction_uid"]
|
part_uid = instruction["instruction_uid"]
|
||||||
part_type_original = (
|
part_type_original = (
|
||||||
instruction["type"].replace("_scl", "").replace("_error", "")
|
instruction["type"].replace(SCL_SUFFIX, "").replace("_error", "")
|
||||||
)
|
) # Usa SCL_SUFFIX
|
||||||
if (
|
if (
|
||||||
part_type_original in functional_block_types
|
part_type_original in functional_block_types
|
||||||
and "en" not in instruction["inputs"]
|
and "en" not in instruction["inputs"]
|
||||||
|
@ -943,7 +1090,9 @@ def parse_network(network_element):
|
||||||
prev_instr = ordered_logic_list_for_en[j]
|
prev_instr = ordered_logic_list_for_en[j]
|
||||||
prev_uid = prev_instr["instruction_uid"]
|
prev_uid = prev_instr["instruction_uid"]
|
||||||
prev_type_original = (
|
prev_type_original = (
|
||||||
prev_instr["type"].replace("_scl", "").replace("_error", "")
|
prev_instr["type"]
|
||||||
|
.replace(SCL_SUFFIX, "")
|
||||||
|
.replace("_error", "")
|
||||||
)
|
)
|
||||||
if prev_type_original in rlo_generators:
|
if prev_type_original in rlo_generators:
|
||||||
inferred_en_source = {
|
inferred_en_source = {
|
||||||
|
@ -979,7 +1128,7 @@ def parse_network(network_element):
|
||||||
processed_blocks_en_inference.add(part_uid)
|
processed_blocks_en_inference.add(part_uid)
|
||||||
something_changed = True
|
something_changed = True
|
||||||
|
|
||||||
# 5. Añadir lógica ENO interesante (sin cambios)
|
# 5. Añadir lógica ENO interesante (sin cambios en lógica)
|
||||||
for source_instr_uid, eno_destinations in eno_outputs.items():
|
for source_instr_uid, eno_destinations in eno_outputs.items():
|
||||||
if source_instr_uid not in all_logic_steps:
|
if source_instr_uid not in all_logic_steps:
|
||||||
continue
|
continue
|
||||||
|
@ -1025,17 +1174,30 @@ def parse_network(network_element):
|
||||||
if interesting_eno_logic:
|
if interesting_eno_logic:
|
||||||
all_logic_steps[source_instr_uid]["eno_logic"] = interesting_eno_logic
|
all_logic_steps[source_instr_uid]["eno_logic"] = interesting_eno_logic
|
||||||
|
|
||||||
# 6. Ordenar y Devolver (sin cambios)
|
# 6. Ordenar y Devolver
|
||||||
network_logic_final = [
|
network_logic_final = [
|
||||||
all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps
|
all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps
|
||||||
]
|
]
|
||||||
|
# Determinar lenguaje de la red para devolverlo
|
||||||
|
network_lang = "Unknown"
|
||||||
|
if network_element is not None:
|
||||||
|
attr_list_net = network_element.xpath("./*[local-name()='AttributeList']")
|
||||||
|
if attr_list_net:
|
||||||
|
lang_node_net = attr_list_net[0].xpath(
|
||||||
|
"./*[local-name()='ProgrammingLanguage']/text()"
|
||||||
|
)
|
||||||
|
if lang_node_net:
|
||||||
|
network_lang = lang_node_net[0].strip()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": network_id,
|
"id": network_id,
|
||||||
"title": network_title,
|
"title": network_title,
|
||||||
"comment": network_comment,
|
"comment": network_comment,
|
||||||
|
"language": network_lang,
|
||||||
"logic": network_logic_final,
|
"logic": network_logic_final,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def convert_xml_to_json(xml_filepath, json_filepath):
|
def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
||||||
if not os.path.exists(xml_filepath):
|
if not os.path.exists(xml_filepath):
|
||||||
|
@ -1047,25 +1209,37 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
tree = etree.parse(xml_filepath, parser)
|
tree = etree.parse(xml_filepath, parser)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
print("Paso 1: Parseo XML completado.")
|
print("Paso 1: Parseo XML completado.")
|
||||||
print("Paso 2: Buscando el bloque SW.Blocks.FC...") # Asume FC primero
|
print("Paso 2: Buscando el bloque SW.Blocks.FC, SW.Blocks.FB o SW.Blocks.GlobalDB...")
|
||||||
block_list = root.xpath("//*[local-name()='SW.Blocks.FC']")
|
# --- MODIFICADO: Buscar FC, FB o GlobalDB ---
|
||||||
block_type_found = "FC"
|
block_list = root.xpath("//*[local-name()='SW.Blocks.FC' or local-name()='SW.Blocks.FB' or local-name()='SW.Blocks.GlobalDB']")
|
||||||
if not block_list:
|
block_type_found = None
|
||||||
block_list = root.xpath(
|
the_block = None
|
||||||
"//*[local-name()='SW.Blocks.FB']"
|
|
||||||
) # Busca FB si no hay FC
|
if block_list:
|
||||||
block_type_found = "FB"
|
|
||||||
if not block_list:
|
|
||||||
print("Error Crítico: No se encontró <SW.Blocks.FC> ni <SW.Blocks.FB>.")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
"Advertencia: Se encontró <SW.Blocks.FB> en lugar de <SW.Blocks.FC>."
|
|
||||||
)
|
|
||||||
the_block = block_list[0]
|
the_block = block_list[0]
|
||||||
print(
|
# Obtener el nombre real de la etiqueta encontrada
|
||||||
f"Paso 2: Bloque SW.Blocks.{block_type_found} encontrado (ID={the_block.get('ID')})."
|
block_tag_name = etree.QName(the_block.tag).localname
|
||||||
)
|
if block_tag_name == "SW.Blocks.FC":
|
||||||
|
block_type_found = "FC"
|
||||||
|
elif block_tag_name == "SW.Blocks.FB":
|
||||||
|
block_type_found = "FB"
|
||||||
|
elif block_tag_name == "SW.Blocks.GlobalDB":
|
||||||
|
block_type_found = "GlobalDB" # Identificar el tipo DB
|
||||||
|
print(f"Paso 2: Bloque {block_tag_name} encontrado (ID={the_block.get('ID')}).")
|
||||||
|
else:
|
||||||
|
# Mensaje de error más específico y añadimos depuración
|
||||||
|
print("Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC>, <SW.Blocks.FB> o <SW.Blocks.GlobalDB>) usando XPath.")
|
||||||
|
# --- Añadir Debugging ---
|
||||||
|
print(f"DEBUG: Tag del elemento raíz del XML: {root.tag}")
|
||||||
|
print(f"DEBUG: Primeros hijos del raíz:")
|
||||||
|
for i, child in enumerate(root.getchildren()):
|
||||||
|
if i < 5: # Imprimir solo los primeros 5 para no saturar
|
||||||
|
print(f"DEBUG: - Hijo {i+1}: {child.tag}")
|
||||||
|
else:
|
||||||
|
print("DEBUG: - ... (más hijos)")
|
||||||
|
break
|
||||||
|
# --- Fin Debugging ---
|
||||||
|
return # Salir si no se encuentra el bloque principal
|
||||||
print("Paso 3: Extrayendo atributos del bloque...")
|
print("Paso 3: Extrayendo atributos del bloque...")
|
||||||
attribute_list_node = the_block.xpath("./*[local-name()='AttributeList']")
|
attribute_list_node = the_block.xpath("./*[local-name()='AttributeList']")
|
||||||
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown"
|
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown"
|
||||||
|
@ -1241,12 +1415,18 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
|
|
||||||
reconstructed_stl = f"// STL extraction failed for Network {network_id}: StatementList node not found.\n"
|
reconstructed_stl = f"// STL extraction failed for Network {network_id}: StatementList node not found.\n"
|
||||||
if statement_list_node:
|
if statement_list_node:
|
||||||
print(f" Reconstruyendo STL desde StatementList para red {network_id}...")
|
print(
|
||||||
|
f" Reconstruyendo STL desde StatementList para red {network_id}..."
|
||||||
|
)
|
||||||
# Llama a la nueva función de reconstrucción STL
|
# Llama a la nueva función de reconstrucción STL
|
||||||
reconstructed_stl = reconstruct_stl_from_statementlist(statement_list_node[0])
|
reconstructed_stl = reconstruct_stl_from_statementlist(
|
||||||
|
statement_list_node[0]
|
||||||
|
)
|
||||||
# print(f" ... STL reconstruido (parcial):\n{reconstructed_stl[:200]}...") # Preview opcional
|
# print(f" ... STL reconstruido (parcial):\n{reconstructed_stl[:200]}...") # Preview opcional
|
||||||
else:
|
else:
|
||||||
print(f" Advertencia: No se encontró nodo <StatementList> para red STL {network_id}.")
|
print(
|
||||||
|
f" Advertencia: No se encontró nodo <StatementList> para red STL {network_id}."
|
||||||
|
)
|
||||||
|
|
||||||
# Guardar como un chunk de texto crudo
|
# Guardar como un chunk de texto crudo
|
||||||
parsed_network_data = {
|
parsed_network_data = {
|
||||||
|
@ -1340,6 +1520,7 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print("--- Fin Traceback ---")
|
print("--- Fin Traceback ---")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Imports necesarios solo para la ejecución como script principal
|
# Imports necesarios solo para la ejecución como script principal
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -1371,7 +1552,9 @@ if __name__ == "__main__":
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
json_output_file = os.path.join(output_dir, f"{xml_filename_base}_simplified.json")
|
json_output_file = os.path.join(output_dir, f"{xml_filename_base}_simplified.json")
|
||||||
|
|
||||||
print(f"(x1) Convirtiendo: '{os.path.relpath(xml_input_file)}' -> '{os.path.relpath(json_output_file)}'")
|
print(
|
||||||
|
f"(x1) Convirtiendo: '{os.path.relpath(xml_input_file)}' -> '{os.path.relpath(json_output_file)}'"
|
||||||
|
)
|
||||||
|
|
||||||
# Llamar a la función principal de conversión del script
|
# Llamar a la función principal de conversión del script
|
||||||
# Asumiendo que tu función principal se llama convert_xml_to_json(input_path, output_path)
|
# Asumiendo que tu función principal se llama convert_xml_to_json(input_path, output_path)
|
||||||
|
@ -1380,6 +1563,6 @@ if __name__ == "__main__":
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error Crítico (x1) durante la conversión de '{xml_input_file}': {e}")
|
print(f"Error Crítico (x1) durante la conversión de '{xml_input_file}': {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be us
|
||||||
# which works but passing it explicitly might be cleaner.
|
# which works but passing it explicitly might be cleaner.
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
|
||||||
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
"""
|
"""
|
||||||
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
|
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
|
||||||
|
@ -37,24 +38,45 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
(Esta es la implementación de la función como la tenías en el archivo original)
|
(Esta es la implementación de la función como la tenías en el archivo original)
|
||||||
"""
|
"""
|
||||||
instr_uid = instruction["instruction_uid"]
|
instr_uid = instruction["instruction_uid"]
|
||||||
instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "")
|
instr_type_original = (
|
||||||
|
instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "")
|
||||||
|
)
|
||||||
made_change = False
|
made_change = False
|
||||||
|
|
||||||
# Check if this instruction *could* generate a condition suitable for grouping
|
# Check if this instruction *could* generate a condition suitable for grouping
|
||||||
# It must have been processed by the new SymPy method
|
# It must have been processed by the new SymPy method
|
||||||
if (
|
if (
|
||||||
not instruction.get("type", "").endswith(SCL_SUFFIX) # Check if processed by new method
|
not instruction.get("type", "").endswith(
|
||||||
|
SCL_SUFFIX
|
||||||
|
) # Check if processed by new method
|
||||||
or "_error" in instruction.get("type", "")
|
or "_error" in instruction.get("type", "")
|
||||||
or instruction.get("grouped", False)
|
or instruction.get("grouped", False)
|
||||||
or instr_type_original not in [ # Original types that produce boolean results
|
or instr_type_original
|
||||||
"Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "PBox", "NBox", "And", "Xor", "Not" # Add others like comparison
|
not in [ # Original types that produce boolean results
|
||||||
|
"Contact",
|
||||||
|
"O",
|
||||||
|
"Eq",
|
||||||
|
"Ne",
|
||||||
|
"Gt",
|
||||||
|
"Lt",
|
||||||
|
"Ge",
|
||||||
|
"Le",
|
||||||
|
"PBox",
|
||||||
|
"NBox",
|
||||||
|
"And",
|
||||||
|
"Xor",
|
||||||
|
"Not", # Add others like comparison
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Avoid reagruping if SCL already contains a complex IF (less likely now)
|
# Avoid reagruping if SCL already contains a complex IF (less likely now)
|
||||||
current_scl = instruction.get("scl", "")
|
current_scl = instruction.get("scl", "")
|
||||||
if current_scl.strip().startswith("IF") and "END_IF;" in current_scl and GROUPED_COMMENT not in current_scl:
|
if (
|
||||||
|
current_scl.strip().startswith("IF")
|
||||||
|
and "END_IF;" in current_scl
|
||||||
|
and GROUPED_COMMENT not in current_scl
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# *** Get the SymPy expression for the condition ***
|
# *** Get the SymPy expression for the condition ***
|
||||||
|
@ -62,20 +84,34 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
sympy_condition_expr = sympy_map.get(map_key_out)
|
sympy_condition_expr = sympy_map.get(map_key_out)
|
||||||
|
|
||||||
# No SymPy expression found or trivial conditions
|
# No SymPy expression found or trivial conditions
|
||||||
if sympy_condition_expr is None or sympy_condition_expr in [sympy.true, sympy.false]:
|
if sympy_condition_expr is None or sympy_condition_expr in [
|
||||||
|
sympy.true,
|
||||||
|
sympy.false,
|
||||||
|
]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# --- Find consumer instructions (logic similar to before) ---
|
# --- Find consumer instructions (logic similar to before) ---
|
||||||
grouped_instructions_cores = []
|
grouped_instructions_cores = []
|
||||||
consumer_instr_list = []
|
consumer_instr_list = []
|
||||||
network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), [])
|
network_logic = next(
|
||||||
if not network_logic: return False
|
(net["logic"] for net in data["networks"] if net["id"] == network_id), []
|
||||||
|
)
|
||||||
|
if not network_logic:
|
||||||
|
return False
|
||||||
|
|
||||||
groupable_types = [ # Types whose *final SCL* we want to group
|
groupable_types = [ # Types whose *final SCL* we want to group
|
||||||
"Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert",
|
"Move",
|
||||||
"Call_FC", "Call_FB", # Assuming these generate final SCL in their processors now
|
"Add",
|
||||||
|
"Sub",
|
||||||
|
"Mul",
|
||||||
|
"Div",
|
||||||
|
"Mod",
|
||||||
|
"Convert",
|
||||||
|
"Call_FC",
|
||||||
|
"Call_FB", # Assuming these generate final SCL in their processors now
|
||||||
# SCoil/RCoil might also be groupable if their SCL is final assignment
|
# SCoil/RCoil might also be groupable if their SCL is final assignment
|
||||||
"SCoil", "RCoil"
|
"SCoil",
|
||||||
|
"RCoil",
|
||||||
]
|
]
|
||||||
|
|
||||||
for consumer_instr in network_logic:
|
for consumer_instr in network_logic:
|
||||||
|
@ -85,19 +121,27 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
|
|
||||||
consumer_en = consumer_instr.get("inputs", {}).get("en")
|
consumer_en = consumer_instr.get("inputs", {}).get("en")
|
||||||
consumer_type = consumer_instr.get("type", "") # Current type suffix matters
|
consumer_type = consumer_instr.get("type", "") # Current type suffix matters
|
||||||
consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace("_error", "")
|
consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace(
|
||||||
|
"_error", ""
|
||||||
|
)
|
||||||
|
|
||||||
is_enabled_by_us = False
|
is_enabled_by_us = False
|
||||||
if ( isinstance(consumer_en, dict) and consumer_en.get("type") == "connection" and
|
if (
|
||||||
consumer_en.get("source_instruction_uid") == instr_uid and
|
isinstance(consumer_en, dict)
|
||||||
consumer_en.get("source_pin") == "out"):
|
and consumer_en.get("type") == "connection"
|
||||||
|
and consumer_en.get("source_instruction_uid") == instr_uid
|
||||||
|
and consumer_en.get("source_pin") == "out"
|
||||||
|
):
|
||||||
is_enabled_by_us = True
|
is_enabled_by_us = True
|
||||||
|
|
||||||
# Check if consumer is groupable AND has its final SCL generated
|
# Check if consumer is groupable AND has its final SCL generated
|
||||||
# The suffix check needs adjustment based on how terminating processors set it.
|
# The suffix check needs adjustment based on how terminating processors set it.
|
||||||
# Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix.
|
# Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix.
|
||||||
if ( is_enabled_by_us and consumer_type.endswith(SCL_SUFFIX) and # Or a specific "final_scl" suffix
|
if (
|
||||||
consumer_type_original in groupable_types ):
|
is_enabled_by_us
|
||||||
|
and consumer_type.endswith(SCL_SUFFIX) # Or a specific "final_scl" suffix
|
||||||
|
and consumer_type_original in groupable_types
|
||||||
|
):
|
||||||
|
|
||||||
consumer_scl = consumer_instr.get("scl", "")
|
consumer_scl = consumer_instr.get("scl", "")
|
||||||
# Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now)
|
# Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now)
|
||||||
|
@ -105,9 +149,15 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
if consumer_scl:
|
if consumer_scl:
|
||||||
# If consumer SCL itself is an IF generated by EN, take the body
|
# If consumer SCL itself is an IF generated by EN, take the body
|
||||||
if consumer_scl.strip().startswith("IF"):
|
if consumer_scl.strip().startswith("IF"):
|
||||||
match = re.search(r"THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE)
|
match = re.search(
|
||||||
|
r"THEN\s*(.*?)\s*END_IF;",
|
||||||
|
consumer_scl,
|
||||||
|
re.DOTALL | re.IGNORECASE,
|
||||||
|
)
|
||||||
core_scl = match.group(1).strip() if match else None
|
core_scl = match.group(1).strip() if match else None
|
||||||
elif not consumer_scl.strip().startswith("//"): # Otherwise, take the whole line if not comment
|
elif not consumer_scl.strip().startswith(
|
||||||
|
"//"
|
||||||
|
): # Otherwise, take the whole line if not comment
|
||||||
core_scl = consumer_scl.strip()
|
core_scl = consumer_scl.strip()
|
||||||
|
|
||||||
if core_scl:
|
if core_scl:
|
||||||
|
@ -116,12 +166,16 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
|
|
||||||
# --- If groupable consumers found ---
|
# --- If groupable consumers found ---
|
||||||
if len(grouped_instructions_cores) > 1:
|
if len(grouped_instructions_cores) > 1:
|
||||||
print(f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}")
|
print(
|
||||||
|
f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}"
|
||||||
|
)
|
||||||
|
|
||||||
# *** Simplify the SymPy condition ***
|
# *** Simplify the SymPy condition ***
|
||||||
try:
|
try:
|
||||||
#simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True)
|
# simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True)
|
||||||
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_condition_expr, simplify=True)
|
simplified_expr = sympy.logic.boolalg.to_dnf(
|
||||||
|
sympy_condition_expr, simplify=True
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error simplifying condition for grouping UID {instr_uid}: {e}")
|
print(f"Error simplifying condition for grouping UID {instr_uid}: {e}")
|
||||||
simplified_expr = sympy_condition_expr # Fallback
|
simplified_expr = sympy_condition_expr # Fallback
|
||||||
|
@ -132,7 +186,9 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
# *** Build the grouped IF SCL ***
|
# *** Build the grouped IF SCL ***
|
||||||
scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"]
|
scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"]
|
||||||
for core_line in grouped_instructions_cores:
|
for core_line in grouped_instructions_cores:
|
||||||
indented_core = "\n".join([f" {line.strip()}" for line in core_line.splitlines()])
|
indented_core = "\n".join(
|
||||||
|
[f" {line.strip()}" for line in core_line.splitlines()]
|
||||||
|
)
|
||||||
scl_grouped_lines.append(indented_core)
|
scl_grouped_lines.append(indented_core)
|
||||||
scl_grouped_lines.append("END_IF;")
|
scl_grouped_lines.append("END_IF;")
|
||||||
final_grouped_scl = "\n".join(scl_grouped_lines)
|
final_grouped_scl = "\n".join(scl_grouped_lines)
|
||||||
|
@ -147,6 +203,7 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
|
|
||||||
return made_change
|
return made_change
|
||||||
|
|
||||||
|
|
||||||
def load_processors(processors_dir="processors"):
|
def load_processors(processors_dir="processors"):
|
||||||
"""
|
"""
|
||||||
Escanea el directorio, importa módulos, construye el mapa y una lista
|
Escanea el directorio, importa módulos, construye el mapa y una lista
|
||||||
|
@ -170,7 +227,9 @@ def load_processors(processors_dir="processors"):
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(full_module_name)
|
module = importlib.import_module(full_module_name)
|
||||||
|
|
||||||
if hasattr(module, 'get_processor_info') and callable(module.get_processor_info):
|
if hasattr(module, "get_processor_info") and callable(
|
||||||
|
module.get_processor_info
|
||||||
|
):
|
||||||
processor_info = module.get_processor_info()
|
processor_info = module.get_processor_info()
|
||||||
info_list = []
|
info_list = []
|
||||||
if isinstance(processor_info, dict):
|
if isinstance(processor_info, dict):
|
||||||
|
@ -178,29 +237,51 @@ def load_processors(processors_dir="processors"):
|
||||||
elif isinstance(processor_info, list):
|
elif isinstance(processor_info, list):
|
||||||
info_list = processor_info
|
info_list = processor_info
|
||||||
else:
|
else:
|
||||||
print(f" Advertencia: get_processor_info en {full_module_name} devolvió tipo inesperado. Se ignora.")
|
print(
|
||||||
|
f" Advertencia: get_processor_info en {full_module_name} devolvió tipo inesperado. Se ignora."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for info in info_list:
|
for info in info_list:
|
||||||
if isinstance(info, dict) and 'type_name' in info and 'processor_func' in info:
|
if (
|
||||||
type_name = info['type_name'].lower()
|
isinstance(info, dict)
|
||||||
processor_func = info['processor_func']
|
and "type_name" in info
|
||||||
|
and "processor_func" in info
|
||||||
|
):
|
||||||
|
type_name = info["type_name"].lower()
|
||||||
|
processor_func = info["processor_func"]
|
||||||
# Obtener prioridad, usar default si no existe
|
# Obtener prioridad, usar default si no existe
|
||||||
priority = info.get('priority', default_priority)
|
priority = info.get("priority", default_priority)
|
||||||
|
|
||||||
if callable(processor_func):
|
if callable(processor_func):
|
||||||
if type_name in processor_map:
|
if type_name in processor_map:
|
||||||
print(f" Advertencia: '{type_name}' en {full_module_name} sobrescribe definición anterior.")
|
print(
|
||||||
|
f" Advertencia: '{type_name}' en {full_module_name} sobrescribe definición anterior."
|
||||||
|
)
|
||||||
processor_map[type_name] = processor_func
|
processor_map[type_name] = processor_func
|
||||||
# Añadir a la lista para ordenar
|
# Añadir a la lista para ordenar
|
||||||
processor_list_unsorted.append({'priority': priority, 'type_name': type_name, 'func': processor_func})
|
processor_list_unsorted.append(
|
||||||
print(f" - Cargado '{type_name}' (Prio: {priority}) desde {module_name_rel}.py")
|
{
|
||||||
|
"priority": priority,
|
||||||
|
"type_name": type_name,
|
||||||
|
"func": processor_func,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" - Cargado '{type_name}' (Prio: {priority}) desde {module_name_rel}.py"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f" Advertencia: 'processor_func' para '{type_name}' en {full_module_name} no es callable.")
|
print(
|
||||||
|
f" Advertencia: 'processor_func' para '{type_name}' en {full_module_name} no es callable."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f" Advertencia: Entrada inválida en {full_module_name}: {info}")
|
print(
|
||||||
|
f" Advertencia: Entrada inválida en {full_module_name}: {info}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f" Advertencia: Módulo {module_name_rel}.py no tiene 'get_processor_info'.")
|
print(
|
||||||
|
f" Advertencia: Módulo {module_name_rel}.py no tiene 'get_processor_info'."
|
||||||
|
)
|
||||||
|
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f"Error importando {full_module_name}: {e}")
|
print(f"Error importando {full_module_name}: {e}")
|
||||||
|
@ -209,22 +290,24 @@ def load_processors(processors_dir="processors"):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
# Ordenar la lista por prioridad (menor primero)
|
# Ordenar la lista por prioridad (menor primero)
|
||||||
processor_list_sorted = sorted(processor_list_unsorted, key=lambda x: x['priority'])
|
processor_list_sorted = sorted(processor_list_unsorted, key=lambda x: x["priority"])
|
||||||
|
|
||||||
print(f"\nTotal de tipos de procesadores cargados: {len(processor_map)}")
|
print(f"\nTotal de tipos de procesadores cargados: {len(processor_map)}")
|
||||||
print(f"Orden de procesamiento por prioridad: {[item['type_name'] for item in processor_list_sorted]}")
|
print(
|
||||||
|
f"Orden de procesamiento por prioridad: {[item['type_name'] for item in processor_list_sorted]}"
|
||||||
|
)
|
||||||
|
|
||||||
# Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada
|
# Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada
|
||||||
return processor_map, processor_list_sorted
|
return processor_map, processor_list_sorted
|
||||||
|
|
||||||
|
|
||||||
# --- Bucle Principal de Procesamiento (Modificado para STL) ---
|
# --- Bucle Principal de Procesamiento (Modificado para STL) ---
|
||||||
def process_json_to_scl(json_filepath):
|
def process_json_to_scl(json_filepath):
|
||||||
"""
|
"""
|
||||||
Lee el JSON simplificado, aplica los procesadores dinámicamente cargados
|
Lee JSON simplificado, aplica procesadores dinámicos (ignorando redes STL y bloques DB),
|
||||||
siguiendo un orden de prioridad (ignorando redes STL), y guarda el JSON procesado.
|
y guarda JSON procesado.
|
||||||
"""
|
"""
|
||||||
global data # Necesario para que load_processors y process_group_ifs (definidas fuera) puedan acceder a ella.
|
global data
|
||||||
# Considerar pasar 'data' como argumento si es posible refactorizar.
|
|
||||||
|
|
||||||
if not os.path.exists(json_filepath):
|
if not os.path.exists(json_filepath):
|
||||||
print(f"Error: JSON no encontrado: {json_filepath}")
|
print(f"Error: JSON no encontrado: {json_filepath}")
|
||||||
|
@ -232,48 +315,83 @@ def process_json_to_scl(json_filepath):
|
||||||
print(f"Cargando JSON desde: {json_filepath}")
|
print(f"Cargando JSON desde: {json_filepath}")
|
||||||
try:
|
try:
|
||||||
with open(json_filepath, "r", encoding="utf-8") as f:
|
with open(json_filepath, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f) # Carga en 'data' global
|
data = json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al cargar JSON: {e}")
|
print(f"Error al cargar JSON: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Carga dinámica de procesadores ---
|
# --- Obtener lenguaje del bloque principal ---
|
||||||
|
block_language = data.get("language", "Unknown")
|
||||||
|
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
|
||||||
|
print(f"Procesando bloque tipo: {block_type}, Lenguaje principal: {block_language}")
|
||||||
|
|
||||||
|
# --- SI ES UN DB, SALTAR EL PROCESAMIENTO LÓGICO ---
|
||||||
|
if block_language == "DB":
|
||||||
|
print(
|
||||||
|
"INFO: El bloque es un Data Block (DB). Saltando procesamiento lógico de x2."
|
||||||
|
)
|
||||||
|
# Simplemente guardamos una copia (o el mismo archivo si no se requiere sufijo)
|
||||||
|
output_filename = json_filepath.replace(
|
||||||
|
"_simplified.json", "_simplified_processed.json"
|
||||||
|
)
|
||||||
|
print(f"Guardando JSON de DB (sin cambios lógicos) en: {output_filename}")
|
||||||
|
try:
|
||||||
|
with open(output_filename, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
|
print("Guardado de DB completado.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error Crítico al guardar JSON del DB: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return # <<< SALIR TEMPRANO PARA DBs
|
||||||
|
|
||||||
|
# --- SI NO ES DB, CONTINUAR CON EL PROCESAMIENTO LÓGICO (FC/FB) ---
|
||||||
|
print("INFO: El bloque es FC/FB. Iniciando procesamiento lógico...")
|
||||||
|
|
||||||
script_dir = os.path.dirname(__file__)
|
script_dir = os.path.dirname(__file__)
|
||||||
processors_dir_path = os.path.join(script_dir, 'processors')
|
processors_dir_path = os.path.join(script_dir, "processors")
|
||||||
processor_map, sorted_processors = load_processors(processors_dir_path)
|
processor_map, sorted_processors = load_processors(processors_dir_path)
|
||||||
if not processor_map:
|
if not processor_map:
|
||||||
print("Error crítico: No se cargaron procesadores. Abortando.")
|
print("Error crítico: No se cargaron procesadores. Abortando.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Crear mapas de acceso por red ---
|
|
||||||
network_access_maps = {}
|
network_access_maps = {}
|
||||||
# (La lógica para llenar network_access_maps no cambia, puedes copiarla de tu original)
|
# Crear mapas de acceso por red (copiado/adaptado de versión anterior)
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
net_id = network["id"]
|
net_id = network["id"]
|
||||||
current_access_map = {}
|
current_access_map = {}
|
||||||
for instr in network.get("logic", []):
|
for instr in network.get("logic", []):
|
||||||
for _, source in instr.get("inputs", {}).items():
|
for _, source in instr.get("inputs", {}).items():
|
||||||
sources_to_check = (source if isinstance(source, list) else ([source] if isinstance(source, dict) else []))
|
sources_to_check = (
|
||||||
|
source
|
||||||
|
if isinstance(source, list)
|
||||||
|
else ([source] if isinstance(source, dict) else [])
|
||||||
|
)
|
||||||
for src in sources_to_check:
|
for src in sources_to_check:
|
||||||
if (isinstance(src, dict) and src.get("uid") and src.get("type") in ["variable", "constant"]):
|
if (
|
||||||
|
isinstance(src, dict)
|
||||||
|
and src.get("uid")
|
||||||
|
and src.get("type") in ["variable", "constant"]
|
||||||
|
):
|
||||||
current_access_map[src["uid"]] = src
|
current_access_map[src["uid"]] = src
|
||||||
for _, dest_list in instr.get("outputs", {}).items():
|
for _, dest_list in instr.get("outputs", {}).items():
|
||||||
if isinstance(dest_list, list):
|
if isinstance(dest_list, list):
|
||||||
for dest in dest_list:
|
for dest in dest_list:
|
||||||
if (isinstance(dest, dict) and dest.get("uid") and dest.get("type") in ["variable", "constant"]):
|
if (
|
||||||
|
isinstance(dest, dict)
|
||||||
|
and dest.get("uid")
|
||||||
|
and dest.get("type") in ["variable", "constant"]
|
||||||
|
):
|
||||||
current_access_map[dest["uid"]] = dest
|
current_access_map[dest["uid"]] = dest
|
||||||
network_access_maps[net_id] = current_access_map
|
network_access_maps[net_id] = current_access_map
|
||||||
|
|
||||||
# --- Inicializar mapa SymPy y SymbolManager ---
|
|
||||||
symbol_manager = SymbolManager()
|
symbol_manager = SymbolManager()
|
||||||
sympy_map = {}
|
sympy_map = {}
|
||||||
|
|
||||||
max_passes = 30
|
max_passes = 30
|
||||||
passes = 0
|
passes = 0
|
||||||
processing_complete = False
|
processing_complete = False
|
||||||
|
|
||||||
print("\n--- Iniciando Bucle de Procesamiento Iterativo (con SymPy y prioridad) ---")
|
print("\n--- Iniciando Bucle de Procesamiento Iterativo (FC/FB) ---")
|
||||||
while passes < max_passes and not processing_complete:
|
while passes < max_passes and not processing_complete:
|
||||||
passes += 1
|
passes += 1
|
||||||
made_change_in_base_pass = False
|
made_change_in_base_pass = False
|
||||||
|
@ -284,90 +402,99 @@ def process_json_to_scl(json_filepath):
|
||||||
|
|
||||||
# --- FASE 1: Procesadores Base (Ignorando STL) ---
|
# --- FASE 1: Procesadores Base (Ignorando STL) ---
|
||||||
print(f" Fase 1 (SymPy Base - Orden por Prioridad):")
|
print(f" Fase 1 (SymPy Base - Orden por Prioridad):")
|
||||||
num_sympy_processed_this_pass = 0
|
num_sympy_processed_this_pass = 0 # Resetear contador para el pase
|
||||||
for processor_info in sorted_processors:
|
for processor_info in sorted_processors:
|
||||||
current_type_name = processor_info['type_name']
|
current_type_name = processor_info["type_name"]
|
||||||
func_to_call = processor_info['func']
|
func_to_call = processor_info["func"]
|
||||||
|
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
network_id = network["id"]
|
network_id = network["id"]
|
||||||
network_lang = network.get("language", "LAD") # Obtener lenguaje de la red
|
network_lang = network.get("language", "LAD")
|
||||||
|
|
||||||
# *** IGNORAR REDES STL EN ESTA FASE ***
|
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
continue # Saltar al siguiente network
|
continue # Saltar STL
|
||||||
|
|
||||||
access_map = network_access_maps.get(network_id, {})
|
access_map = network_access_maps.get(network_id, {})
|
||||||
network_logic = network.get("logic", [])
|
network_logic = network.get("logic", [])
|
||||||
|
|
||||||
for instruction in network_logic:
|
for instruction in network_logic:
|
||||||
instr_uid = instruction.get("instruction_uid")
|
instr_uid = instruction.get("instruction_uid")
|
||||||
instr_type_original = instruction.get("type", "Unknown")
|
instr_type_original = instruction.get("type", "Unknown")
|
||||||
|
if (
|
||||||
# Saltar si ya procesado, error, agrupado o es chunk STL/SCL/Unsupported
|
instr_type_original.endswith(SCL_SUFFIX)
|
||||||
if (instr_type_original.endswith(SCL_SUFFIX)
|
|
||||||
or "_error" in instr_type_original
|
or "_error" in instr_type_original
|
||||||
or instruction.get("grouped", False)
|
or instruction.get("grouped", False)
|
||||||
or instr_type_original in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]):
|
or instr_type_original
|
||||||
|
in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Determinar tipo efectivo (como antes)
|
|
||||||
lookup_key = instr_type_original.lower()
|
lookup_key = instr_type_original.lower()
|
||||||
effective_type_name = lookup_key
|
effective_type_name = lookup_key
|
||||||
if instr_type_original == "Call":
|
if instr_type_original == "Call":
|
||||||
block_type = instruction.get("block_type", "").upper()
|
block_type = instruction.get("block_type", "").upper()
|
||||||
if block_type == "FC": effective_type_name = "call_fc"
|
if block_type == "FC":
|
||||||
elif block_type == "FB": effective_type_name = "call_fb"
|
effective_type_name = "call_fc"
|
||||||
|
elif block_type == "FB":
|
||||||
|
effective_type_name = "call_fb"
|
||||||
|
|
||||||
# Llamar al procesador si coincide el tipo
|
|
||||||
if effective_type_name == current_type_name:
|
if effective_type_name == current_type_name:
|
||||||
try:
|
try:
|
||||||
# Pasa sympy_map, symbol_manager y data
|
changed = func_to_call(
|
||||||
changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data)
|
instruction, network_id, sympy_map, symbol_manager, data
|
||||||
|
)
|
||||||
if changed:
|
if changed:
|
||||||
made_change_in_base_pass = True
|
made_change_in_base_pass = True
|
||||||
num_sympy_processed_this_pass += 1
|
num_sympy_processed_this_pass += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}")
|
print(
|
||||||
|
f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}"
|
||||||
|
)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
instruction["scl"] = f"// ERROR en SymPy procesador base: {e}"
|
instruction["scl"] = (
|
||||||
|
f"// ERROR en SymPy procesador base: {e}"
|
||||||
|
)
|
||||||
instruction["type"] = instr_type_original + "_error"
|
instruction["type"] = instr_type_original + "_error"
|
||||||
made_change_in_base_pass = True # Marcar cambio aunque sea error
|
made_change_in_base_pass = True
|
||||||
print(f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy.")
|
print(
|
||||||
|
f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy."
|
||||||
|
)
|
||||||
|
|
||||||
# --- FASE 2: Agrupación IF (Ignorando STL) ---
|
# --- FASE 2: Agrupación IF (Ignorando STL) ---
|
||||||
if made_change_in_base_pass or passes == 1:
|
if (
|
||||||
|
made_change_in_base_pass or passes == 1
|
||||||
|
): # Ejecutar siempre en el primer pase
|
||||||
print(f" Fase 2 (Agrupación IF con Simplificación):")
|
print(f" Fase 2 (Agrupación IF con Simplificación):")
|
||||||
num_grouped_this_pass = 0
|
num_grouped_this_pass = 0 # Resetear contador para el pase
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
network_id = network["id"]
|
network_id = network["id"]
|
||||||
network_lang = network.get("language", "LAD") # Obtener lenguaje
|
network_lang = network.get("language", "LAD")
|
||||||
|
|
||||||
# *** IGNORAR REDES STL EN ESTA FASE ***
|
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
continue # Saltar red STL
|
continue # Saltar STL
|
||||||
|
|
||||||
network_logic = network.get("logic", [])
|
network_logic = network.get("logic", [])
|
||||||
for instruction in network_logic:
|
for instruction in network_logic:
|
||||||
try:
|
try:
|
||||||
# Llama a process_group_ifs (que necesita acceso a 'data' global o pasado)
|
group_changed = process_group_ifs(
|
||||||
group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data)
|
instruction, network_id, sympy_map, symbol_manager, data
|
||||||
|
)
|
||||||
if group_changed:
|
if group_changed:
|
||||||
made_change_in_group_pass = True
|
made_change_in_group_pass = True
|
||||||
num_grouped_this_pass += 1
|
num_grouped_this_pass += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}")
|
print(
|
||||||
|
f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}"
|
||||||
|
)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(f" -> {num_grouped_this_pass} agrupaciones realizadas (en redes no STL).")
|
print(
|
||||||
|
f" -> {num_grouped_this_pass} agrupaciones realizadas (en redes no STL)."
|
||||||
|
)
|
||||||
|
|
||||||
# --- Comprobar si se completó el procesamiento ---
|
# --- Comprobar si se completó el procesamiento ---
|
||||||
if not made_change_in_base_pass and not made_change_in_group_pass:
|
if not made_change_in_base_pass and not made_change_in_group_pass:
|
||||||
print(f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---")
|
print(
|
||||||
|
f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---"
|
||||||
|
)
|
||||||
processing_complete = True
|
processing_complete = True
|
||||||
else:
|
else:
|
||||||
print(f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando...")
|
print(
|
||||||
|
f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando..."
|
||||||
|
)
|
||||||
|
|
||||||
# --- Comprobar límite de pases ---
|
# --- Comprobar límite de pases ---
|
||||||
if passes == max_passes and not processing_complete:
|
if passes == max_passes and not processing_complete:
|
||||||
|
@ -376,46 +503,51 @@ def process_json_to_scl(json_filepath):
|
||||||
# --- FIN BUCLE ITERATIVO ---
|
# --- FIN BUCLE ITERATIVO ---
|
||||||
|
|
||||||
# --- Verificación Final (Ajustada para RAW_STL_CHUNK) ---
|
# --- Verificación Final (Ajustada para RAW_STL_CHUNK) ---
|
||||||
print("\n--- Verificación Final de Instrucciones No Procesadas ---")
|
print("\n--- Verificación Final de Instrucciones No Procesadas (FC/FB) ---")
|
||||||
unprocessed_count = 0
|
unprocessed_count = 0
|
||||||
unprocessed_details = []
|
unprocessed_details = []
|
||||||
# Añadir RAW_STL_CHUNK a los tipos ignorados
|
ignored_types = [
|
||||||
ignored_types = ['raw_scl_chunk', 'unsupported_lang', 'raw_stl_chunk'] # Añadido raw_stl_chunk
|
"raw_scl_chunk",
|
||||||
|
"unsupported_lang",
|
||||||
|
"raw_stl_chunk",
|
||||||
|
] # Añadido raw_stl_chunk
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
network_id = network.get("id", "Unknown ID")
|
network_id = network.get("id", "Unknown ID")
|
||||||
network_title = network.get("title", f"Network {network_id}")
|
network_title = network.get("title", f"Network {network_id}")
|
||||||
network_lang = network.get("language", "LAD") # Obtener lenguaje
|
network_lang = network.get("language", "LAD")
|
||||||
|
|
||||||
# No verificar instrucciones dentro de redes STL, ya que no se procesan
|
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
continue
|
continue # No verificar redes STL
|
||||||
|
|
||||||
for instruction in network.get("logic", []):
|
for instruction in network.get("logic", []):
|
||||||
instr_uid = instruction.get("instruction_uid", "Unknown UID")
|
instr_uid = instruction.get("instruction_uid", "Unknown UID")
|
||||||
instr_type = instruction.get("type", "Unknown Type")
|
instr_type = instruction.get("type", "Unknown Type")
|
||||||
is_grouped = instruction.get("grouped", False)
|
is_grouped = instruction.get("grouped", False)
|
||||||
|
if (
|
||||||
# Condición revisada para ignorar los chunks crudos
|
not instr_type.endswith(SCL_SUFFIX)
|
||||||
if (not instr_type.endswith(SCL_SUFFIX) and
|
and "_error" not in instr_type
|
||||||
"_error" not in instr_type and
|
and not is_grouped
|
||||||
not is_grouped and
|
and instr_type.lower() not in ignored_types
|
||||||
instr_type.lower() not in ignored_types): # Verifica contra lista actualizada
|
):
|
||||||
unprocessed_count += 1
|
unprocessed_count += 1
|
||||||
unprocessed_details.append(
|
unprocessed_details.append(
|
||||||
f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), "
|
f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), "
|
||||||
f"Instrucción UID: {instr_uid}, Tipo: '{instr_type}'"
|
f"Instrucción UID: {instr_uid}, Tipo: '{instr_type}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
if unprocessed_count > 0:
|
if unprocessed_count > 0:
|
||||||
print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:")
|
print(
|
||||||
for detail in unprocessed_details: print(detail)
|
f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:"
|
||||||
|
)
|
||||||
|
for detail in unprocessed_details:
|
||||||
|
print(detail)
|
||||||
else:
|
else:
|
||||||
print("INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas.")
|
print(
|
||||||
|
"INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas."
|
||||||
|
)
|
||||||
|
|
||||||
# --- Guardar JSON Final ---
|
# --- Guardar JSON Final ---
|
||||||
output_filename = json_filepath.replace("_simplified.json", "_simplified_processed.json")
|
output_filename = json_filepath.replace(
|
||||||
print(f"\nGuardando JSON procesado en: {output_filename}")
|
"_simplified.json", "_simplified_processed.json"
|
||||||
|
)
|
||||||
|
print(f"\nGuardando JSON procesado (FC/FB) en: {output_filename}")
|
||||||
try:
|
try:
|
||||||
with open(output_filename, "w", encoding="utf-8") as f:
|
with open(output_filename, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
|
@ -424,6 +556,7 @@ def process_json_to_scl(json_filepath):
|
||||||
print(f"Error Crítico al guardar JSON procesado: {e}")
|
print(f"Error Crítico al guardar JSON procesado: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
# --- Ejecución (sin cambios) ---
|
# --- Ejecución (sin cambios) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Imports necesarios solo para la ejecución como script principal
|
# Imports necesarios solo para la ejecución como script principal
|
||||||
|
@ -446,7 +579,9 @@ if __name__ == "__main__":
|
||||||
# Verificar si el archivo XML original existe (como referencia, útil para depuración)
|
# Verificar si el archivo XML original existe (como referencia, útil para depuración)
|
||||||
# No es estrictamente necesario para la lógica aquí, pero ayuda a confirmar
|
# No es estrictamente necesario para la lógica aquí, pero ayuda a confirmar
|
||||||
if not os.path.exists(source_xml_file):
|
if not os.path.exists(source_xml_file):
|
||||||
print(f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente.")
|
print(
|
||||||
|
f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente."
|
||||||
|
)
|
||||||
# No salir necesariamente, pero es bueno saberlo.
|
# No salir necesariamente, pero es bueno saberlo.
|
||||||
|
|
||||||
# Derivar nombre del archivo JSON de entrada (_simplified.json)
|
# Derivar nombre del archivo JSON de entrada (_simplified.json)
|
||||||
|
@ -456,15 +591,22 @@ if __name__ == "__main__":
|
||||||
input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json")
|
input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json")
|
||||||
|
|
||||||
# Determinar el nombre esperado del archivo JSON procesado de salida
|
# Determinar el nombre esperado del archivo JSON procesado de salida
|
||||||
output_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.json")
|
output_json_file = os.path.join(
|
||||||
|
input_dir, f"{xml_filename_base}_simplified_processed.json"
|
||||||
print(f"(x2) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'")
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"(x2) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'"
|
||||||
|
)
|
||||||
|
|
||||||
# Verificar si el archivo JSON de entrada (_simplified.json) EXISTE antes de procesar
|
# Verificar si el archivo JSON de entrada (_simplified.json) EXISTE antes de procesar
|
||||||
if not os.path.exists(input_json_file):
|
if not os.path.exists(input_json_file):
|
||||||
print(f"Error Fatal (x2): El archivo de entrada JSON simplificado no existe: '{input_json_file}'")
|
print(
|
||||||
print(f"Asegúrate de que 'x1_to_json.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'.")
|
f"Error Fatal (x2): El archivo de entrada JSON simplificado no existe: '{input_json_file}'"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"Asegúrate de que 'x1_to_json.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'."
|
||||||
|
)
|
||||||
sys.exit(1) # Salir si el archivo necesario no está
|
sys.exit(1) # Salir si el archivo necesario no está
|
||||||
else:
|
else:
|
||||||
# Llamar a la función principal de procesamiento del script
|
# Llamar a la función principal de procesamiento del script
|
||||||
|
@ -472,7 +614,10 @@ if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
process_json_to_scl(input_json_file)
|
process_json_to_scl(input_json_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}")
|
print(
|
||||||
|
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}"
|
||||||
|
)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
|
@ -11,284 +11,475 @@ import traceback # Importar traceback para errores
|
||||||
try:
|
try:
|
||||||
# Intenta importar desde el paquete de procesadores si está estructurado así
|
# Intenta importar desde el paquete de procesadores si está estructurado así
|
||||||
from processors.processor_utils import format_variable_name
|
from processors.processor_utils import format_variable_name
|
||||||
|
|
||||||
# Definir SCL_SUFFIX aquí o importarlo si está centralizado
|
# Definir SCL_SUFFIX aquí o importarlo si está centralizado
|
||||||
SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py
|
SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py
|
||||||
GROUPED_COMMENT = "// Logic included in grouped IF" # Opcional, si se usa para filtrar
|
GROUPED_COMMENT = (
|
||||||
|
"// Logic included in grouped IF" # Opcional, si se usa para filtrar
|
||||||
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils.")
|
print(
|
||||||
print("Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!).")
|
"Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils."
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!)."
|
||||||
|
)
|
||||||
|
|
||||||
# Implementación local BÁSICA como fallback (MENOS RECOMENDADA)
|
# Implementación local BÁSICA como fallback (MENOS RECOMENDADA)
|
||||||
def format_variable_name(name):
|
def format_variable_name(name):
|
||||||
if not name: return "_INVALID_NAME_"
|
if not name:
|
||||||
if name.startswith('"') and name.endswith('"'): return name # Mantener comillas
|
return "_INVALID_NAME_"
|
||||||
|
if name.startswith('"') and name.endswith('"'):
|
||||||
|
return name # Mantener comillas
|
||||||
prefix = "#" if name.startswith("#") else ""
|
prefix = "#" if name.startswith("#") else ""
|
||||||
if prefix: name = name[1:]
|
if prefix:
|
||||||
if name and name[0].isdigit(): name = "_" + name
|
name = name[1:]
|
||||||
|
if name and name[0].isdigit():
|
||||||
|
name = "_" + name
|
||||||
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
||||||
return prefix + name
|
return prefix + name
|
||||||
|
|
||||||
SCL_SUFFIX = "_sympy_processed"
|
SCL_SUFFIX = "_sympy_processed"
|
||||||
GROUPED_COMMENT = "// Logic included in grouped IF"
|
GROUPED_COMMENT = "// Logic included in grouped IF"
|
||||||
|
|
||||||
|
|
||||||
# --- Función Principal de Generación SCL ---
|
# para formatear valores iniciales
|
||||||
|
def format_scl_start_value(value, datatype):
|
||||||
|
"""Formatea un valor para la inicialización SCL según el tipo."""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
datatype_lower = datatype.lower() if datatype else ""
|
||||||
|
value_str = str(value)
|
||||||
|
|
||||||
|
if "bool" in datatype_lower:
|
||||||
|
return "TRUE" if value_str.lower() == "true" else "FALSE"
|
||||||
|
elif "string" in datatype_lower:
|
||||||
|
escaped_value = value_str.replace("'", "''")
|
||||||
|
if escaped_value.startswith("'") and escaped_value.endswith("'"):
|
||||||
|
escaped_value = escaped_value[1:-1]
|
||||||
|
return f"'{escaped_value}'"
|
||||||
|
elif "char" in datatype_lower: # Añadido Char
|
||||||
|
escaped_value = value_str.replace("'", "''")
|
||||||
|
if escaped_value.startswith("'") and escaped_value.endswith("'"):
|
||||||
|
escaped_value = escaped_value[1:-1]
|
||||||
|
return f"'{escaped_value}'"
|
||||||
|
elif any(
|
||||||
|
t in datatype_lower
|
||||||
|
for t in [
|
||||||
|
"int",
|
||||||
|
"byte",
|
||||||
|
"word",
|
||||||
|
"dint",
|
||||||
|
"dword",
|
||||||
|
"lint",
|
||||||
|
"lword",
|
||||||
|
"sint",
|
||||||
|
"usint",
|
||||||
|
"uint",
|
||||||
|
"udint",
|
||||||
|
"ulint",
|
||||||
|
]
|
||||||
|
): # Ampliado
|
||||||
|
try:
|
||||||
|
return str(int(value_str))
|
||||||
|
except ValueError:
|
||||||
|
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str):
|
||||||
|
return value_str
|
||||||
|
return f"'{value_str}'" # O como string si no es entero ni símbolo
|
||||||
|
elif "real" in datatype_lower or "lreal" in datatype_lower:
|
||||||
|
try:
|
||||||
|
f_val = float(value_str)
|
||||||
|
s_val = str(f_val)
|
||||||
|
if "." not in s_val and "e" not in s_val.lower():
|
||||||
|
s_val += ".0"
|
||||||
|
return s_val
|
||||||
|
except ValueError:
|
||||||
|
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str):
|
||||||
|
return value_str
|
||||||
|
return f"'{value_str}'"
|
||||||
|
elif "time" in datatype_lower: # Añadido Time, S5Time, LTime
|
||||||
|
# Quitar T#, LT#, S5T# si existen
|
||||||
|
prefix = ""
|
||||||
|
if value_str.upper().startswith("T#"):
|
||||||
|
prefix = "T#"
|
||||||
|
value_str = value_str[2:]
|
||||||
|
elif value_str.upper().startswith("LT#"):
|
||||||
|
prefix = "LT#"
|
||||||
|
value_str = value_str[3:]
|
||||||
|
elif value_str.upper().startswith("S5T#"):
|
||||||
|
prefix = "S5T#"
|
||||||
|
value_str = value_str[4:]
|
||||||
|
# Devolver con el prefijo correcto o T# por defecto si no había
|
||||||
|
if prefix:
|
||||||
|
return f"{prefix}{value_str}"
|
||||||
|
elif "s5time" in datatype_lower:
|
||||||
|
return f"S5T#{value_str}"
|
||||||
|
elif "ltime" in datatype_lower:
|
||||||
|
return f"LT#{value_str}"
|
||||||
|
else:
|
||||||
|
return f"T#{value_str}" # Default a TIME
|
||||||
|
elif "date" in datatype_lower: # Añadido Date, DT, TOD
|
||||||
|
if value_str.upper().startswith("D#"):
|
||||||
|
return value_str
|
||||||
|
elif "dt" in datatype_lower or "date_and_time" in datatype_lower:
|
||||||
|
if value_str.upper().startswith("DT#"):
|
||||||
|
return value_str
|
||||||
|
else:
|
||||||
|
return f"DT#{value_str}" # Añadir prefijo DT#
|
||||||
|
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
|
||||||
|
if value_str.upper().startswith("TOD#"):
|
||||||
|
return value_str
|
||||||
|
else:
|
||||||
|
return f"TOD#{value_str}" # Añadir prefijo TOD#
|
||||||
|
else:
|
||||||
|
return f"D#{value_str}" # Default a Date
|
||||||
|
# Fallback genérico
|
||||||
|
else:
|
||||||
|
if re.match(
|
||||||
|
r'^[a-zA-Z_][a-zA-Z0-9_."#\[\]]+$', value_str
|
||||||
|
): # Permitir más caracteres en símbolos/tipos
|
||||||
|
# Si es un UDT o Struct complejo, podría venir con comillas, quitarlas
|
||||||
|
if value_str.startswith('"') and value_str.endswith('"'):
|
||||||
|
return value_str[1:-1]
|
||||||
|
return value_str
|
||||||
|
else:
|
||||||
|
escaped_value = value_str.replace("'", "''")
|
||||||
|
return f"'{escaped_value}'"
|
||||||
|
|
||||||
|
|
||||||
|
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
|
||||||
|
def generate_scl_declarations(variables, indent_level=1):
|
||||||
|
"""Genera las líneas SCL para declarar variables, structs y arrays."""
|
||||||
|
scl_lines = []
|
||||||
|
indent = " " * indent_level
|
||||||
|
for var in variables:
|
||||||
|
var_name_scl = format_variable_name(var.get("name"))
|
||||||
|
var_dtype_raw = var.get("datatype", "VARIANT")
|
||||||
|
# Limpiar comillas de tipos de datos UDT ("MyType" -> MyType)
|
||||||
|
var_dtype = (
|
||||||
|
var_dtype_raw.strip('"')
|
||||||
|
if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"')
|
||||||
|
else var_dtype_raw
|
||||||
|
)
|
||||||
|
|
||||||
|
var_comment = var.get("comment")
|
||||||
|
start_value = var.get("start_value")
|
||||||
|
children = var.get("children") # Para structs
|
||||||
|
array_elements = var.get("array_elements") # Para arrays
|
||||||
|
|
||||||
|
# Manejar tipos de datos Array especiales
|
||||||
|
array_match = re.match(r"(Array\[.*\]\s+of\s+)(.*)", var_dtype, re.IGNORECASE)
|
||||||
|
base_type_for_init = var_dtype
|
||||||
|
declaration_dtype = var_dtype
|
||||||
|
if array_match:
|
||||||
|
array_prefix = array_match.group(1)
|
||||||
|
base_type_raw = array_match.group(2).strip()
|
||||||
|
# Limpiar comillas del tipo base del array
|
||||||
|
base_type_for_init = (
|
||||||
|
base_type_raw.strip('"')
|
||||||
|
if base_type_raw.startswith('"') and base_type_raw.endswith('"')
|
||||||
|
else base_type_raw
|
||||||
|
)
|
||||||
|
declaration_dtype = (
|
||||||
|
f'{array_prefix}"{base_type_for_init}"'
|
||||||
|
if '"' not in base_type_raw
|
||||||
|
else f"{array_prefix}{base_type_raw}"
|
||||||
|
) # Reconstruir con comillas si es UDT
|
||||||
|
|
||||||
|
# Reconstruir declaración con comillas si es UDT y no array
|
||||||
|
elif (
|
||||||
|
not array_match and var_dtype != base_type_for_init
|
||||||
|
): # Es un tipo que necesita comillas (UDT)
|
||||||
|
declaration_dtype = f'"{var_dtype}"'
|
||||||
|
|
||||||
|
declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}"
|
||||||
|
init_value = None
|
||||||
|
|
||||||
|
# ---- Arrays ----
|
||||||
|
if array_elements:
|
||||||
|
# Ordenar índices (asumiendo que son numéricos)
|
||||||
|
try:
|
||||||
|
sorted_indices = sorted(array_elements.keys(), key=int)
|
||||||
|
except ValueError:
|
||||||
|
sorted_indices = sorted(
|
||||||
|
array_elements.keys()
|
||||||
|
) # Fallback a orden alfabético
|
||||||
|
|
||||||
|
init_values = [
|
||||||
|
format_scl_start_value(array_elements[idx], base_type_for_init)
|
||||||
|
for idx in sorted_indices
|
||||||
|
]
|
||||||
|
valid_inits = [v for v in init_values if v is not None]
|
||||||
|
if valid_inits:
|
||||||
|
init_value = f"[{', '.join(valid_inits)}]"
|
||||||
|
|
||||||
|
# ---- Structs ----
|
||||||
|
elif children:
|
||||||
|
# No añadir comentario // Struct aquí, es redundante
|
||||||
|
scl_lines.append(declaration_line) # Añadir línea de declaración base
|
||||||
|
scl_lines.append(f"{indent}STRUCT")
|
||||||
|
scl_lines.extend(generate_scl_declarations(children, indent_level + 1))
|
||||||
|
scl_lines.append(f"{indent}END_STRUCT;")
|
||||||
|
if var_comment:
|
||||||
|
scl_lines.append(f"{indent}// {var_comment}")
|
||||||
|
scl_lines.append("") # Línea extra
|
||||||
|
continue # Saltar resto para Struct
|
||||||
|
|
||||||
|
# ---- Tipos Simples ----
|
||||||
|
else:
|
||||||
|
if start_value is not None:
|
||||||
|
init_value = format_scl_start_value(start_value, var_dtype)
|
||||||
|
|
||||||
|
# Añadir inicialización si existe
|
||||||
|
if init_value:
|
||||||
|
declaration_line += f" := {init_value}"
|
||||||
|
declaration_line += ";"
|
||||||
|
if var_comment:
|
||||||
|
declaration_line += f" // {var_comment}"
|
||||||
|
scl_lines.append(declaration_line)
|
||||||
|
|
||||||
|
return scl_lines
|
||||||
|
|
||||||
|
|
||||||
|
# --- Función Principal de Generación SCL ---
|
||||||
def generate_scl(processed_json_filepath, output_scl_filepath):
|
def generate_scl(processed_json_filepath, output_scl_filepath):
|
||||||
"""Genera un archivo SCL a partir del JSON procesado por x2_process (versión SymPy)."""
|
"""Genera un archivo SCL a partir del JSON procesado (FC/FB o DB)."""
|
||||||
|
|
||||||
if not os.path.exists(processed_json_filepath):
|
if not os.path.exists(processed_json_filepath):
|
||||||
print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'")
|
print(
|
||||||
|
f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Cargando JSON procesado desde: {processed_json_filepath}")
|
print(f"Cargando JSON procesado desde: {processed_json_filepath}")
|
||||||
try:
|
try:
|
||||||
with open(processed_json_filepath, 'r', encoding='utf-8') as f:
|
with open(processed_json_filepath, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al cargar o parsear JSON: {e}")
|
print(f"Error al cargar o parsear JSON: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Extracción de Información del Bloque ---
|
# --- Extracción de Información del Bloque (Común) ---
|
||||||
block_name = data.get('block_name', 'UnknownBlock')
|
block_name = data.get("block_name", "UnknownBlock")
|
||||||
block_number = data.get('block_number')
|
block_number = data.get("block_number")
|
||||||
block_lang_original = data.get('language', 'LAD') # Lenguaje original
|
block_lang_original = data.get("language", "Unknown") # Será "DB" para Data Blocks
|
||||||
# Determinar tipo de bloque SCL (Asumir FB si no se especifica)
|
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
|
||||||
# Idealmente, x1_to_json.py guardaría esto en data['block_type_scl'] = 'FC' o 'FB'
|
block_comment = data.get("block_comment", "")
|
||||||
block_type_scl = data.get('block_type_scl', 'FUNCTION_BLOCK')
|
scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
|
||||||
block_comment = data.get('block_comment', '')
|
print(
|
||||||
|
f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name}, Lang: {block_lang_original})"
|
||||||
# Usar format_variable_name para el nombre del bloque en SCL
|
)
|
||||||
scl_block_name = format_variable_name(block_name)
|
|
||||||
print(f"Generando SCL para {block_type_scl}: {scl_block_name} (Original: {block_name})")
|
|
||||||
|
|
||||||
# --- Identificación de Variables Temporales y Estáticas ---
|
|
||||||
# La detección basada en regex sobre el SCL final debería seguir funcionando
|
|
||||||
temp_vars = set()
|
|
||||||
stat_vars = set()
|
|
||||||
# Regex mejorado para capturar variables temporales que empiezan con # o _temp_
|
|
||||||
# y estáticas (si usas un prefijo como 'stat_' o para bits de memoria de flanco)
|
|
||||||
temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?') # Captura con o sin #
|
|
||||||
stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') # Para memorias de flanco si usan prefijo 'stat_'
|
|
||||||
|
|
||||||
edge_memory_bits = set() # Para detectar bits de memoria de flanco por nombre
|
|
||||||
|
|
||||||
for network in data.get('networks', []):
|
|
||||||
for instruction in network.get('logic', []):
|
|
||||||
scl_code = instruction.get('scl', '')
|
|
||||||
# Buscar también en _edge_mem_update_scl si existe
|
|
||||||
edge_update_code = instruction.get('_edge_mem_update_scl','')
|
|
||||||
code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '')
|
|
||||||
|
|
||||||
if code_to_scan:
|
|
||||||
# Buscar #_temp_... o _temp_...
|
|
||||||
found_temps = temp_pattern.findall(code_to_scan)
|
|
||||||
for temp_tuple in found_temps:
|
|
||||||
# findall devuelve tuplas por los grupos de captura, tomar el no vacío
|
|
||||||
temp_name = next((t for t in temp_tuple if t), None)
|
|
||||||
if temp_name:
|
|
||||||
temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name) # Asegurar que empiece con #
|
|
||||||
|
|
||||||
# Buscar estáticas (ej: stat_...)
|
|
||||||
found_stats = stat_pattern.findall(code_to_scan)
|
|
||||||
stat_vars.update(found_stats)
|
|
||||||
|
|
||||||
# Identificar explícitamente bits de memoria usados por PBox/NBox
|
|
||||||
# Asumiendo que el nombre se guarda en el JSON (requiere ajuste en x1/x2)
|
|
||||||
# if instruction.get("type","").startswith(("PBox", "NBox")):
|
|
||||||
# mem_bit_info = instruction.get("inputs", {}).get("bit")
|
|
||||||
# if mem_bit_info and mem_bit_info.get("type") == "variable":
|
|
||||||
# edge_memory_bits.add(format_variable_name(mem_bit_info.get("name")))
|
|
||||||
|
|
||||||
|
|
||||||
print(f"Variables temporales (#_temp_...) detectadas: {len(temp_vars)}")
|
|
||||||
# Si se detectan memorias de flanco, añadirlas a stat_vars si no tienen prefijo 'stat_'
|
|
||||||
# stat_vars.update(edge_memory_bits - stat_vars) # Añadir solo las nuevas
|
|
||||||
print(f"Variables estáticas (stat_...) detectadas: {len(stat_vars)}")
|
|
||||||
|
|
||||||
# --- Construcción del String SCL ---
|
|
||||||
scl_output = []
|
scl_output = []
|
||||||
|
|
||||||
# Cabecera del Bloque
|
# --- GENERACIÓN PARA DATA BLOCK (DB) ---
|
||||||
|
if block_lang_original == "DB":
|
||||||
|
print("Modo de generación: DATA_BLOCK")
|
||||||
|
scl_output.append(f"// Block Type: {block_type}")
|
||||||
scl_output.append(f"// Block Name (Original): {block_name}")
|
scl_output.append(f"// Block Name (Original): {block_name}")
|
||||||
if block_number: scl_output.append(f"// Block Number: {block_number}")
|
if block_number:
|
||||||
scl_output.append(f"// Original Language: {block_lang_original}")
|
scl_output.append(f"// Block Number: {block_number}")
|
||||||
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
|
if block_comment:
|
||||||
|
scl_output.append(f"// Block Comment: {block_comment}")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
scl_output.append(f"{block_type_scl} \"{scl_block_name}\"")
|
scl_output.append(f'DATA_BLOCK "{scl_block_name}"')
|
||||||
|
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
|
||||||
|
scl_output.append("VERSION : 0.1")
|
||||||
|
scl_output.append("")
|
||||||
|
interface_data = data.get("interface", {})
|
||||||
|
static_vars = interface_data.get("Static", [])
|
||||||
|
if static_vars:
|
||||||
|
scl_output.append("VAR")
|
||||||
|
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
|
||||||
|
scl_output.append("END_VAR")
|
||||||
|
scl_output.append("")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB."
|
||||||
|
)
|
||||||
|
scl_output.append("VAR")
|
||||||
|
scl_output.append("END_VAR")
|
||||||
|
scl_output.append("")
|
||||||
|
scl_output.append("BEGIN")
|
||||||
|
scl_output.append("")
|
||||||
|
scl_output.append("END_DATA_BLOCK")
|
||||||
|
|
||||||
|
# --- GENERACIÓN PARA FUNCTION BLOCK / FUNCTION (FC/FB) ---
|
||||||
|
else:
|
||||||
|
print("Modo de generación: FUNCTION_BLOCK / FUNCTION")
|
||||||
|
scl_block_keyword = "FUNCTION_BLOCK" if block_type == "FB" else "FUNCTION"
|
||||||
|
# Cabecera del Bloque
|
||||||
|
scl_output.append(f"// Block Type: {block_type}")
|
||||||
|
scl_output.append(f"// Block Name (Original): {block_name}")
|
||||||
|
if block_number:
|
||||||
|
scl_output.append(f"// Block Number: {block_number}")
|
||||||
|
scl_output.append(f"// Original Language: {block_lang_original}")
|
||||||
|
if block_comment:
|
||||||
|
scl_output.append(f"// Block Comment: {block_comment}")
|
||||||
|
scl_output.append("")
|
||||||
|
# Manejar tipo de retorno para FUNCTION
|
||||||
|
return_type = "Void" # Default
|
||||||
|
interface_data = data.get("interface", {})
|
||||||
|
if scl_block_keyword == "FUNCTION" and interface_data.get("Return"):
|
||||||
|
return_member = interface_data["Return"][
|
||||||
|
0
|
||||||
|
] # Asumir un solo valor de retorno
|
||||||
|
return_type_raw = return_member.get("datatype", "Void")
|
||||||
|
return_type = (
|
||||||
|
return_type_raw.strip('"')
|
||||||
|
if return_type_raw.startswith('"') and return_type_raw.endswith('"')
|
||||||
|
else return_type_raw
|
||||||
|
)
|
||||||
|
# Añadir comillas si es UDT
|
||||||
|
if return_type != return_type_raw:
|
||||||
|
return_type = f'"{return_type}"'
|
||||||
|
|
||||||
|
scl_output.append(
|
||||||
|
f'{scl_block_keyword} "{scl_block_name}" : {return_type}'
|
||||||
|
if scl_block_keyword == "FUNCTION"
|
||||||
|
else f'{scl_block_keyword} "{scl_block_name}"'
|
||||||
|
)
|
||||||
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
|
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
|
||||||
scl_output.append("VERSION : 0.1")
|
scl_output.append("VERSION : 0.1")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
|
||||||
# Declaraciones de Interfaz (Implementación básica)
|
# Declaraciones de Interfaz FC/FB
|
||||||
interface_sections = ["Input", "Output", "InOut", "Static", "Temp", "Constant", "Return"]
|
section_order = [
|
||||||
interface_data = data.get('interface', {})
|
"Input",
|
||||||
|
"Output",
|
||||||
for section_name in interface_sections:
|
"InOut",
|
||||||
scl_section_name = section_name
|
"Static",
|
||||||
# Ajustar nombres de sección para SCL (Static -> STAT, Temp -> TEMP)
|
"Temp",
|
||||||
if section_name == "Static": scl_section_name = "STAT"
|
"Constant",
|
||||||
if section_name == "Temp": scl_section_name = "TEMP" # Usar VAR_TEMP para variables #temp
|
] # Return ya está en cabecera
|
||||||
|
|
||||||
vars_in_section = interface_data.get(section_name, [])
|
|
||||||
# No declarar VAR_TEMP aquí, se hará después con las detectadas/originales
|
|
||||||
if section_name == "Temp": continue
|
|
||||||
|
|
||||||
# No declarar VAR_STAT aquí si ya lo hacemos abajo con las detectadas
|
|
||||||
if section_name == "Static" and stat_vars: continue
|
|
||||||
|
|
||||||
|
|
||||||
if vars_in_section or (section_name == "Static" and stat_vars): # Incluir STAT si hay detectadas
|
|
||||||
# Usar VAR para Input/Output/InOut/Constant/Return
|
|
||||||
var_keyword = "VAR" if section_name != "Static" else "VAR_STAT"
|
|
||||||
scl_output.append(f"{var_keyword}_{section_name.upper()}")
|
|
||||||
|
|
||||||
for var in vars_in_section:
|
|
||||||
var_name = var.get('name')
|
|
||||||
var_dtype = var.get('datatype', 'VARIANT') # Default a VARIANT
|
|
||||||
if var_name:
|
|
||||||
# Usar format_variable_name CORRECTO
|
|
||||||
scl_name = format_variable_name(var_name)
|
|
||||||
scl_output.append(f" {scl_name} : {var_dtype};")
|
|
||||||
|
|
||||||
# Declarar stat_vars detectadas si esta es la sección STAT
|
|
||||||
if section_name == "Static" and stat_vars:
|
|
||||||
for var_name in sorted(list(stat_vars)):
|
|
||||||
# Asumir Bool para stat_, podría necesitar inferencia
|
|
||||||
scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
|
|
||||||
|
|
||||||
scl_output.append("END_VAR")
|
|
||||||
scl_output.append("")
|
|
||||||
|
|
||||||
# Declaraciones Estáticas (Si no estaban en la interfaz y se detectaron)
|
|
||||||
# Esto es redundante si la sección VAR_STAT ya se generó arriba
|
|
||||||
# if stat_vars and not interface_data.get("Static"):
|
|
||||||
# scl_output.append("VAR_STAT")
|
|
||||||
# for var_name in sorted(list(stat_vars)):
|
|
||||||
# scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
|
|
||||||
# scl_output.append("END_VAR")
|
|
||||||
# scl_output.append("")
|
|
||||||
|
|
||||||
|
|
||||||
# Declaraciones Temporales (Interfaz Temp + _temp_ detectadas)
|
|
||||||
scl_output.append("VAR_TEMP")
|
|
||||||
declared_temps = set()
|
declared_temps = set()
|
||||||
interface_temps = interface_data.get('Temp', [])
|
for section_name in section_order:
|
||||||
if interface_temps:
|
vars_in_section = interface_data.get(section_name, [])
|
||||||
for var in interface_temps:
|
if vars_in_section:
|
||||||
var_name = var.get('name')
|
scl_section_keyword = f"VAR_{section_name.upper()}"
|
||||||
var_dtype = var.get('datatype', 'VARIANT')
|
if section_name == "Static":
|
||||||
if var_name:
|
scl_section_keyword = "VAR_STAT"
|
||||||
scl_name = format_variable_name(var_name)
|
if section_name == "Temp":
|
||||||
scl_output.append(f" {scl_name} : {var_dtype};")
|
scl_section_keyword = "VAR_TEMP"
|
||||||
declared_temps.add(scl_name) # Marcar como declarada
|
if section_name == "Constant":
|
||||||
|
scl_section_keyword = "CONSTANT"
|
||||||
# Declarar las _temp_ generadas si no estaban ya en la interfaz Temp
|
scl_output.append(scl_section_keyword)
|
||||||
if temp_vars:
|
scl_output.extend(
|
||||||
for var_name in sorted(list(temp_vars)):
|
generate_scl_declarations(vars_in_section, indent_level=1)
|
||||||
scl_name = format_variable_name(var_name) # #_temp_...
|
)
|
||||||
if scl_name not in declared_temps:
|
if section_name == "Temp":
|
||||||
# Inferencia básica de tipo
|
declared_temps.update(
|
||||||
inferred_type = "Bool" # Asumir Bool para la mayoría de temps de lógica
|
format_variable_name(v.get("name"))
|
||||||
# Se podría mejorar si los procesadores añadieran info de tipo
|
for v in vars_in_section
|
||||||
scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary")
|
if v.get("name")
|
||||||
declared_temps.add(scl_name)
|
)
|
||||||
scl_output.append("END_VAR")
|
scl_output.append("END_VAR")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
|
||||||
# Cuerpo del Bloque
|
# Declaraciones VAR_TEMP adicionales detectadas
|
||||||
|
temp_vars = set()
|
||||||
|
temp_pattern = re.compile(
|
||||||
|
r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?'
|
||||||
|
)
|
||||||
|
for network in data.get("networks", []):
|
||||||
|
for instruction in network.get("logic", []):
|
||||||
|
scl_code = instruction.get("scl", "")
|
||||||
|
edge_update_code = instruction.get("_edge_mem_update_scl", "")
|
||||||
|
code_to_scan = (
|
||||||
|
(scl_code if scl_code else "")
|
||||||
|
+ "\n"
|
||||||
|
+ (edge_update_code if edge_update_code else "")
|
||||||
|
)
|
||||||
|
if code_to_scan:
|
||||||
|
found_temps = temp_pattern.findall(code_to_scan)
|
||||||
|
for temp_tuple in found_temps:
|
||||||
|
temp_name = next((t for t in temp_tuple if t), None)
|
||||||
|
if temp_name:
|
||||||
|
temp_vars.add(
|
||||||
|
"#" + temp_name
|
||||||
|
if not temp_name.startswith("#")
|
||||||
|
else temp_name
|
||||||
|
)
|
||||||
|
additional_temps = sorted(list(temp_vars - declared_temps))
|
||||||
|
if additional_temps:
|
||||||
|
if not interface_data.get("Temp"):
|
||||||
|
scl_output.append("VAR_TEMP")
|
||||||
|
for var_name in additional_temps:
|
||||||
|
scl_name = format_variable_name(var_name)
|
||||||
|
inferred_type = "Bool" # Asumir Bool
|
||||||
|
scl_output.append(
|
||||||
|
f" {scl_name} : {inferred_type}; // Auto-generated temporary"
|
||||||
|
)
|
||||||
|
if not interface_data.get("Temp"):
|
||||||
|
scl_output.append("END_VAR")
|
||||||
|
scl_output.append("")
|
||||||
|
|
||||||
|
# Cuerpo del Bloque FC/FB
|
||||||
scl_output.append("BEGIN")
|
scl_output.append("BEGIN")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
# Iterar por redes y lógica (como antes, incluyendo manejo STL Markdown)
|
||||||
# Iterar por redes y lógica
|
for i, network in enumerate(data.get("networks", [])):
|
||||||
for i, network in enumerate(data.get('networks', [])):
|
network_title = network.get("title", f'Network {network.get("id")}')
|
||||||
network_title = network.get('title', f'Network {network.get("id")}')
|
network_comment = network.get("comment", "")
|
||||||
network_comment = network.get('comment', '')
|
network_lang = network.get("language", "LAD")
|
||||||
network_lang = network.get('language', 'LAD') # O el lenguaje original
|
scl_output.append(
|
||||||
|
f" // Network {i+1}: {network_title} (Original Language: {network_lang})"
|
||||||
scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})")
|
)
|
||||||
if network_comment:
|
if network_comment:
|
||||||
for line in network_comment.splitlines():
|
for line in network_comment.splitlines():
|
||||||
scl_output.append(f" // {line}")
|
scl_output.append(f" // {line}")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
|
||||||
network_has_code = False
|
network_has_code = False
|
||||||
|
|
||||||
# --- NUEVO MANEJO STL con formato Markdown ---
|
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
network_has_code = True # Marcar que la red tiene contenido
|
network_has_code = True
|
||||||
if network.get('logic') and isinstance(network['logic'], list) and len(network['logic']) > 0:
|
if (
|
||||||
stl_chunk = network['logic'][0]
|
network.get("logic")
|
||||||
if stl_chunk.get("type") == "RAW_STL_CHUNK" and "stl" in stl_chunk:
|
and network["logic"][0].get("type") == "RAW_STL_CHUNK"
|
||||||
raw_stl_code = stl_chunk["stl"]
|
):
|
||||||
# Añadir marcador de inicio (como comentario SCL para evitar errores)
|
raw_stl_code = network["logic"][0].get(
|
||||||
scl_output.append(f" {'//'} ```STL") # Doble '//' para asegurar que sea comentario
|
"stl", "// ERROR: STL code missing"
|
||||||
# Escribir el código STL crudo, indentado
|
)
|
||||||
|
scl_output.append(f" {'//'} ```STL")
|
||||||
for stl_line in raw_stl_code.splitlines():
|
for stl_line in raw_stl_code.splitlines():
|
||||||
# Añadir indentación estándar de SCL
|
scl_output.append(f" {stl_line}")
|
||||||
scl_output.append(f" {stl_line}") # <-- STL sin comentar
|
|
||||||
# Añadir marcador de fin (como comentario SCL)
|
|
||||||
scl_output.append(f" {'//'} ```")
|
scl_output.append(f" {'//'} ```")
|
||||||
else:
|
else:
|
||||||
scl_output.append(" // ERROR: Contenido STL inesperado en JSON.")
|
scl_output.append(" // ERROR: Contenido STL inesperado.")
|
||||||
else:
|
else: # LAD, FBD, SCL, etc.
|
||||||
scl_output.append(" // ERROR: No se encontró lógica STL en JSON para esta red.")
|
for instruction in network.get("logic", []):
|
||||||
scl_output.append("") # Línea en blanco después de la red STL
|
|
||||||
# --- FIN NUEVO MANEJO STL con formato Markdown ---
|
|
||||||
else:
|
|
||||||
|
|
||||||
# Iterar sobre la 'logica' de la red
|
|
||||||
for instruction in network.get('logic', []):
|
|
||||||
instruction_type = instruction.get("type", "")
|
instruction_type = instruction.get("type", "")
|
||||||
scl_code = instruction.get('scl', "") # Obtener SCL generado por x2
|
scl_code = instruction.get("scl", "")
|
||||||
|
is_grouped = instruction.get("grouped", False)
|
||||||
# Saltar instrucciones agrupadas
|
if is_grouped:
|
||||||
if instruction.get("grouped", False):
|
|
||||||
continue
|
continue
|
||||||
|
if (
|
||||||
# Escribir SCL si es un tipo procesado y tiene código relevante
|
instruction_type.endswith(SCL_SUFFIX)
|
||||||
# (Ignorar comentarios de depuración de SymPy)
|
or instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
|
||||||
if instruction_type.endswith(SCL_SUFFIX) and scl_code:
|
) and scl_code:
|
||||||
is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \
|
is_only_comment = all(
|
||||||
scl_code.strip().startswith("// PBox SymPy processed") or \
|
line.strip().startswith("//")
|
||||||
scl_code.strip().startswith("// NBox SymPy processed")
|
for line in scl_code.splitlines()
|
||||||
# O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL
|
if line.strip()
|
||||||
is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines())
|
)
|
||||||
|
is_if_block = scl_code.strip().startswith("IF")
|
||||||
|
if not is_only_comment or is_if_block:
|
||||||
# Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse)
|
|
||||||
if not is_only_comment or scl_code.strip().startswith("IF"):
|
|
||||||
network_has_code = True
|
network_has_code = True
|
||||||
for line in scl_code.splitlines():
|
for line in scl_code.splitlines():
|
||||||
# Añadir indentación estándar
|
|
||||||
scl_output.append(f" {line}")
|
scl_output.append(f" {line}")
|
||||||
|
|
||||||
# Incluir también tipos especiales directamente
|
|
||||||
elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code:
|
|
||||||
network_has_code = True
|
|
||||||
for line in scl_code.splitlines():
|
|
||||||
scl_output.append(f" {line}") # Indentar
|
|
||||||
|
|
||||||
# Podríamos añadir comentarios para errores si se desea
|
|
||||||
# elif "_error" in instruction_type:
|
|
||||||
# network_has_code = True
|
|
||||||
# scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}")
|
|
||||||
|
|
||||||
|
|
||||||
if network_has_code:
|
if network_has_code:
|
||||||
scl_output.append("") # Línea en blanco después del código de la red
|
scl_output.append("")
|
||||||
else:
|
else:
|
||||||
scl_output.append(f" // Network did not produce printable SCL code.")
|
scl_output.append(f" // Network did not produce printable SCL code.")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
# Fin del bloque FC/FB
|
||||||
|
scl_output.append(f"END_{scl_block_keyword}")
|
||||||
|
|
||||||
# Fin del bloque
|
# --- Escritura del Archivo SCL (Común) ---
|
||||||
scl_output.append("END_FUNCTION_BLOCK") # O END_FUNCTION si es FC
|
|
||||||
|
|
||||||
# --- Escritura del Archivo SCL ---
|
|
||||||
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
|
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
|
||||||
try:
|
try:
|
||||||
with open(output_scl_filepath, 'w', encoding='utf-8') as f:
|
with open(output_scl_filepath, "w", encoding="utf-8") as f:
|
||||||
for line in scl_output:
|
for line in scl_output:
|
||||||
f.write(line + '\n')
|
f.write(line + "\n")
|
||||||
print("Generación de SCL completada.")
|
print("Generación de SCL completada.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al escribir el archivo SCL: {e}")
|
print(f"Error al escribir el archivo SCL: {e}")
|
||||||
|
@ -317,7 +508,9 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Verificar si el archivo XML original existe (como referencia)
|
# Verificar si el archivo XML original existe (como referencia)
|
||||||
if not os.path.exists(source_xml_file):
|
if not os.path.exists(source_xml_file):
|
||||||
print(f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado.")
|
print(
|
||||||
|
f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado."
|
||||||
|
)
|
||||||
# No salir necesariamente.
|
# No salir necesariamente.
|
||||||
|
|
||||||
# Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL)
|
# Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL)
|
||||||
|
@ -325,15 +518,25 @@ if __name__ == "__main__":
|
||||||
# Asumir que los archivos están en el mismo directorio que el XML original
|
# Asumir que los archivos están en el mismo directorio que el XML original
|
||||||
base_dir = os.path.dirname(source_xml_file) # Directorio del XML original
|
base_dir = os.path.dirname(source_xml_file) # Directorio del XML original
|
||||||
|
|
||||||
input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json")
|
input_json_file = os.path.join(
|
||||||
output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl")
|
base_dir, f"{xml_filename_base}_simplified_processed.json"
|
||||||
|
)
|
||||||
|
output_scl_file = os.path.join(
|
||||||
|
base_dir, f"{xml_filename_base}_simplified_processed.scl"
|
||||||
|
)
|
||||||
|
|
||||||
print(f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'")
|
print(
|
||||||
|
f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'"
|
||||||
|
)
|
||||||
|
|
||||||
# Verificar si el archivo JSON procesado de entrada EXISTE
|
# Verificar si el archivo JSON procesado de entrada EXISTE
|
||||||
if not os.path.exists(input_json_file):
|
if not os.path.exists(input_json_file):
|
||||||
print(f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'")
|
print(
|
||||||
print(f"Asegúrate de que 'x2_process.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'.")
|
f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"Asegúrate de que 'x2_process.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'."
|
||||||
|
)
|
||||||
sys.exit(1) # Salir si el archivo necesario no está
|
sys.exit(1) # Salir si el archivo necesario no está
|
||||||
else:
|
else:
|
||||||
# Llamar a la función principal de generación SCL del script
|
# Llamar a la función principal de generación SCL del script
|
||||||
|
@ -341,7 +544,9 @@ if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
generate_scl(input_json_file, output_scl_file)
|
generate_scl(input_json_file, output_scl_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}")
|
print(
|
||||||
|
f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}"
|
||||||
|
)
|
||||||
# traceback ya debería estar importado si generate_scl lo necesita
|
# traceback ya debería estar importado si generate_scl lo necesita
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
273
paste.py
273
paste.py
|
@ -1,273 +0,0 @@
|
||||||
# x3_generate_scl.py
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
# --- Importar Utilidades (mantener como estaba) ---
|
|
||||||
try:
|
|
||||||
from processors.processor_utils import format_variable_name
|
|
||||||
SCL_SUFFIX = "_sympy_processed"
|
|
||||||
GROUPED_COMMENT = "// Logic included in grouped IF"
|
|
||||||
except ImportError:
|
|
||||||
print("Advertencia: No se pudo importar 'format_variable_name'. Usando fallback.")
|
|
||||||
def format_variable_name(name): # Fallback BÁSICO
|
|
||||||
if not name: return "_INVALID_NAME_"
|
|
||||||
if name.startswith('"') and name.endswith('"'): return name
|
|
||||||
prefix = "#" if name.startswith("#") else ""
|
|
||||||
if prefix: name = name[1:]
|
|
||||||
if name and name[0].isdigit(): name = "_" + name
|
|
||||||
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
|
||||||
return prefix + name
|
|
||||||
SCL_SUFFIX = "_sympy_processed"
|
|
||||||
GROUPED_COMMENT = "// Logic included in grouped IF"
|
|
||||||
|
|
||||||
# --- NUEVA FUNCIÓN para formatear valores iniciales SCL ---
|
|
||||||
def format_scl_start_value(value, datatype):
|
|
||||||
"""Formatea un valor para la inicialización SCL según el tipo."""
|
|
||||||
if value is None: return None
|
|
||||||
datatype_lower = datatype.lower() if datatype else ""
|
|
||||||
value_str = str(value)
|
|
||||||
|
|
||||||
if "bool" in datatype_lower:
|
|
||||||
return "TRUE" if value_str.lower() == 'true' else "FALSE"
|
|
||||||
elif "string" in datatype_lower:
|
|
||||||
escaped_value = value_str.replace("'", "''")
|
|
||||||
if escaped_value.startswith("'") and escaped_value.endswith("'"): escaped_value = escaped_value[1:-1]
|
|
||||||
return f"'{escaped_value}'"
|
|
||||||
elif "char" in datatype_lower: # Añadido Char
|
|
||||||
escaped_value = value_str.replace("'", "''")
|
|
||||||
if escaped_value.startswith("'") and escaped_value.endswith("'"): escaped_value = escaped_value[1:-1]
|
|
||||||
return f"'{escaped_value}'"
|
|
||||||
elif any(t in datatype_lower for t in ["int", "byte", "word", "dint", "dword", "lint", "lword", "sint", "usint", "uint", "udint", "ulint"]): # Ampliado
|
|
||||||
try: return str(int(value_str))
|
|
||||||
except ValueError:
|
|
||||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', value_str): return value_str
|
|
||||||
return f"'{value_str}'" # O como string si no es entero ni símbolo
|
|
||||||
elif "real" in datatype_lower or "lreal" in datatype_lower:
|
|
||||||
try:
|
|
||||||
f_val = float(value_str); s_val = str(f_val)
|
|
||||||
if '.' not in s_val and 'e' not in s_val.lower(): s_val += ".0"
|
|
||||||
return s_val
|
|
||||||
except ValueError:
|
|
||||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', value_str): return value_str
|
|
||||||
return f"'{value_str}'"
|
|
||||||
elif "time" in datatype_lower: # Añadido Time, S5Time, LTime
|
|
||||||
# Quitar T#, LT#, S5T# si existen
|
|
||||||
prefix = ""
|
|
||||||
if value_str.upper().startswith("T#"): prefix="T#"; value_str = value_str[2:]
|
|
||||||
elif value_str.upper().startswith("LT#"): prefix="LT#"; value_str = value_str[3:]
|
|
||||||
elif value_str.upper().startswith("S5T#"): prefix="S5T#"; value_str = value_str[4:]
|
|
||||||
# Devolver con el prefijo correcto o T# por defecto si no había
|
|
||||||
if prefix: return f"{prefix}{value_str}"
|
|
||||||
elif "s5time" in datatype_lower: return f"S5T#{value_str}"
|
|
||||||
elif "ltime" in datatype_lower: return f"LT#{value_str}"
|
|
||||||
else: return f"T#{value_str}" # Default a TIME
|
|
||||||
elif "date" in datatype_lower: # Añadido Date, DT, TOD
|
|
||||||
if value_str.upper().startswith("D#"): return value_str
|
|
||||||
elif "dt" in datatype_lower or "date_and_time" in datatype_lower:
|
|
||||||
if value_str.upper().startswith("DT#"): return value_str
|
|
||||||
else: return f"DT#{value_str}" # Añadir prefijo DT#
|
|
||||||
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
|
|
||||||
if value_str.upper().startswith("TOD#"): return value_str
|
|
||||||
else: return f"TOD#{value_str}" # Añadir prefijo TOD#
|
|
||||||
else: return f"D#{value_str}" # Default a Date
|
|
||||||
# Fallback genérico
|
|
||||||
else:
|
|
||||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_."#\[\]]+$', value_str): # Permitir más caracteres en símbolos/tipos
|
|
||||||
# Si es un UDT o Struct complejo, podría venir con comillas, quitarlas
|
|
||||||
if value_str.startswith('"') and value_str.endswith('"'):
|
|
||||||
return value_str[1:-1]
|
|
||||||
return value_str
|
|
||||||
else:
|
|
||||||
escaped_value = value_str.replace("'", "''")
|
|
||||||
return f"'{escaped_value}'"
|
|
||||||
|
|
||||||
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
|
|
||||||
|
|
||||||
# --- Función Principal de Generación SCL (MODIFICADA) ---
|
|
||||||
def generate_scl(processed_json_filepath, output_scl_filepath):
|
|
||||||
"""Genera un archivo SCL a partir del JSON procesado (FC/FB o DB)."""
|
|
||||||
|
|
||||||
if not os.path.exists(processed_json_filepath):
|
|
||||||
print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"Cargando JSON procesado desde: {processed_json_filepath}")
|
|
||||||
try:
|
|
||||||
with open(processed_json_filepath, 'r', encoding='utf-8') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al cargar o parsear JSON: {e}"); traceback.print_exc(); return
|
|
||||||
|
|
||||||
# --- Extracción de Información del Bloque (Común) ---
|
|
||||||
block_name = data.get('block_name', 'UnknownBlock')
|
|
||||||
block_number = data.get('block_number')
|
|
||||||
block_lang_original = data.get('language', 'Unknown') # Será "DB" para Data Blocks
|
|
||||||
block_type = data.get('block_type', 'Unknown') # FC, FB, GlobalDB
|
|
||||||
block_comment = data.get('block_comment', '')
|
|
||||||
scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
|
|
||||||
print(f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name}, Lang: {block_lang_original})")
|
|
||||||
scl_output = []
|
|
||||||
|
|
||||||
# --- GENERACIÓN PARA DATA BLOCK (DB) ---
|
|
||||||
if block_lang_original == "DB":
|
|
||||||
print("Modo de generación: DATA_BLOCK")
|
|
||||||
scl_output.append(f"// Block Type: {block_type}")
|
|
||||||
scl_output.append(f"// Block Name (Original): {block_name}")
|
|
||||||
if block_number: scl_output.append(f"// Block Number: {block_number}")
|
|
||||||
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
|
|
||||||
scl_output.append("")
|
|
||||||
scl_output.append(f"DATA_BLOCK \"{scl_block_name}\"")
|
|
||||||
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
|
|
||||||
scl_output.append("VERSION : 0.1")
|
|
||||||
scl_output.append("")
|
|
||||||
interface_data = data.get('interface', {})
|
|
||||||
static_vars = interface_data.get('Static', [])
|
|
||||||
if static_vars:
|
|
||||||
scl_output.append("VAR")
|
|
||||||
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
|
|
||||||
scl_output.append("END_VAR")
|
|
||||||
scl_output.append("")
|
|
||||||
else:
|
|
||||||
print("Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB.")
|
|
||||||
scl_output.append("VAR"); scl_output.append("END_VAR"); scl_output.append("")
|
|
||||||
scl_output.append("BEGIN"); scl_output.append(""); scl_output.append("END_DATA_BLOCK")
|
|
||||||
|
|
||||||
# --- GENERACIÓN PARA FUNCTION BLOCK / FUNCTION (FC/FB) ---
|
|
||||||
else:
|
|
||||||
print("Modo de generación: FUNCTION_BLOCK / FUNCTION")
|
|
||||||
scl_block_keyword = "FUNCTION_BLOCK" if block_type == "FB" else "FUNCTION"
|
|
||||||
# Cabecera del Bloque
|
|
||||||
scl_output.append(f"// Block Type: {block_type}")
|
|
||||||
scl_output.append(f"// Block Name (Original): {block_name}")
|
|
||||||
if block_number: scl_output.append(f"// Block Number: {block_number}")
|
|
||||||
scl_output.append(f"// Original Language: {block_lang_original}")
|
|
||||||
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
|
|
||||||
scl_output.append("")
|
|
||||||
# Manejar tipo de retorno para FUNCTION
|
|
||||||
return_type = "Void" # Default
|
|
||||||
interface_data = data.get('interface', {})
|
|
||||||
if scl_block_keyword == "FUNCTION" and interface_data.get('Return'):
|
|
||||||
return_member = interface_data['Return'][0] # Asumir un solo valor de retorno
|
|
||||||
return_type_raw = return_member.get('datatype', 'Void')
|
|
||||||
return_type = return_type_raw.strip('"') if return_type_raw.startswith('"') and return_type_raw.endswith('"') else return_type_raw
|
|
||||||
# Añadir comillas si es UDT
|
|
||||||
if return_type != return_type_raw: return_type = f'"{return_type}"'
|
|
||||||
|
|
||||||
scl_output.append(f"{scl_block_keyword} \"{scl_block_name}\" : {return_type}" if scl_block_keyword == "FUNCTION" else f"{scl_block_keyword} \"{scl_block_name}\"")
|
|
||||||
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
|
|
||||||
scl_output.append("VERSION : 0.1"); scl_output.append("")
|
|
||||||
|
|
||||||
# Declaraciones de Interfaz FC/FB
|
|
||||||
section_order = ["Input", "Output", "InOut", "Static", "Temp", "Constant"] # Return ya está en cabecera
|
|
||||||
declared_temps = set()
|
|
||||||
for section_name in section_order:
|
|
||||||
vars_in_section = interface_data.get(section_name, [])
|
|
||||||
if vars_in_section:
|
|
||||||
scl_section_keyword = f"VAR_{section_name.upper()}"
|
|
||||||
if section_name == "Static": scl_section_keyword = "VAR_STAT"
|
|
||||||
if section_name == "Temp": scl_section_keyword = "VAR_TEMP"
|
|
||||||
if section_name == "Constant": scl_section_keyword = "CONSTANT"
|
|
||||||
scl_output.append(scl_section_keyword)
|
|
||||||
scl_output.extend(generate_scl_declarations(vars_in_section, indent_level=1))
|
|
||||||
if section_name == "Temp": declared_temps.update(format_variable_name(v.get("name")) for v in vars_in_section if v.get("name"))
|
|
||||||
scl_output.append("END_VAR"); scl_output.append("")
|
|
||||||
|
|
||||||
# Declaraciones VAR_TEMP adicionales detectadas
|
|
||||||
temp_vars = set()
|
|
||||||
temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?')
|
|
||||||
for network in data.get('networks', []):
|
|
||||||
for instruction in network.get('logic', []):
|
|
||||||
scl_code = instruction.get('scl', ''); edge_update_code = instruction.get('_edge_mem_update_scl','')
|
|
||||||
code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '')
|
|
||||||
if code_to_scan:
|
|
||||||
found_temps = temp_pattern.findall(code_to_scan)
|
|
||||||
for temp_tuple in found_temps:
|
|
||||||
temp_name = next((t for t in temp_tuple if t), None)
|
|
||||||
if temp_name: temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name)
|
|
||||||
additional_temps = sorted(list(temp_vars - declared_temps))
|
|
||||||
if additional_temps:
|
|
||||||
if not interface_data.get("Temp"): scl_output.append("VAR_TEMP")
|
|
||||||
for var_name in additional_temps:
|
|
||||||
scl_name = format_variable_name(var_name); inferred_type = "Bool" # Asumir Bool
|
|
||||||
scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary")
|
|
||||||
if not interface_data.get("Temp"): scl_output.append("END_VAR"); scl_output.append("")
|
|
||||||
|
|
||||||
# Cuerpo del Bloque FC/FB
|
|
||||||
scl_output.append("BEGIN"); scl_output.append("")
|
|
||||||
# Iterar por redes y lógica (como antes, incluyendo manejo STL Markdown)
|
|
||||||
for i, network in enumerate(data.get('networks', [])):
|
|
||||||
network_title = network.get('title', f'Network {network.get("id")}')
|
|
||||||
network_comment = network.get('comment', '')
|
|
||||||
network_lang = network.get('language', 'LAD')
|
|
||||||
scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})")
|
|
||||||
if network_comment:
|
|
||||||
for line in network_comment.splitlines(): scl_output.append(f" // {line}")
|
|
||||||
scl_output.append("")
|
|
||||||
network_has_code = False
|
|
||||||
if network_lang == "STL":
|
|
||||||
network_has_code = True
|
|
||||||
if network.get('logic') and network['logic'][0].get("type") == "RAW_STL_CHUNK":
|
|
||||||
raw_stl_code = network['logic'][0].get("stl", "// ERROR: STL code missing")
|
|
||||||
scl_output.append(f" {'//'} ```STL")
|
|
||||||
for stl_line in raw_stl_code.splitlines(): scl_output.append(f" {stl_line}")
|
|
||||||
scl_output.append(f" {'//'} ```")
|
|
||||||
else: scl_output.append(" // ERROR: Contenido STL inesperado.")
|
|
||||||
else: # LAD, FBD, SCL, etc.
|
|
||||||
for instruction in network.get('logic', []):
|
|
||||||
instruction_type = instruction.get("type", ""); scl_code = instruction.get('scl', ''); is_grouped = instruction.get("grouped", False)
|
|
||||||
if is_grouped: continue
|
|
||||||
if (instruction_type.endswith(SCL_SUFFIX) or instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]) and scl_code:
|
|
||||||
is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines() if line.strip())
|
|
||||||
is_if_block = scl_code.strip().startswith("IF")
|
|
||||||
if not is_only_comment or is_if_block:
|
|
||||||
network_has_code = True
|
|
||||||
for line in scl_code.splitlines(): scl_output.append(f" {line}")
|
|
||||||
if network_has_code: scl_output.append("")
|
|
||||||
else: scl_output.append(f" // Network did not produce printable SCL code."); scl_output.append("")
|
|
||||||
# Fin del bloque FC/FB
|
|
||||||
scl_output.append(f"END_{scl_block_keyword}")
|
|
||||||
|
|
||||||
# --- Escritura del Archivo SCL (Común) ---
|
|
||||||
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
|
|
||||||
try:
|
|
||||||
with open(output_scl_filepath, 'w', encoding='utf-8') as f:
|
|
||||||
for line in scl_output: f.write(line + '\n')
|
|
||||||
print("Generación de SCL completada.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error al escribir el archivo SCL: {e}"); traceback.print_exc()
|
|
||||||
|
|
||||||
|
|
||||||
# --- Ejecución (sin cambios) ---
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(description="Generate final SCL file from processed JSON (FC/FB/DB).")
|
|
||||||
parser.add_argument("source_xml_filepath", help="Path to the original source XML file.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
source_xml_file = args.source_xml_filepath
|
|
||||||
|
|
||||||
if not os.path.exists(source_xml_file):
|
|
||||||
print(f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado.")
|
|
||||||
# Continuar, pero verificar existencia del JSON procesado
|
|
||||||
|
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
|
||||||
base_dir = os.path.dirname(source_xml_file)
|
|
||||||
input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json")
|
|
||||||
output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl")
|
|
||||||
|
|
||||||
print(f"(x3) Generando SCL: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_scl_file)}'")
|
|
||||||
|
|
||||||
if not os.path.exists(input_json_file):
|
|
||||||
print(f"Error Fatal (x3): Archivo JSON procesado no encontrado: '{input_json_file}'")
|
|
||||||
print(f"Asegúrate de que 'x1_to_json.py' y 'x2_process.py' se ejecutaron correctamente para '{os.path.relpath(source_xml_file)}'.")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
generate_scl(input_json_file, output_scl_file)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
254
x0_main.py
254
x0_main.py
|
@ -3,255 +3,163 @@ import subprocess
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import locale
|
import locale
|
||||||
import glob # <--- Importar glob para buscar archivos
|
import glob
|
||||||
|
|
||||||
|
# (Función get_console_encoding y variable CONSOLE_ENCODING como antes)
|
||||||
# (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior)
|
|
||||||
def get_console_encoding():
|
def get_console_encoding():
|
||||||
"""Obtiene la codificación preferida de la consola, con fallback."""
|
"""Obtiene la codificación preferida de la consola, con fallback."""
|
||||||
try:
|
try:
|
||||||
return locale.getpreferredencoding(False)
|
return locale.getpreferredencoding(False)
|
||||||
except Exception:
|
except Exception:
|
||||||
return "cp1252"
|
# Fallback común en Windows si falla getpreferredencoding
|
||||||
|
return "cp1252" # O prueba con 'utf-8' si cp1252 da problemas
|
||||||
|
|
||||||
CONSOLE_ENCODING = get_console_encoding()
|
CONSOLE_ENCODING = get_console_encoding()
|
||||||
# Descomenta la siguiente línea si quieres ver la codificación detectada:
|
|
||||||
# print(f"Detected console encoding: {CONSOLE_ENCODING}")
|
# print(f"Detected console encoding: {CONSOLE_ENCODING}")
|
||||||
|
|
||||||
|
# (Función run_script como antes, usando CONSOLE_ENCODING)
|
||||||
# (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING)
|
|
||||||
def run_script(script_name, xml_arg):
|
def run_script(script_name, xml_arg):
|
||||||
"""Runs a given script with the specified XML file argument."""
|
"""Runs a given script with the specified XML file argument."""
|
||||||
script_path = os.path.join(os.path.dirname(__file__), script_name)
|
# Asegurarse que la ruta al script sea absoluta o relativa al script actual
|
||||||
command = [sys.executable, script_path, xml_arg]
|
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), script_name)
|
||||||
|
# Usar la ruta absoluta al ejecutable de Python actual
|
||||||
|
python_executable = sys.executable
|
||||||
|
command = [python_executable, script_path, xml_arg] # Usar la ruta absoluta de python
|
||||||
print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
|
print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
|
||||||
try:
|
try:
|
||||||
|
# Ejecutar el proceso hijo
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
command,
|
command,
|
||||||
check=True,
|
check=True, # Lanza excepción si el script falla (return code != 0)
|
||||||
capture_output=True,
|
capture_output=True,# Captura stdout y stderr
|
||||||
text=True,
|
text=True, # Decodifica stdout/stderr como texto
|
||||||
encoding=CONSOLE_ENCODING,
|
encoding=CONSOLE_ENCODING, # Usa la codificación detectada
|
||||||
errors="replace",
|
errors='replace' # Reemplaza caracteres no decodificables
|
||||||
) # 'replace' para evitar errores
|
)
|
||||||
|
|
||||||
|
# Imprimir stdout y stderr si no están vacíos
|
||||||
|
stdout_clean = result.stdout.strip() if result.stdout else ""
|
||||||
|
stderr_clean = result.stderr.strip() if result.stderr else ""
|
||||||
|
|
||||||
# Imprimir stdout y stderr
|
|
||||||
# Eliminar saltos de línea extra al final si existen
|
|
||||||
stdout_clean = result.stdout.strip()
|
|
||||||
stderr_clean = result.stderr.strip()
|
|
||||||
if stdout_clean:
|
if stdout_clean:
|
||||||
print(stdout_clean)
|
print(stdout_clean)
|
||||||
if stderr_clean:
|
if stderr_clean:
|
||||||
print("--- Stderr ---")
|
# Imprimir stderr claramente para errores del script hijo
|
||||||
print(stderr_clean)
|
print(f"--- Stderr ({script_name}) ---", file=sys.stderr) # Imprimir en stderr
|
||||||
print("--------------")
|
print(stderr_clean, file=sys.stderr)
|
||||||
|
print("--------------------------", file=sys.stderr)
|
||||||
|
|
||||||
print(f"--- {script_name} finished successfully ---")
|
print(f"--- {script_name} finished successfully ---")
|
||||||
return True
|
return True # Indicar éxito
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Error: Script '{script_path}' not found.")
|
# Error si el script python o el ejecutable no se encuentran
|
||||||
|
print(f"Error: Script '{script_path}' or Python executable '{python_executable}' not found.", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Error running {script_name}:")
|
# Error si el script hijo devuelve un código de error (ej., sys.exit(1))
|
||||||
print(f"Return code: {e.returncode}")
|
print(f"Error running {script_name}: Script returned non-zero exit code {e.returncode}.", file=sys.stderr)
|
||||||
stdout_decoded = (
|
|
||||||
e.stdout.decode(CONSOLE_ENCODING, errors="replace").strip()
|
# Decodificar e imprimir stdout/stderr del proceso fallido
|
||||||
if isinstance(e.stdout, bytes)
|
stdout_decoded = e.stdout.strip() if e.stdout else ""
|
||||||
else (e.stdout or "").strip()
|
stderr_decoded = e.stderr.strip() if e.stderr else ""
|
||||||
)
|
|
||||||
stderr_decoded = (
|
|
||||||
e.stderr.decode(CONSOLE_ENCODING, errors="replace").strip()
|
|
||||||
if isinstance(e.stderr, bytes)
|
|
||||||
else (e.stderr or "").strip()
|
|
||||||
)
|
|
||||||
if stdout_decoded:
|
if stdout_decoded:
|
||||||
print("--- Stdout ---")
|
print(f"--- Stdout ({script_name}) ---", file=sys.stderr)
|
||||||
print(stdout_decoded)
|
print(stdout_decoded, file=sys.stderr)
|
||||||
if stderr_decoded:
|
if stderr_decoded:
|
||||||
print("--- Stderr ---")
|
print(f"--- Stderr ({script_name}) ---", file=sys.stderr)
|
||||||
print(stderr_decoded)
|
print(stderr_decoded, file=sys.stderr)
|
||||||
print("--------------")
|
print("--------------------------", file=sys.stderr)
|
||||||
return False
|
return False # Indicar fallo
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"An unexpected error occurred while running {script_name}: {e}")
|
# Otros errores inesperados
|
||||||
return False
|
print(f"An unexpected error occurred while running {script_name}: {e}", file=sys.stderr)
|
||||||
|
# Imprimir traceback para depuración
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
return False # Indicar fallo
|
||||||
|
|
||||||
|
|
||||||
# --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO ---
|
# --- NO SE NECESITA select_xml_file() si procesamos todos ---
|
||||||
def select_xml_file():
|
|
||||||
"""Busca archivos .xml, los lista y pide al usuario que elija uno."""
|
|
||||||
print("No XML file specified. Searching for XML files in current directory...")
|
|
||||||
# Buscar archivos .xml en el directorio actual (.)
|
|
||||||
xml_files = sorted(glob.glob("*.xml")) # sorted para orden alfabético
|
|
||||||
|
|
||||||
if not xml_files:
|
|
||||||
print("Error: No .xml files found in the current directory.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("\nAvailable XML files:")
|
|
||||||
for i, filename in enumerate(xml_files, start=1):
|
|
||||||
print(f" {i}: {filename}")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
choice = input(
|
|
||||||
f"Enter the number of the file to process (1-{len(xml_files)}): "
|
|
||||||
)
|
|
||||||
choice_num = int(choice)
|
|
||||||
if 1 <= choice_num <= len(xml_files):
|
|
||||||
selected_file = xml_files[choice_num - 1]
|
|
||||||
print(f"Selected: {selected_file}")
|
|
||||||
return selected_file
|
|
||||||
else:
|
|
||||||
print("Invalid choice. Please enter a number from the list.")
|
|
||||||
except ValueError:
|
|
||||||
print("Invalid input. Please enter a number.")
|
|
||||||
except EOFError: # Manejar si la entrada se cierra inesperadamente
|
|
||||||
print("\nSelection cancelled.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
# --- FIN NUEVA FUNCIÓN ---
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Imports necesarios para esta sección
|
# --- PARTE 1: BUSCAR ARCHIVOS ---
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import glob # Asegúrate de que glob esté importado al principio del archivo
|
|
||||||
|
|
||||||
# Directorio base donde buscar los archivos XML (relativo al script)
|
# Directorio base donde buscar los archivos XML (relativo al script)
|
||||||
base_search_dir = "XML Project"
|
base_search_dir = "XML Project"
|
||||||
script_dir = os.path.dirname(__file__) # Directorio donde está x0_main.py
|
# Obtener la ruta absoluta del directorio donde está x0_main.py
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
xml_project_dir = os.path.join(script_dir, base_search_dir)
|
xml_project_dir = os.path.join(script_dir, base_search_dir)
|
||||||
|
|
||||||
print(f"Buscando archivos XML recursivamente en: '{xml_project_dir}'")
|
print(f"Buscando archivos XML recursivamente en: '{xml_project_dir}'")
|
||||||
|
|
||||||
# Verificar si el directorio 'XML Project' existe
|
# Verificar si el directorio 'XML Project' existe
|
||||||
if not os.path.isdir(xml_project_dir):
|
if not os.path.isdir(xml_project_dir):
|
||||||
print(
|
print(f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio.", file=sys.stderr)
|
||||||
f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio."
|
print("Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro.")
|
||||||
)
|
sys.exit(1) # Salir con error
|
||||||
print(
|
|
||||||
"Por favor, crea el directorio 'XML Project' en la misma carpeta que este script y coloca tus archivos XML dentro."
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Buscar todos los archivos .xml recursivamente dentro de xml_project_dir
|
# Buscar todos los archivos .xml recursivamente
|
||||||
# Usamos os.path.join para construir la ruta de búsqueda correctamente
|
|
||||||
# y '**/*.xml' para la recursividad con glob
|
|
||||||
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
||||||
xml_files_found = glob.glob(search_pattern, recursive=True)
|
xml_files_found = glob.glob(search_pattern, recursive=True)
|
||||||
|
|
||||||
if not xml_files_found:
|
if not xml_files_found:
|
||||||
print(
|
print(f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios.")
|
||||||
f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios."
|
|
||||||
)
|
|
||||||
sys.exit(0) # Salir limpiamente si no hay archivos
|
sys.exit(0) # Salir limpiamente si no hay archivos
|
||||||
|
|
||||||
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
|
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
|
||||||
# Ordenar para un procesamiento predecible (opcional)
|
xml_files_found.sort() # Ordenar para consistencia
|
||||||
xml_files_found.sort()
|
|
||||||
for xml_file in xml_files_found:
|
for xml_file in xml_files_found:
|
||||||
# Imprimir la ruta relativa desde el directorio del script para claridad
|
|
||||||
print(f" - {os.path.relpath(xml_file, script_dir)}")
|
print(f" - {os.path.relpath(xml_file, script_dir)}")
|
||||||
|
|
||||||
# Scripts a ejecutar en secuencia (asegúrate que los nombres son correctos)
|
# --- PARTE 2: PROCESAR CADA ARCHIVO ---
|
||||||
|
# Scripts a ejecutar en secuencia
|
||||||
script1 = "x1_to_json.py"
|
script1 = "x1_to_json.py"
|
||||||
script2 = "x2_process.py"
|
script2 = "x2_process.py"
|
||||||
script3 = "x3_generate_scl.py"
|
script3 = "x3_generate_scl.py"
|
||||||
|
|
||||||
# Procesar cada archivo encontrado
|
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
failed_count = 0
|
failed_count = 0
|
||||||
for xml_filepath in xml_files_found:
|
|
||||||
print(
|
|
||||||
f"\n--- Iniciando pipeline para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Usar la ruta absoluta para evitar problemas si los scripts cambian de directorio
|
# Procesar cada archivo encontrado en el bucle
|
||||||
|
for xml_filepath in xml_files_found:
|
||||||
|
relative_path = os.path.relpath(xml_filepath, script_dir)
|
||||||
|
print(f"\n--- Iniciando pipeline para: {relative_path} ---")
|
||||||
|
|
||||||
|
# Usar la ruta absoluta para los scripts hijos
|
||||||
absolute_xml_filepath = os.path.abspath(xml_filepath)
|
absolute_xml_filepath = os.path.abspath(xml_filepath)
|
||||||
|
|
||||||
# Ejecutar los scripts en secuencia para el archivo actual
|
# Ejecutar los scripts en secuencia
|
||||||
# La función run_script ya está definida en tu script x0_main.py
|
|
||||||
success = True
|
success = True
|
||||||
if not run_script(script1, absolute_xml_filepath):
|
if not run_script(script1, absolute_xml_filepath):
|
||||||
print(
|
print(f"\nPipeline falló en el script '{script1}' para el archivo: {relative_path}", file=sys.stderr)
|
||||||
f"\nPipeline falló en el script '{script1}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
|
||||||
)
|
|
||||||
success = False
|
success = False
|
||||||
elif not run_script(script2, absolute_xml_filepath):
|
elif not run_script(script2, absolute_xml_filepath):
|
||||||
print(
|
print(f"\nPipeline falló en el script '{script2}' para el archivo: {relative_path}", file=sys.stderr)
|
||||||
f"\nPipeline falló en el script '{script2}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
|
||||||
)
|
|
||||||
success = False
|
success = False
|
||||||
elif not run_script(script3, absolute_xml_filepath):
|
elif not run_script(script3, absolute_xml_filepath):
|
||||||
print(
|
print(f"\nPipeline falló en el script '{script3}' para el archivo: {relative_path}", file=sys.stderr)
|
||||||
f"\nPipeline falló en el script '{script3}' para el archivo: {os.path.relpath(xml_filepath, script_dir)}"
|
|
||||||
)
|
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
|
# Actualizar contadores y mostrar estado
|
||||||
if success:
|
if success:
|
||||||
print(
|
print(f"--- Pipeline completado exitosamente para: {relative_path} ---")
|
||||||
f"--- Pipeline completado exitosamente para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
|
||||||
)
|
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
else:
|
else:
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
print(
|
print(f"--- Pipeline falló para: {relative_path} ---", file=sys.stderr) # Indicar fallo
|
||||||
f"--- Pipeline falló para: {os.path.relpath(xml_filepath, script_dir)} ---"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# --- PARTE 3: RESUMEN FINAL ---
|
||||||
print("\n--- Resumen Final del Procesamiento ---")
|
print("\n--- Resumen Final del Procesamiento ---")
|
||||||
print(f"Total de archivos XML encontrados: {len(xml_files_found)}")
|
print(f"Total de archivos XML encontrados: {len(xml_files_found)}")
|
||||||
print(
|
print(f"Archivos procesados exitosamente por el pipeline completo: {processed_count}")
|
||||||
f"Archivos procesados exitosamente por el pipeline completo: {processed_count}"
|
|
||||||
)
|
|
||||||
print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}")
|
print(f"Archivos que fallaron en algún punto del pipeline: {failed_count}")
|
||||||
print("---------------------------------------")
|
print("---------------------------------------")
|
||||||
xml_filename = None
|
|
||||||
|
|
||||||
# Comprobar si se pasó un argumento de línea de comandos
|
# Salir con código 0 si todo fue bien, 1 si hubo fallos
|
||||||
# sys.argv[0] es el nombre del script, sys.argv[1] sería el primer argumento
|
if failed_count > 0:
|
||||||
if len(sys.argv) > 1:
|
|
||||||
# Si hay argumentos, usar argparse para parsearlo (permite -h, etc.)
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Run the Simatic XML processing pipeline."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"xml_file",
|
|
||||||
# Ya no necesitamos nargs='?' ni default aquí porque sabemos que hay un argumento
|
|
||||||
help="Path to the XML file to process.",
|
|
||||||
)
|
|
||||||
# Parsear solo los argumentos conocidos, ignorar extras si los hubiera
|
|
||||||
args, unknown = parser.parse_known_args()
|
|
||||||
xml_filename = args.xml_file
|
|
||||||
print(f"XML file specified via argument: {xml_filename}")
|
|
||||||
else:
|
|
||||||
# Si no hay argumentos, llamar a la función interactiva
|
|
||||||
xml_filename = select_xml_file()
|
|
||||||
|
|
||||||
# --- El resto del script continúa igual, usando xml_filename ---
|
|
||||||
|
|
||||||
# Verificar si el archivo XML de entrada (seleccionado o pasado) existe
|
|
||||||
if not os.path.exists(xml_filename):
|
|
||||||
print(f"Error: Selected or specified XML file not found: {xml_filename}")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"\nStarting pipeline for: {xml_filename}")
|
|
||||||
|
|
||||||
# Run scripts sequentially (asegúrate que los nombres son correctos)
|
|
||||||
script1 = "x1_to_json.py"
|
|
||||||
script2 = "x2_process.py"
|
|
||||||
script3 = "x3_generate_scl.py"
|
|
||||||
|
|
||||||
if run_script(script1, xml_filename):
|
|
||||||
if run_script(script2, xml_filename):
|
|
||||||
if run_script(script3, xml_filename):
|
|
||||||
print("\nPipeline completed successfully.")
|
|
||||||
else:
|
else:
|
||||||
print("\nPipeline failed at script:", script3)
|
sys.exit(0)
|
||||||
else:
|
|
||||||
print("\nPipeline failed at script:", script2)
|
# --- FIN: Se elimina la lógica redundante que venía después del bucle ---
|
||||||
else:
|
|
||||||
print("\nPipeline failed at script:", script1)
|
|
164
x1_to_json.py
164
x1_to_json.py
|
@ -18,6 +18,8 @@ ns = {
|
||||||
|
|
||||||
|
|
||||||
# --- Helper Functions ---
|
# --- Helper Functions ---
|
||||||
|
# ... (El resto de las funciones helper: get_multilingual_text, get_symbol_name, etc. permanecen igual) ...
|
||||||
|
# --- (Incluye aquí todas tus funciones helper sin cambios) ---
|
||||||
def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"):
|
def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"):
|
||||||
# (Sin cambios respecto a la versión anterior)
|
# (Sin cambios respecto a la versión anterior)
|
||||||
if element is None:
|
if element is None:
|
||||||
|
@ -247,9 +249,7 @@ def parse_call(call_element):
|
||||||
call_data["instance_scope"] = instance_scope
|
call_data["instance_scope"] = instance_scope
|
||||||
return call_data
|
return call_data
|
||||||
|
|
||||||
|
# ... (Incluye aquí las funciones reconstruct_scl_from_tokens, get_access_text, get_comment_text, reconstruct_stl_from_statementlist, parse_interface_members, parse_network SIN CAMBIOS) ...
|
||||||
# SCL (Structured Text) Parser
|
|
||||||
|
|
||||||
|
|
||||||
def reconstruct_scl_from_tokens(st_node):
|
def reconstruct_scl_from_tokens(st_node):
|
||||||
"""
|
"""
|
||||||
|
@ -807,13 +807,18 @@ def parse_network(network_element):
|
||||||
# Buscar FlgNet usando namespace flg
|
# Buscar FlgNet usando namespace flg
|
||||||
flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns)
|
flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns)
|
||||||
if not flgnet_list:
|
if not flgnet_list:
|
||||||
|
# Intentar buscar directamente si está en la raíz (caso SCL/STL simple?)
|
||||||
|
# Esta parte puede necesitar ajuste si FlgNet no es el contenedor principal
|
||||||
|
# para lógica SCL/STL tokenizada.
|
||||||
|
# Si no hay FlgNet, no podemos parsear lógica LAD/FBD.
|
||||||
|
# El parseo de SCL/STL se hace en convert_xml_to_json, así que aquí devolvemos error si no hay FlgNet.
|
||||||
return {
|
return {
|
||||||
"id": network_id,
|
"id": network_id,
|
||||||
"title": network_title,
|
"title": network_title,
|
||||||
"comment": network_comment,
|
"comment": network_comment,
|
||||||
"logic": [],
|
"logic": [],
|
||||||
"language": "Unknown",
|
"language": "Unknown", # Podríamos intentar leer el lenguaje aquí también
|
||||||
"error": "FlgNet not found",
|
"error": "FlgNet not found for LAD/FBD parsing",
|
||||||
}
|
}
|
||||||
flgnet = flgnet_list[0]
|
flgnet = flgnet_list[0]
|
||||||
|
|
||||||
|
@ -885,6 +890,8 @@ def parse_network(network_element):
|
||||||
|
|
||||||
# 3. Construcción Lógica Inicial (sin cambios en lógica, pero verificar llamadas)
|
# 3. Construcción Lógica Inicial (sin cambios en lógica, pero verificar llamadas)
|
||||||
all_logic_steps = {}
|
all_logic_steps = {}
|
||||||
|
# Define SCL_SUFFIX, por ejemplo, "_scl" o "_sympy_processed"
|
||||||
|
SCL_SUFFIX = "_sympy_processed" # Asegúrate que esto coincida con x2_process.py
|
||||||
functional_block_types = [
|
functional_block_types = [
|
||||||
"Move",
|
"Move",
|
||||||
"Add",
|
"Add",
|
||||||
|
@ -1198,6 +1205,7 @@ def parse_network(network_element):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Main Conversion Function ---
|
||||||
def convert_xml_to_json(xml_filepath, json_filepath):
|
def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
||||||
if not os.path.exists(xml_filepath):
|
if not os.path.exists(xml_filepath):
|
||||||
|
@ -1209,9 +1217,11 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
tree = etree.parse(xml_filepath, parser)
|
tree = etree.parse(xml_filepath, parser)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
print("Paso 1: Parseo XML completado.")
|
print("Paso 1: Parseo XML completado.")
|
||||||
print("Paso 2: Buscando el bloque SW.Blocks.FC, SW.Blocks.FB o SW.Blocks.GlobalDB...")
|
|
||||||
# --- MODIFICADO: Buscar FC, FB o GlobalDB ---
|
# --- MODIFICADO: Buscar FC, FB, GlobalDB o OB ---
|
||||||
block_list = root.xpath("//*[local-name()='SW.Blocks.FC' or local-name()='SW.Blocks.FB' or local-name()='SW.Blocks.GlobalDB']")
|
print("Paso 2: Buscando el bloque SW.Blocks.FC, SW.Blocks.FB, SW.Blocks.GlobalDB o SW.Blocks.OB...")
|
||||||
|
block_list = root.xpath("//*[local-name()='SW.Blocks.FC' or local-name()='SW.Blocks.FB' or local-name()='SW.Blocks.GlobalDB' or local-name()='SW.Blocks.OB']") # <-- Añadido OB
|
||||||
|
|
||||||
block_type_found = None
|
block_type_found = None
|
||||||
the_block = None
|
the_block = None
|
||||||
|
|
||||||
|
@ -1224,11 +1234,13 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
elif block_tag_name == "SW.Blocks.FB":
|
elif block_tag_name == "SW.Blocks.FB":
|
||||||
block_type_found = "FB"
|
block_type_found = "FB"
|
||||||
elif block_tag_name == "SW.Blocks.GlobalDB":
|
elif block_tag_name == "SW.Blocks.GlobalDB":
|
||||||
block_type_found = "GlobalDB" # Identificar el tipo DB
|
block_type_found = "GlobalDB"
|
||||||
|
elif block_tag_name == "SW.Blocks.OB": # <-- Añadido caso OB
|
||||||
|
block_type_found = "OB" # <-- Establecer tipo OB
|
||||||
print(f"Paso 2: Bloque {block_tag_name} encontrado (ID={the_block.get('ID')}).")
|
print(f"Paso 2: Bloque {block_tag_name} encontrado (ID={the_block.get('ID')}).")
|
||||||
else:
|
else:
|
||||||
# Mensaje de error más específico y añadimos depuración
|
# Mensaje de error actualizado
|
||||||
print("Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC>, <SW.Blocks.FB> o <SW.Blocks.GlobalDB>) usando XPath.")
|
print("Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC>, <SW.Blocks.FB>, <SW.Blocks.GlobalDB> o <SW.Blocks.OB>) usando XPath.")
|
||||||
# --- Añadir Debugging ---
|
# --- Añadir Debugging ---
|
||||||
print(f"DEBUG: Tag del elemento raíz del XML: {root.tag}")
|
print(f"DEBUG: Tag del elemento raíz del XML: {root.tag}")
|
||||||
print(f"DEBUG: Primeros hijos del raíz:")
|
print(f"DEBUG: Primeros hijos del raíz:")
|
||||||
|
@ -1240,6 +1252,7 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
break
|
break
|
||||||
# --- Fin Debugging ---
|
# --- Fin Debugging ---
|
||||||
return # Salir si no se encuentra el bloque principal
|
return # Salir si no se encuentra el bloque principal
|
||||||
|
|
||||||
print("Paso 3: Extrayendo atributos del bloque...")
|
print("Paso 3: Extrayendo atributos del bloque...")
|
||||||
attribute_list_node = the_block.xpath("./*[local-name()='AttributeList']")
|
attribute_list_node = the_block.xpath("./*[local-name()='AttributeList']")
|
||||||
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown"
|
block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown"
|
||||||
|
@ -1255,7 +1268,9 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
lang_node = attr_list.xpath(
|
lang_node = attr_list.xpath(
|
||||||
"./*[local-name()='ProgrammingLanguage']/text()"
|
"./*[local-name()='ProgrammingLanguage']/text()"
|
||||||
)
|
)
|
||||||
block_lang_val = lang_node[0].strip() if lang_node else block_lang_val
|
# Para DBs, el lenguaje principal es 'DB'. Para OBs/FCs/FBs puede ser SCL, STL, LAD, FBD etc.
|
||||||
|
block_lang_val = lang_node[0].strip() if lang_node else ("DB" if block_type_found == "GlobalDB" else "Unknown")
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'"
|
f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'"
|
||||||
)
|
)
|
||||||
|
@ -1263,6 +1278,10 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
print(
|
print(
|
||||||
f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}."
|
f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}."
|
||||||
)
|
)
|
||||||
|
# Asignar 'DB' como lenguaje si es un GlobalDB y no se encontró explícitamente
|
||||||
|
if block_type_found == "GlobalDB":
|
||||||
|
block_lang_val = "DB"
|
||||||
|
|
||||||
block_comment_val = ""
|
block_comment_val = ""
|
||||||
comment_node_list = the_block.xpath(
|
comment_node_list = the_block.xpath(
|
||||||
"./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']"
|
"./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']"
|
||||||
|
@ -1270,51 +1289,65 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
if comment_node_list:
|
if comment_node_list:
|
||||||
block_comment_val = get_multilingual_text(comment_node_list[0])
|
block_comment_val = get_multilingual_text(comment_node_list[0])
|
||||||
print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'")
|
print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'")
|
||||||
|
|
||||||
|
# --- MODIFICADO: Añadir block_type al resultado ---
|
||||||
result = {
|
result = {
|
||||||
"block_name": block_name_val,
|
"block_name": block_name_val,
|
||||||
"block_number": block_number_val,
|
"block_number": block_number_val,
|
||||||
"language": block_lang_val,
|
"language": block_lang_val, # Lenguaje del bloque (SCL, LAD, DB, etc.)
|
||||||
|
"block_type": block_type_found, # Tipo de bloque (FC, FB, GlobalDB, OB)
|
||||||
"block_comment": block_comment_val,
|
"block_comment": block_comment_val,
|
||||||
"interface": {},
|
"interface": {},
|
||||||
"networks": [],
|
"networks": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Paso 4: Extrayendo la interfaz del bloque...")
|
print("Paso 4: Extrayendo la interfaz del bloque...")
|
||||||
if attribute_list_node:
|
# La estructura de la interfaz suele ser la misma para FC/FB/OB y la sección Static para DB
|
||||||
interface_node_list = attribute_list_node[0].xpath(
|
# Si Interface está dentro de AttributeList
|
||||||
".//*[local-name()='Interface']"
|
interface_node_list = attribute_list_node[0].xpath(".//*[local-name()='Interface']") if attribute_list_node else []
|
||||||
)
|
|
||||||
if interface_node_list:
|
if interface_node_list:
|
||||||
interface_node = interface_node_list[0]
|
interface_node = interface_node_list[0]
|
||||||
print("Paso 4: Nodo Interface encontrado.")
|
print("Paso 4: Nodo Interface encontrado.")
|
||||||
for section in interface_node.xpath(".//iface:Section", namespaces=ns):
|
# Usar parse_interface_members para extraer todas las secciones
|
||||||
|
# Esta función ya maneja structs anidados y arrays
|
||||||
|
all_sections = interface_node.xpath(".//iface:Section", namespaces=ns)
|
||||||
|
for section in all_sections:
|
||||||
section_name = section.get("Name")
|
section_name = section.get("Name")
|
||||||
if not section_name:
|
if not section_name:
|
||||||
continue
|
continue
|
||||||
members = []
|
# Obtener los miembros directos de esta sección
|
||||||
for member in section.xpath("./iface:Member", namespaces=ns):
|
# Asegurarse de no obtener miembros de secciones anidadas accidentalmente
|
||||||
member_name = member.get("Name")
|
members_in_section = section.xpath("./iface:Member", namespaces=ns)
|
||||||
member_dtype = member.get("Datatype")
|
if members_in_section:
|
||||||
if member_name and member_dtype:
|
result["interface"][section_name] = parse_interface_members(members_in_section)
|
||||||
members.append(
|
|
||||||
{"name": member_name, "datatype": member_dtype}
|
|
||||||
)
|
|
||||||
if members:
|
|
||||||
result["interface"][section_name] = members
|
|
||||||
if not result["interface"]:
|
if not result["interface"]:
|
||||||
print("Advertencia: Interface sin secciones iface:Section válidas.")
|
print("Advertencia: Interface encontrada pero sin secciones iface:Section válidas.")
|
||||||
else:
|
else:
|
||||||
print(
|
# Para GlobalDB, la interfaz podría no estar explícita o ser solo la sección Static
|
||||||
"Advertencia: No se encontró <Interface> dentro de <AttributeList>."
|
if block_type_found == "GlobalDB":
|
||||||
)
|
# Intentar buscar directamente los miembros bajo Sections/Section Name="Static"
|
||||||
|
static_members = the_block.xpath(".//iface:Section[@Name='Static']/iface:Member", namespaces=ns)
|
||||||
|
if static_members:
|
||||||
|
print("Paso 4: Encontrada sección Static para GlobalDB.")
|
||||||
|
result["interface"]["Static"] = parse_interface_members(static_members)
|
||||||
|
else:
|
||||||
|
print("Advertencia: No se encontró sección 'Static' para GlobalDB.")
|
||||||
|
else: # FC/FB/OB
|
||||||
|
print(f"Advertencia: No se encontró <Interface> para bloque {block_type_found}.")
|
||||||
|
|
||||||
if not result["interface"]:
|
if not result["interface"]:
|
||||||
print("Advertencia: No se pudo extraer información de la interfaz.")
|
print("Advertencia: No se pudo extraer información de la interfaz.")
|
||||||
|
|
||||||
|
|
||||||
print("Paso 5: Extrayendo y PROCESANDO lógica de redes (CompileUnits)...")
|
print("Paso 5: Extrayendo y PROCESANDO lógica de redes (CompileUnits)...")
|
||||||
networks_processed_count = 0
|
networks_processed_count = 0
|
||||||
result["networks"] = [] # Initialize networks list here
|
result["networks"] = [] # Initialize networks list here
|
||||||
object_list_node = the_block.xpath("./*[local-name()='ObjectList']")
|
object_list_node = the_block.xpath("./*[local-name()='ObjectList']")
|
||||||
|
|
||||||
if object_list_node:
|
if object_list_node:
|
||||||
|
# Buscar CompileUnits (para FC/FB/OB)
|
||||||
compile_units = object_list_node[0].xpath(
|
compile_units = object_list_node[0].xpath(
|
||||||
"./*[local-name()='SW.Blocks.CompileUnit']"
|
"./*[local-name()='SW.Blocks.CompileUnit']"
|
||||||
)
|
)
|
||||||
|
@ -1443,9 +1476,8 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
elif programming_language in ["LAD", "FBD"]:
|
elif programming_language in ["LAD", "FBD", "GRAPH"]: # GRAPH también usa FlgNet
|
||||||
# Para LAD/FBD, llamar a parse_network (que espera FlgNet dentro de NetworkSource)
|
# Para LAD/FBD/GRAPH, llamar a parse_network
|
||||||
# parse_network ya maneja su propio título/comentario si es necesario, pero podemos pasar los extraídos
|
|
||||||
# Nota: parse_network espera el *CompileUnit* element, no el NetworkSource
|
# Nota: parse_network espera el *CompileUnit* element, no el NetworkSource
|
||||||
parsed_network_data = parse_network(network_elem)
|
parsed_network_data = parse_network(network_elem)
|
||||||
if parsed_network_data:
|
if parsed_network_data:
|
||||||
|
@ -1453,19 +1485,42 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
programming_language # Asegurar que el lenguaje se guarda
|
programming_language # Asegurar que el lenguaje se guarda
|
||||||
)
|
)
|
||||||
if parsed_network_data.get("error"):
|
if parsed_network_data.get("error"):
|
||||||
|
# Si parse_network devuelve error (ej. no encontró FlgNet), lo registramos
|
||||||
print(
|
print(
|
||||||
f" Error al parsear red {programming_language} ID={network_id}: {parsed_network_data['error']}"
|
f" Error al parsear red {programming_language} ID={network_id}: {parsed_network_data['error']}"
|
||||||
)
|
)
|
||||||
# parsed_network_data = None # Descomentar para omitir redes con error
|
# Si es un error esperado para este lenguaje (ej. GRAPH sin FlgNet?), creamos placeholder
|
||||||
|
if "FlgNet not found" in parsed_network_data.get("error", ""):
|
||||||
|
parsed_network_data = {
|
||||||
|
"id": network_id,
|
||||||
|
"title": network_title,
|
||||||
|
"comment": network_comment,
|
||||||
|
"language": programming_language,
|
||||||
|
"logic": [{"instruction_uid": f"PLC_{network_id}", "type": "UNSUPPORTED_CONTENT", "info": f"Contenido {programming_language} sin FlgNet"}],
|
||||||
|
"error": parsed_network_data.get("error") # Mantener el error original
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
|
print(f" Red {programming_language} ID={network_id} parseada.")
|
||||||
|
|
||||||
|
else: # parse_network devolvió None (error interno)
|
||||||
print(
|
print(
|
||||||
f" Error: parse_network devolvió None para red {programming_language} ID={network_id}"
|
f" Error: parse_network devolvió None para red {programming_language} ID={network_id}"
|
||||||
)
|
)
|
||||||
|
# Crear placeholder de error
|
||||||
|
parsed_network_data = {
|
||||||
|
"id": network_id,
|
||||||
|
"title": network_title,
|
||||||
|
"comment": network_comment,
|
||||||
|
"language": programming_language,
|
||||||
|
"logic": [{"instruction_uid": f"ERR_{network_id}", "type": "PARSING_ERROR", "info": "parse_network returned None"}],
|
||||||
|
"error": "parse_network returned None"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Manejar otros lenguajes o casos inesperados
|
# Manejar otros lenguajes o casos inesperados
|
||||||
print(
|
print(
|
||||||
f" Advertencia: Lenguaje no soportado '{programming_language}' en red ID={network_id}. Creando placeholder."
|
f" Advertencia: Lenguaje no soportado o inesperado '{programming_language}' en red ID={network_id}. Creando placeholder."
|
||||||
)
|
)
|
||||||
parsed_network_data = {
|
parsed_network_data = {
|
||||||
"id": network_id,
|
"id": network_id,
|
||||||
|
@ -1476,9 +1531,10 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
{
|
{
|
||||||
"instruction_uid": f"UNS_{network_id}",
|
"instruction_uid": f"UNS_{network_id}",
|
||||||
"type": "UNSUPPORTED_LANG",
|
"type": "UNSUPPORTED_LANG",
|
||||||
"scl": f"// Network {network_id} uses unsupported language: {programming_language}\n",
|
"info": f"Network {network_id} uses unsupported language: {programming_language}",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"error": f"Unsupported language: {programming_language}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Añadir la red procesada (si es válida) al resultado
|
# Añadir la red procesada (si es válida) al resultado
|
||||||
|
@ -1487,17 +1543,21 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
|
|
||||||
# --- Fin del bucle for network_elem ---
|
# --- Fin del bucle for network_elem ---
|
||||||
|
|
||||||
if networks_processed_count == 0:
|
if networks_processed_count == 0 and block_type_found != "GlobalDB":
|
||||||
print(
|
print(
|
||||||
"Advertencia: ObjectList no contenía elementos SW.Blocks.CompileUnit."
|
f"Advertencia: ObjectList para bloque {block_type_found} no contenía elementos SW.Blocks.CompileUnit."
|
||||||
)
|
)
|
||||||
else:
|
# Para DBs, no esperamos CompileUnits
|
||||||
print("Advertencia: No se encontró ObjectList para el bloque.")
|
elif block_type_found == "GlobalDB":
|
||||||
|
print("Paso 5: Saltando búsqueda de CompileUnits para GlobalDB.")
|
||||||
|
else: # No se encontró ObjectList
|
||||||
|
print(f"Advertencia: No se encontró ObjectList para el bloque {block_type_found}.")
|
||||||
|
|
||||||
|
|
||||||
print("Paso 6: Escribiendo el resultado en el archivo JSON...")
|
print("Paso 6: Escribiendo el resultado en el archivo JSON...")
|
||||||
if not result["interface"]:
|
if not result["interface"]:
|
||||||
print("ADVERTENCIA FINAL: 'interface' está vacía.")
|
print("ADVERTENCIA FINAL: 'interface' está vacía.")
|
||||||
if not result["networks"]:
|
if not result["networks"] and block_type_found != "GlobalDB":
|
||||||
print("ADVERTENCIA FINAL: 'networks' está vacía.")
|
print("ADVERTENCIA FINAL: 'networks' está vacía.")
|
||||||
try:
|
try:
|
||||||
with open(json_filepath, "w", encoding="utf-8") as f:
|
with open(json_filepath, "w", encoding="utf-8") as f:
|
||||||
|
@ -1510,6 +1570,19 @@ def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
)
|
)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
print(f"Error Crítico: Problema al serializar a JSON. Error: {e}")
|
print(f"Error Crítico: Problema al serializar a JSON. Error: {e}")
|
||||||
|
print("--- Datos problemáticos (parcial) ---")
|
||||||
|
# Intentar imprimir partes del diccionario para depurar
|
||||||
|
for k, v in result.items():
|
||||||
|
try:
|
||||||
|
json.dumps({k: v}) # Intentar serializar cada parte
|
||||||
|
except TypeError:
|
||||||
|
print(f"Error serializando clave '{k}': {type(v)}")
|
||||||
|
if isinstance(v, list) and v:
|
||||||
|
print(f" Primer elemento tipo: {type(v[0])}")
|
||||||
|
elif isinstance(v, dict) and v:
|
||||||
|
print(f" Primeras claves: {list(v.keys())[:5]}")
|
||||||
|
print("--- Fin Datos problemáticos ---")
|
||||||
|
|
||||||
except etree.XMLSyntaxError as e:
|
except etree.XMLSyntaxError as e:
|
||||||
print(
|
print(
|
||||||
f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}"
|
f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}"
|
||||||
|
@ -1529,7 +1602,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Configurar ArgumentParser para recibir la ruta del XML obligatoria
|
# Configurar ArgumentParser para recibir la ruta del XML obligatoria
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Convert Simatic XML (LAD/FBD/SCL/STL) to simplified JSON. Expects XML filepath as argument."
|
description="Convert Simatic XML (LAD/FBD/SCL/STL/OB/DB) to simplified JSON. Expects XML filepath as argument." # Actualizada descripción
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"xml_filepath", # Argumento posicional obligatorio
|
"xml_filepath", # Argumento posicional obligatorio
|
||||||
|
@ -1557,12 +1630,11 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
|
|
||||||
# Llamar a la función principal de conversión del script
|
# Llamar a la función principal de conversión del script
|
||||||
# Asumiendo que tu función principal se llama convert_xml_to_json(input_path, output_path)
|
|
||||||
try:
|
try:
|
||||||
convert_xml_to_json(xml_input_file, json_output_file)
|
convert_xml_to_json(xml_input_file, json_output_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error Crítico (x1) durante la conversión de '{xml_input_file}': {e}")
|
print(f"Error Crítico (x1) durante la conversión de '{xml_input_file}': {e}")
|
||||||
import traceback
|
import traceback # Asegurarse de que traceback está importado aquí
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
122
x2_process.py
122
x2_process.py
|
@ -18,17 +18,14 @@ from processors.processor_utils import (
|
||||||
from processors.symbol_manager import SymbolManager # Import the manager
|
from processors.symbol_manager import SymbolManager # Import the manager
|
||||||
|
|
||||||
# --- Constantes y Configuración ---
|
# --- Constantes y Configuración ---
|
||||||
# SCL_SUFFIX = "_scl" # Old suffix
|
|
||||||
SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method
|
SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method
|
||||||
GROUPED_COMMENT = "// Logic included in grouped IF"
|
GROUPED_COMMENT = "// Logic included in grouped IF"
|
||||||
SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful
|
SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful
|
||||||
|
|
||||||
# Global data dictionary (consider passing 'data' as argument if needed elsewhere)
|
# Global data dictionary
|
||||||
# It's currently used by process_group_ifs implicitly via the outer scope,
|
|
||||||
# which works but passing it explicitly might be cleaner.
|
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
# --- (Incluye aquí las funciones process_group_ifs y load_processors SIN CAMBIOS) ---
|
||||||
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
"""
|
"""
|
||||||
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
|
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
|
||||||
|
@ -112,6 +109,9 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
# SCoil/RCoil might also be groupable if their SCL is final assignment
|
# SCoil/RCoil might also be groupable if their SCL is final assignment
|
||||||
"SCoil",
|
"SCoil",
|
||||||
"RCoil",
|
"RCoil",
|
||||||
|
"BLKMOV", # Added BLKMOV
|
||||||
|
"TON", "TOF", "TP", "Se", "Sd", # Added timers
|
||||||
|
"CTU", "CTD", "CTUD", # Added counters
|
||||||
]
|
]
|
||||||
|
|
||||||
for consumer_instr in network_logic:
|
for consumer_instr in network_logic:
|
||||||
|
@ -135,26 +135,26 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||||
is_enabled_by_us = True
|
is_enabled_by_us = True
|
||||||
|
|
||||||
# Check if consumer is groupable AND has its final SCL generated
|
# Check if consumer is groupable AND has its final SCL generated
|
||||||
# The suffix check needs adjustment based on how terminating processors set it.
|
|
||||||
# Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix.
|
|
||||||
if (
|
if (
|
||||||
is_enabled_by_us
|
is_enabled_by_us
|
||||||
and consumer_type.endswith(SCL_SUFFIX) # Or a specific "final_scl" suffix
|
and consumer_type.endswith(SCL_SUFFIX) # Check if processed
|
||||||
and consumer_type_original in groupable_types
|
and consumer_type_original in groupable_types
|
||||||
):
|
):
|
||||||
|
|
||||||
consumer_scl = consumer_instr.get("scl", "")
|
consumer_scl = consumer_instr.get("scl", "")
|
||||||
# Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now)
|
# Extract core SCL
|
||||||
core_scl = None
|
core_scl = None
|
||||||
if consumer_scl:
|
if consumer_scl:
|
||||||
# If consumer SCL itself is an IF generated by EN, take the body
|
# If consumer SCL itself is an IF generated by EN, take the body
|
||||||
if consumer_scl.strip().startswith("IF"):
|
if consumer_scl.strip().startswith("IF"):
|
||||||
match = re.search(
|
match = re.search(
|
||||||
r"THEN\s*(.*?)\s*END_IF;",
|
r"IF\s+.*?THEN\s*(.*?)\s*END_IF;", # More robust regex
|
||||||
consumer_scl,
|
consumer_scl,
|
||||||
re.DOTALL | re.IGNORECASE,
|
re.DOTALL | re.IGNORECASE,
|
||||||
)
|
)
|
||||||
core_scl = match.group(1).strip() if match else None
|
core_scl = match.group(1).strip() if match else None
|
||||||
|
# If body contains another IF, maybe don't group? (optional complexity)
|
||||||
|
# if core_scl and core_scl.strip().startswith("IF"): core_scl = None
|
||||||
elif not consumer_scl.strip().startswith(
|
elif not consumer_scl.strip().startswith(
|
||||||
"//"
|
"//"
|
||||||
): # Otherwise, take the whole line if not comment
|
): # Otherwise, take the whole line if not comment
|
||||||
|
@ -300,8 +300,7 @@ def load_processors(processors_dir="processors"):
|
||||||
# Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada
|
# Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada
|
||||||
return processor_map, processor_list_sorted
|
return processor_map, processor_list_sorted
|
||||||
|
|
||||||
|
# --- Bucle Principal de Procesamiento (Modificado para STL y tipo de bloque) ---
|
||||||
# --- Bucle Principal de Procesamiento (Modificado para STL) ---
|
|
||||||
def process_json_to_scl(json_filepath):
|
def process_json_to_scl(json_filepath):
|
||||||
"""
|
"""
|
||||||
Lee JSON simplificado, aplica procesadores dinámicos (ignorando redes STL y bloques DB),
|
Lee JSON simplificado, aplica procesadores dinámicos (ignorando redes STL y bloques DB),
|
||||||
|
@ -321,15 +320,14 @@ def process_json_to_scl(json_filepath):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Obtener lenguaje del bloque principal ---
|
# --- MODIFICADO: Obtener tipo de bloque (FC, FB, GlobalDB, OB) ---
|
||||||
block_language = data.get("language", "Unknown")
|
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB, OB
|
||||||
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
|
print(f"Procesando bloque tipo: {block_type}, Lenguaje principal: {data.get('language', 'Unknown')}")
|
||||||
print(f"Procesando bloque tipo: {block_type}, Lenguaje principal: {block_language}")
|
|
||||||
|
|
||||||
# --- SI ES UN DB, SALTAR EL PROCESAMIENTO LÓGICO ---
|
# --- MODIFICADO: SI ES UN GlobalDB, SALTAR EL PROCESAMIENTO LÓGICO ---
|
||||||
if block_language == "DB":
|
if block_type == "GlobalDB": # <-- Comprobar tipo de bloque
|
||||||
print(
|
print(
|
||||||
"INFO: El bloque es un Data Block (DB). Saltando procesamiento lógico de x2."
|
"INFO: El bloque es un Data Block (GlobalDB). Saltando procesamiento lógico de x2."
|
||||||
)
|
)
|
||||||
# Simplemente guardamos una copia (o el mismo archivo si no se requiere sufijo)
|
# Simplemente guardamos una copia (o el mismo archivo si no se requiere sufijo)
|
||||||
output_filename = json_filepath.replace(
|
output_filename = json_filepath.replace(
|
||||||
|
@ -345,8 +343,8 @@ def process_json_to_scl(json_filepath):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return # <<< SALIR TEMPRANO PARA DBs
|
return # <<< SALIR TEMPRANO PARA DBs
|
||||||
|
|
||||||
# --- SI NO ES DB, CONTINUAR CON EL PROCESAMIENTO LÓGICO (FC/FB) ---
|
# --- SI NO ES DB (FC, FB, OB), CONTINUAR CON EL PROCESAMIENTO LÓGICO ---
|
||||||
print("INFO: El bloque es FC/FB. Iniciando procesamiento lógico...")
|
print(f"INFO: El bloque es {block_type}. Iniciando procesamiento lógico...") # <-- Mensaje actualizado
|
||||||
|
|
||||||
script_dir = os.path.dirname(__file__)
|
script_dir = os.path.dirname(__file__)
|
||||||
processors_dir_path = os.path.join(script_dir, "processors")
|
processors_dir_path = os.path.join(script_dir, "processors")
|
||||||
|
@ -391,7 +389,7 @@ def process_json_to_scl(json_filepath):
|
||||||
passes = 0
|
passes = 0
|
||||||
processing_complete = False
|
processing_complete = False
|
||||||
|
|
||||||
print("\n--- Iniciando Bucle de Procesamiento Iterativo (FC/FB) ---")
|
print(f"\n--- Iniciando Bucle de Procesamiento Iterativo ({block_type}) ---") # <-- Mensaje actualizado
|
||||||
while passes < max_passes and not processing_complete:
|
while passes < max_passes and not processing_complete:
|
||||||
passes += 1
|
passes += 1
|
||||||
made_change_in_base_pass = False
|
made_change_in_base_pass = False
|
||||||
|
@ -408,34 +406,44 @@ def process_json_to_scl(json_filepath):
|
||||||
func_to_call = processor_info["func"]
|
func_to_call = processor_info["func"]
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
network_id = network["id"]
|
network_id = network["id"]
|
||||||
network_lang = network.get("language", "LAD")
|
network_lang = network.get("language", "LAD") # Lenguaje de la red
|
||||||
if network_lang == "STL":
|
if network_lang == "STL": # Saltar redes STL
|
||||||
continue # Saltar STL
|
continue
|
||||||
|
|
||||||
access_map = network_access_maps.get(network_id, {})
|
access_map = network_access_maps.get(network_id, {})
|
||||||
network_logic = network.get("logic", [])
|
network_logic = network.get("logic", [])
|
||||||
for instruction in network_logic:
|
for instruction in network_logic:
|
||||||
instr_uid = instruction.get("instruction_uid")
|
instr_uid = instruction.get("instruction_uid")
|
||||||
instr_type_original = instruction.get("type", "Unknown")
|
# Usar el tipo *actual* de la instrucción para el lookup
|
||||||
|
instr_type_current = instruction.get("type", "Unknown")
|
||||||
|
|
||||||
|
# Saltar si ya está procesado, es error, agrupado, o tipo crudo
|
||||||
if (
|
if (
|
||||||
instr_type_original.endswith(SCL_SUFFIX)
|
instr_type_current.endswith(SCL_SUFFIX)
|
||||||
or "_error" in instr_type_original
|
or "_error" in instr_type_current
|
||||||
or instruction.get("grouped", False)
|
or instruction.get("grouped", False)
|
||||||
or instr_type_original
|
or instr_type_current
|
||||||
in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
|
in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG", "UNSUPPORTED_CONTENT", "PARSING_ERROR"]
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
lookup_key = instr_type_original.lower()
|
|
||||||
effective_type_name = lookup_key
|
|
||||||
if instr_type_original == "Call":
|
|
||||||
block_type = instruction.get("block_type", "").upper()
|
|
||||||
if block_type == "FC":
|
|
||||||
effective_type_name = "call_fc"
|
|
||||||
elif block_type == "FB":
|
|
||||||
effective_type_name = "call_fb"
|
|
||||||
|
|
||||||
|
# El lookup usa el tipo actual (que aún no tiene el sufijo)
|
||||||
|
lookup_key = instr_type_current.lower()
|
||||||
|
effective_type_name = lookup_key
|
||||||
|
|
||||||
|
# Mapeo especial para llamadas FC/FB
|
||||||
|
if instr_type_current == "Call":
|
||||||
|
call_block_type = instruction.get("block_type", "").upper()
|
||||||
|
if call_block_type == "FC":
|
||||||
|
effective_type_name = "call_fc"
|
||||||
|
elif call_block_type == "FB":
|
||||||
|
effective_type_name = "call_fb"
|
||||||
|
# Añadir otros tipos de llamada si es necesario
|
||||||
|
|
||||||
|
# Si el tipo efectivo coincide con el procesador actual
|
||||||
if effective_type_name == current_type_name:
|
if effective_type_name == current_type_name:
|
||||||
try:
|
try:
|
||||||
|
# Pasar 'data' a la función del procesador
|
||||||
changed = func_to_call(
|
changed = func_to_call(
|
||||||
instruction, network_id, sympy_map, symbol_manager, data
|
instruction, network_id, sympy_map, symbol_manager, data
|
||||||
)
|
)
|
||||||
|
@ -444,22 +452,24 @@ def process_json_to_scl(json_filepath):
|
||||||
num_sympy_processed_this_pass += 1
|
num_sympy_processed_this_pass += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}"
|
f"ERROR(SymPy Base) al procesar {instr_type_current} UID {instr_uid}: {e}"
|
||||||
)
|
)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
instruction["scl"] = (
|
instruction["scl"] = (
|
||||||
f"// ERROR en SymPy procesador base: {e}"
|
f"// ERROR en SymPy procesador base: {e}"
|
||||||
)
|
)
|
||||||
instruction["type"] = instr_type_original + "_error"
|
# Añadir sufijo de error al tipo actual
|
||||||
made_change_in_base_pass = True
|
instruction["type"] = instr_type_current + "_error"
|
||||||
|
made_change_in_base_pass = True # Se hizo un cambio (marcar como error)
|
||||||
print(
|
print(
|
||||||
f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy."
|
f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- FASE 2: Agrupación IF (Ignorando STL) ---
|
# --- FASE 2: Agrupación IF (Ignorando STL) ---
|
||||||
if (
|
if (
|
||||||
made_change_in_base_pass or passes == 1
|
made_change_in_base_pass or passes == 1
|
||||||
): # Ejecutar siempre en el primer pase
|
): # Ejecutar siempre en el primer pase o si hubo cambios
|
||||||
print(f" Fase 2 (Agrupación IF con Simplificación):")
|
print(f" Fase 2 (Agrupación IF con Simplificación):")
|
||||||
num_grouped_this_pass = 0 # Resetear contador para el pase
|
num_grouped_this_pass = 0 # Resetear contador para el pase
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
|
@ -468,7 +478,18 @@ def process_json_to_scl(json_filepath):
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
continue # Saltar STL
|
continue # Saltar STL
|
||||||
network_logic = network.get("logic", [])
|
network_logic = network.get("logic", [])
|
||||||
for instruction in network_logic:
|
# Iterar en orden por UID puede ser más estable para agrupación
|
||||||
|
uids_in_network = sorted([instr.get("instruction_uid", "Z") for instr in network_logic if instr.get("instruction_uid")])
|
||||||
|
for uid_to_process in uids_in_network:
|
||||||
|
instruction = next((instr for instr in network_logic if instr.get("instruction_uid") == uid_to_process), None)
|
||||||
|
if not instruction: continue
|
||||||
|
|
||||||
|
# Saltar si ya está agrupada, es error, etc.
|
||||||
|
if instruction.get("grouped") or "_error" in instruction.get("type", ""):
|
||||||
|
continue
|
||||||
|
# La agrupación sólo aplica a instrucciones que generan condiciones booleanas
|
||||||
|
# y que ya fueron procesadas (tienen el sufijo)
|
||||||
|
if instruction.get("type", "").endswith(SCL_SUFFIX):
|
||||||
try:
|
try:
|
||||||
group_changed = process_group_ifs(
|
group_changed = process_group_ifs(
|
||||||
instruction, network_id, sympy_map, symbol_manager, data
|
instruction, network_id, sympy_map, symbol_manager, data
|
||||||
|
@ -503,14 +524,16 @@ def process_json_to_scl(json_filepath):
|
||||||
# --- FIN BUCLE ITERATIVO ---
|
# --- FIN BUCLE ITERATIVO ---
|
||||||
|
|
||||||
# --- Verificación Final (Ajustada para RAW_STL_CHUNK) ---
|
# --- Verificación Final (Ajustada para RAW_STL_CHUNK) ---
|
||||||
print("\n--- Verificación Final de Instrucciones No Procesadas (FC/FB) ---")
|
print(f"\n--- Verificación Final de Instrucciones No Procesadas ({block_type}) ---") # <-- Mensaje actualizado
|
||||||
unprocessed_count = 0
|
unprocessed_count = 0
|
||||||
unprocessed_details = []
|
unprocessed_details = []
|
||||||
ignored_types = [
|
ignored_types = [
|
||||||
"raw_scl_chunk",
|
"raw_scl_chunk",
|
||||||
"unsupported_lang",
|
"unsupported_lang",
|
||||||
"raw_stl_chunk",
|
"raw_stl_chunk",
|
||||||
] # Añadido raw_stl_chunk
|
"unsupported_content", # Añadido de x1
|
||||||
|
"parsing_error", # Añadido de x1
|
||||||
|
]
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
network_id = network.get("id", "Unknown ID")
|
network_id = network.get("id", "Unknown ID")
|
||||||
network_title = network.get("title", f"Network {network_id}")
|
network_title = network.get("title", f"Network {network_id}")
|
||||||
|
@ -547,7 +570,7 @@ def process_json_to_scl(json_filepath):
|
||||||
output_filename = json_filepath.replace(
|
output_filename = json_filepath.replace(
|
||||||
"_simplified.json", "_simplified_processed.json"
|
"_simplified.json", "_simplified_processed.json"
|
||||||
)
|
)
|
||||||
print(f"\nGuardando JSON procesado (FC/FB) en: {output_filename}")
|
print(f"\nGuardando JSON procesado ({block_type}) en: {output_filename}") # <-- Mensaje actualizado
|
||||||
try:
|
try:
|
||||||
with open(output_filename, "w", encoding="utf-8") as f:
|
with open(output_filename, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
|
@ -557,7 +580,7 @@ def process_json_to_scl(json_filepath):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
# --- Ejecución (sin cambios) ---
|
# --- Ejecución (sin cambios en esta parte) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Imports necesarios solo para la ejecución como script principal
|
# Imports necesarios solo para la ejecución como script principal
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -577,12 +600,10 @@ if __name__ == "__main__":
|
||||||
source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original
|
source_xml_file = args.source_xml_filepath # Obtiene la ruta del XML original
|
||||||
|
|
||||||
# Verificar si el archivo XML original existe (como referencia, útil para depuración)
|
# Verificar si el archivo XML original existe (como referencia, útil para depuración)
|
||||||
# No es estrictamente necesario para la lógica aquí, pero ayuda a confirmar
|
|
||||||
if not os.path.exists(source_xml_file):
|
if not os.path.exists(source_xml_file):
|
||||||
print(
|
print(
|
||||||
f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente."
|
f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente."
|
||||||
)
|
)
|
||||||
# No salir necesariamente, pero es bueno saberlo.
|
|
||||||
|
|
||||||
# Derivar nombre del archivo JSON de entrada (_simplified.json)
|
# Derivar nombre del archivo JSON de entrada (_simplified.json)
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
||||||
|
@ -610,14 +631,13 @@ if __name__ == "__main__":
|
||||||
sys.exit(1) # Salir si el archivo necesario no está
|
sys.exit(1) # Salir si el archivo necesario no está
|
||||||
else:
|
else:
|
||||||
# Llamar a la función principal de procesamiento del script
|
# Llamar a la función principal de procesamiento del script
|
||||||
# Asumiendo que tu función principal se llama process_json_to_scl(input_json_path)
|
|
||||||
try:
|
try:
|
||||||
process_json_to_scl(input_json_file)
|
process_json_to_scl(input_json_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}"
|
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}"
|
||||||
)
|
)
|
||||||
import traceback
|
import traceback # Asegurar que traceback está importado
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
|
@ -46,24 +46,23 @@ except ImportError:
|
||||||
# para formatear valores iniciales
|
# para formatear valores iniciales
|
||||||
def format_scl_start_value(value, datatype):
|
def format_scl_start_value(value, datatype):
|
||||||
"""Formatea un valor para la inicialización SCL según el tipo."""
|
"""Formatea un valor para la inicialización SCL según el tipo."""
|
||||||
|
# Add initial debug print
|
||||||
|
# print(f"DEBUG format_scl_start_value: value='{value}', datatype='{datatype}'")
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None # Retornar None si no hay valor
|
||||||
datatype_lower = datatype.lower() if datatype else ""
|
datatype_lower = datatype.lower() if datatype else ""
|
||||||
value_str = str(value)
|
value_str = str(value)
|
||||||
|
|
||||||
if "bool" in datatype_lower:
|
# Intentar quitar comillas si existen (para manejar "TRUE" vs TRUE)
|
||||||
return "TRUE" if value_str.lower() == "true" else "FALSE"
|
if value_str.startswith('"') and value_str.endswith('"') and len(value_str) > 1:
|
||||||
elif "string" in datatype_lower:
|
value_str_unquoted = value_str[1:-1]
|
||||||
escaped_value = value_str.replace("'", "''")
|
elif value_str.startswith("'") and value_str.endswith("'") and len(value_str) > 1:
|
||||||
if escaped_value.startswith("'") and escaped_value.endswith("'"):
|
value_str_unquoted = value_str[1:-1]
|
||||||
escaped_value = escaped_value[1:-1]
|
else:
|
||||||
return f"'{escaped_value}'"
|
value_str_unquoted = value_str
|
||||||
elif "char" in datatype_lower: # Añadido Char
|
|
||||||
escaped_value = value_str.replace("'", "''")
|
# --- Integer-like types ---
|
||||||
if escaped_value.startswith("'") and escaped_value.endswith("'"):
|
if any(
|
||||||
escaped_value = escaped_value[1:-1]
|
|
||||||
return f"'{escaped_value}'"
|
|
||||||
elif any(
|
|
||||||
t in datatype_lower
|
t in datatype_lower
|
||||||
for t in [
|
for t in [
|
||||||
"int",
|
"int",
|
||||||
|
@ -79,72 +78,169 @@ def format_scl_start_value(value, datatype):
|
||||||
"udint",
|
"udint",
|
||||||
"ulint",
|
"ulint",
|
||||||
]
|
]
|
||||||
): # Ampliado
|
):
|
||||||
try:
|
try:
|
||||||
return str(int(value_str))
|
# Intentar convertir el valor (sin comillas) a entero
|
||||||
|
return str(int(value_str_unquoted))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str):
|
# Si no es un entero válido, podría ser una constante simbólica
|
||||||
return value_str
|
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted):
|
||||||
return f"'{value_str}'" # O como string si no es entero ni símbolo
|
return value_str_unquoted # Devolver como símbolo
|
||||||
|
|
||||||
|
# --- Fallback for non-integer, non-symbol ---
|
||||||
|
print(
|
||||||
|
f"DEBUG format_scl_start_value: Fallback for int-like. value_str_unquoted='{repr(value_str_unquoted)}', datatype='{datatype}'"
|
||||||
|
) # More debug
|
||||||
|
# MODIFIED FALLBACK: Escape newlines and use repr() for safety before formatting
|
||||||
|
try:
|
||||||
|
# Escape backslashes and single quotes properly for SCL string literal
|
||||||
|
escaped_for_scl = value_str_unquoted.replace("\\", "\\\\").replace(
|
||||||
|
"'", "''"
|
||||||
|
)
|
||||||
|
# Remove potential newlines that break Python f-string; SCL strings usually don't span lines implicitly
|
||||||
|
escaped_for_scl = escaped_for_scl.replace("\n", "").replace("\r", "")
|
||||||
|
# Format as SCL string literal
|
||||||
|
formatted_scl_string = f"'{escaped_for_scl}'"
|
||||||
|
print(
|
||||||
|
f"DEBUG format_scl_start_value: Fallback result='{formatted_scl_string}'"
|
||||||
|
)
|
||||||
|
return formatted_scl_string
|
||||||
|
except Exception as format_exc:
|
||||||
|
print(
|
||||||
|
f"ERROR format_scl_start_value: Exception during fallback formatting: {format_exc}"
|
||||||
|
)
|
||||||
|
return f"'ERROR_FORMATTING_{value_str_unquoted[:20]}'" # Return an error string
|
||||||
|
|
||||||
|
# --- Other types (Bool, Real, String, Char, Time, Date, etc.) ---
|
||||||
|
elif "bool" in datatype_lower:
|
||||||
|
# Comparar sin importar mayúsculas/minúsculas y sin comillas
|
||||||
|
return "TRUE" if value_str_unquoted.lower() == "true" else "FALSE"
|
||||||
|
elif "string" in datatype_lower:
|
||||||
|
# Usar el valor sin comillas originales y escapar las internas
|
||||||
|
escaped_value = value_str_unquoted.replace("'", "''")
|
||||||
|
return f"'{escaped_value}'"
|
||||||
|
elif "char" in datatype_lower:
|
||||||
|
# Usar el valor sin comillas originales y escapar las internas
|
||||||
|
escaped_value = value_str_unquoted.replace("'", "''")
|
||||||
|
# SCL usa comillas simples para Char. Asegurar que sea un solo caracter si es posible?
|
||||||
|
# Por ahora, solo formatear. Longitud se verifica en TIA.
|
||||||
|
return f"'{escaped_value}'"
|
||||||
elif "real" in datatype_lower or "lreal" in datatype_lower:
|
elif "real" in datatype_lower or "lreal" in datatype_lower:
|
||||||
try:
|
try:
|
||||||
f_val = float(value_str)
|
# Intentar convertir a float
|
||||||
|
f_val = float(value_str_unquoted)
|
||||||
s_val = str(f_val)
|
s_val = str(f_val)
|
||||||
|
# Asegurar que tenga punto decimal si es entero
|
||||||
if "." not in s_val and "e" not in s_val.lower():
|
if "." not in s_val and "e" not in s_val.lower():
|
||||||
s_val += ".0"
|
s_val += ".0"
|
||||||
return s_val
|
return s_val
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str):
|
# Podría ser constante simbólica
|
||||||
return value_str
|
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value_str_unquoted):
|
||||||
return f"'{value_str}'"
|
return value_str_unquoted
|
||||||
elif "time" in datatype_lower: # Añadido Time, S5Time, LTime
|
print(
|
||||||
# Quitar T#, LT#, S5T# si existen
|
f"Advertencia: Valor '{value_str}' no reconocido como real o símbolo para tipo {datatype}. Devolviendo como string."
|
||||||
|
)
|
||||||
|
# Use the robust fallback formatting here too
|
||||||
|
escaped_for_scl = (
|
||||||
|
value_str_unquoted.replace("\\", "\\\\")
|
||||||
|
.replace("'", "''")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("\r", "")
|
||||||
|
)
|
||||||
|
return f"'{escaped_for_scl}'"
|
||||||
|
elif "time" in datatype_lower:
|
||||||
|
# Quitar prefijos y añadir el correcto según el tipo específico
|
||||||
prefix = ""
|
prefix = ""
|
||||||
if value_str.upper().startswith("T#"):
|
val_to_use = value_str_unquoted # Usar valor sin comillas
|
||||||
|
if val_to_use.upper().startswith("T#"):
|
||||||
prefix = "T#"
|
prefix = "T#"
|
||||||
value_str = value_str[2:]
|
val_to_use = val_to_use[2:]
|
||||||
elif value_str.upper().startswith("LT#"):
|
elif val_to_use.upper().startswith("LT#"):
|
||||||
prefix = "LT#"
|
prefix = "LT#"
|
||||||
value_str = value_str[3:]
|
val_to_use = val_to_use[3:]
|
||||||
elif value_str.upper().startswith("S5T#"):
|
elif val_to_use.upper().startswith("S5T#"):
|
||||||
prefix = "S5T#"
|
prefix = "S5T#"
|
||||||
value_str = value_str[4:]
|
val_to_use = val_to_use[4:]
|
||||||
# Devolver con el prefijo correcto o T# por defecto si no había
|
|
||||||
if prefix:
|
if "s5time" in datatype_lower:
|
||||||
return f"{prefix}{value_str}"
|
return f"S5T#{val_to_use}"
|
||||||
elif "s5time" in datatype_lower:
|
|
||||||
return f"S5T#{value_str}"
|
|
||||||
elif "ltime" in datatype_lower:
|
elif "ltime" in datatype_lower:
|
||||||
return f"LT#{value_str}"
|
return f"LT#{val_to_use}"
|
||||||
else:
|
else:
|
||||||
return f"T#{value_str}" # Default a TIME
|
return f"T#{val_to_use}" # Default a TIME
|
||||||
elif "date" in datatype_lower: # Añadido Date, DT, TOD
|
elif "date" in datatype_lower:
|
||||||
if value_str.upper().startswith("D#"):
|
val_to_use = value_str_unquoted
|
||||||
return value_str
|
# Handle DTL first as it's longer
|
||||||
elif "dt" in datatype_lower or "date_and_time" in datatype_lower:
|
if "dtl" in datatype_lower or "date_and_time" in datatype_lower:
|
||||||
if value_str.upper().startswith("DT#"):
|
prefix = "DTL#" if val_to_use.upper().startswith("DTL#") else "DTL#"
|
||||||
return value_str
|
val_to_use = (
|
||||||
else:
|
val_to_use[4:] if val_to_use.upper().startswith("DTL#") else val_to_use
|
||||||
return f"DT#{value_str}" # Añadir prefijo DT#
|
)
|
||||||
|
return f"{prefix}{val_to_use}"
|
||||||
|
elif "dt" in datatype_lower:
|
||||||
|
prefix = "DT#" if val_to_use.upper().startswith("DT#") else "DT#"
|
||||||
|
val_to_use = (
|
||||||
|
val_to_use[3:] if val_to_use.upper().startswith("DT#") else val_to_use
|
||||||
|
)
|
||||||
|
return f"{prefix}{val_to_use}"
|
||||||
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
|
elif "tod" in datatype_lower or "time_of_day" in datatype_lower:
|
||||||
if value_str.upper().startswith("TOD#"):
|
prefix = "TOD#" if val_to_use.upper().startswith("TOD#") else "TOD#"
|
||||||
return value_str
|
val_to_use = (
|
||||||
else:
|
val_to_use[4:] if val_to_use.upper().startswith("TOD#") else val_to_use
|
||||||
return f"TOD#{value_str}" # Añadir prefijo TOD#
|
)
|
||||||
else:
|
return f"{prefix}{val_to_use}"
|
||||||
return f"D#{value_str}" # Default a Date
|
else: # Default a Date D#
|
||||||
# Fallback genérico
|
prefix = "D#" if val_to_use.upper().startswith("D#") else "D#"
|
||||||
|
val_to_use = (
|
||||||
|
val_to_use[2:] if val_to_use.upper().startswith("D#") else val_to_use
|
||||||
|
)
|
||||||
|
return f"{prefix}{val_to_use}"
|
||||||
|
|
||||||
|
# --- Fallback for completely unknown types or complex structures ---
|
||||||
else:
|
else:
|
||||||
|
# Si es un nombre válido (posiblemente UDT, constante global, etc.), devolverlo tal cual
|
||||||
|
# Ajustar regex para permitir más caracteres si es necesario
|
||||||
if re.match(
|
if re.match(
|
||||||
r'^[a-zA-Z_][a-zA-Z0-9_."#\[\]]+$', value_str
|
r'^[a-zA-Z_#"][a-zA-Z0-9_."#\[\]%]+$', value_str
|
||||||
): # Permitir más caracteres en símbolos/tipos
|
): # Permitir % para accesos tipo %DB1.DBD0
|
||||||
# Si es un UDT o Struct complejo, podría venir con comillas, quitarlas
|
# Quitar comillas externas si es un UDT o struct complejo
|
||||||
if value_str.startswith('"') and value_str.endswith('"'):
|
if (
|
||||||
|
value_str.startswith('"')
|
||||||
|
and value_str.endswith('"')
|
||||||
|
and len(value_str) > 1
|
||||||
|
):
|
||||||
return value_str[1:-1]
|
return value_str[1:-1]
|
||||||
|
# Mantener comillas si es acceso a DB ("DB_Name".Var)
|
||||||
|
if '"' in value_str and "." in value_str and value_str.count('"') == 2:
|
||||||
|
return value_str
|
||||||
|
# Si no tiene comillas y es un nombre simple o acceso #temp o %I0.0 etc
|
||||||
|
if not value_str.startswith('"') and not value_str.startswith("'"):
|
||||||
|
# Formatear nombres simples, pero dejar accesos % y # tal cual
|
||||||
|
if value_str.startswith("#") or value_str.startswith("%"):
|
||||||
return value_str
|
return value_str
|
||||||
else:
|
else:
|
||||||
escaped_value = value_str.replace("'", "''")
|
# return format_variable_name(value_str) # Evitar formatear aquí, puede ser una constante
|
||||||
return f"'{escaped_value}'"
|
return value_str # Return as is if it looks symbolic
|
||||||
|
# Devolver el valor original si tiene comillas internas o estructura compleja no manejada arriba
|
||||||
|
return value_str
|
||||||
|
else:
|
||||||
|
# Si no parece un nombre/símbolo/acceso, tratarlo como string (último recurso)
|
||||||
|
print(
|
||||||
|
f"DEBUG format_scl_start_value: Fallback final. value_str_unquoted='{repr(value_str_unquoted)}', datatype='{datatype}'"
|
||||||
|
)
|
||||||
|
# Use the robust fallback formatting
|
||||||
|
escaped_for_scl = (
|
||||||
|
value_str_unquoted.replace("\\", "\\\\")
|
||||||
|
.replace("'", "''")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("\r", "")
|
||||||
|
)
|
||||||
|
return f"'{escaped_for_scl}'"
|
||||||
|
|
||||||
|
|
||||||
|
# ... (generate_scl_declarations and generate_scl function remain the same as the previous version) ...
|
||||||
|
# --- (Incluye aquí las funciones generate_scl_declarations y generate_scl SIN CAMBIOS respecto a la respuesta anterior) ---
|
||||||
|
|
||||||
|
|
||||||
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
|
# --- NUEVA FUNCIÓN RECURSIVA para generar declaraciones SCL (VAR/STRUCT/ARRAY) ---
|
||||||
|
@ -155,87 +251,132 @@ def generate_scl_declarations(variables, indent_level=1):
|
||||||
for var in variables:
|
for var in variables:
|
||||||
var_name_scl = format_variable_name(var.get("name"))
|
var_name_scl = format_variable_name(var.get("name"))
|
||||||
var_dtype_raw = var.get("datatype", "VARIANT")
|
var_dtype_raw = var.get("datatype", "VARIANT")
|
||||||
# Limpiar comillas de tipos de datos UDT ("MyType" -> MyType)
|
|
||||||
var_dtype = (
|
|
||||||
var_dtype_raw.strip('"')
|
|
||||||
if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"')
|
|
||||||
else var_dtype_raw
|
|
||||||
)
|
|
||||||
|
|
||||||
var_comment = var.get("comment")
|
var_comment = var.get("comment")
|
||||||
start_value = var.get("start_value")
|
start_value = var.get("start_value")
|
||||||
children = var.get("children") # Para structs
|
children = var.get("children") # Para structs
|
||||||
array_elements = var.get("array_elements") # Para arrays
|
array_elements = var.get("array_elements") # Para arrays
|
||||||
|
|
||||||
# Manejar tipos de datos Array especiales
|
# Limpiar comillas del tipo de dato si es UDT/String/etc.
|
||||||
array_match = re.match(r"(Array\[.*\]\s+of\s+)(.*)", var_dtype, re.IGNORECASE)
|
var_dtype_cleaned = var_dtype_raw
|
||||||
base_type_for_init = var_dtype
|
if isinstance(var_dtype_raw, str):
|
||||||
declaration_dtype = var_dtype
|
if var_dtype_raw.startswith('"') and var_dtype_raw.endswith('"'):
|
||||||
if array_match:
|
var_dtype_cleaned = var_dtype_raw[1:-1]
|
||||||
array_prefix = array_match.group(1)
|
# Manejar caso 'Array [...] of "MyUDT"'
|
||||||
base_type_raw = array_match.group(2).strip()
|
array_match = re.match(
|
||||||
# Limpiar comillas del tipo base del array
|
r'(Array\[.*\]\s+of\s+)"(.*)"', var_dtype_raw, re.IGNORECASE
|
||||||
base_type_for_init = (
|
|
||||||
base_type_raw.strip('"')
|
|
||||||
if base_type_raw.startswith('"') and base_type_raw.endswith('"')
|
|
||||||
else base_type_raw
|
|
||||||
)
|
)
|
||||||
declaration_dtype = (
|
if array_match:
|
||||||
f'{array_prefix}"{base_type_for_init}"'
|
var_dtype_cleaned = f"{array_match.group(1)}{array_match.group(2)}" # Quitar comillas del tipo base
|
||||||
if '"' not in base_type_raw
|
|
||||||
else f"{array_prefix}{base_type_raw}"
|
|
||||||
) # Reconstruir con comillas si es UDT
|
|
||||||
|
|
||||||
# Reconstruir declaración con comillas si es UDT y no array
|
# Determinar tipo base para inicialización (importante para arrays)
|
||||||
elif (
|
base_type_for_init = var_dtype_cleaned
|
||||||
not array_match and var_dtype != base_type_for_init
|
array_prefix_for_decl = ""
|
||||||
): # Es un tipo que necesita comillas (UDT)
|
if var_dtype_cleaned.lower().startswith("array["):
|
||||||
declaration_dtype = f'"{var_dtype}"'
|
match = re.match(
|
||||||
|
r"(Array\[.*\]\s+of\s+)(.*)", var_dtype_cleaned, re.IGNORECASE
|
||||||
|
)
|
||||||
|
if match:
|
||||||
|
array_prefix_for_decl = match.group(1)
|
||||||
|
base_type_for_init = match.group(2).strip()
|
||||||
|
|
||||||
|
# Construir tipo de dato para la declaración SCL
|
||||||
|
declaration_dtype = var_dtype_raw # Usar el raw por defecto
|
||||||
|
# Si es UDT o tipo complejo que requiere comillas y no es array simple
|
||||||
|
if base_type_for_init != var_dtype_cleaned and not array_prefix_for_decl:
|
||||||
|
# Poner comillas si no las tiene ya el tipo base
|
||||||
|
if not base_type_for_init.startswith('"'):
|
||||||
|
declaration_dtype = f'"{base_type_for_init}"'
|
||||||
|
else:
|
||||||
|
declaration_dtype = base_type_for_init # Ya tiene comillas
|
||||||
|
# Si es array de UDT/complejo, reconstruir con comillas en el tipo base
|
||||||
|
elif array_prefix_for_decl and base_type_for_init != var_dtype_cleaned:
|
||||||
|
if not base_type_for_init.startswith('"'):
|
||||||
|
declaration_dtype = f'{array_prefix_for_decl}"{base_type_for_init}"'
|
||||||
|
else:
|
||||||
|
declaration_dtype = f"{array_prefix_for_decl}{base_type_for_init}"
|
||||||
|
|
||||||
declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}"
|
declaration_line = f"{indent}{var_name_scl} : {declaration_dtype}"
|
||||||
init_value = None
|
init_value_scl = None
|
||||||
|
|
||||||
# ---- Arrays ----
|
# ---- Arrays ----
|
||||||
if array_elements:
|
if array_elements:
|
||||||
# Ordenar índices (asumiendo que son numéricos)
|
# Ordenar índices (asumiendo que son numéricos '0', '1', ...)
|
||||||
try:
|
try:
|
||||||
sorted_indices = sorted(array_elements.keys(), key=int)
|
# Extraer números de los índices string
|
||||||
|
indices_numeric = {int(k): v for k, v in array_elements.items()}
|
||||||
|
sorted_indices = sorted(indices_numeric.keys())
|
||||||
|
# Mapear de nuevo a string para buscar valor
|
||||||
|
sorted_indices_str = [str(k) for k in sorted_indices]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
sorted_indices = sorted(
|
# Fallback a orden alfabético si los índices no son números
|
||||||
array_elements.keys()
|
print(
|
||||||
) # Fallback a orden alfabético
|
f"Advertencia: Índices de array no numéricos para '{var_name_scl}'. Usando orden alfabético."
|
||||||
|
)
|
||||||
|
sorted_indices_str = sorted(array_elements.keys())
|
||||||
|
|
||||||
init_values = [
|
init_values = []
|
||||||
format_scl_start_value(array_elements[idx], base_type_for_init)
|
for idx_str in sorted_indices_str:
|
||||||
for idx in sorted_indices
|
try:
|
||||||
]
|
formatted_val = format_scl_start_value(
|
||||||
|
array_elements[idx_str], base_type_for_init
|
||||||
|
)
|
||||||
|
init_values.append(formatted_val)
|
||||||
|
except Exception as e_fmt:
|
||||||
|
print(
|
||||||
|
f"ERROR: Falló formateo para índice {idx_str} de array '{var_name_scl}'. Valor: {array_elements[idx_str]}. Error: {e_fmt}"
|
||||||
|
)
|
||||||
|
init_values.append(f"/*ERR_FMT_{idx_str}*/") # Placeholder de error
|
||||||
|
|
||||||
|
# Filtrar Nones que pueden venir de format_scl_start_value si el valor era None
|
||||||
valid_inits = [v for v in init_values if v is not None]
|
valid_inits = [v for v in init_values if v is not None]
|
||||||
if valid_inits:
|
if valid_inits:
|
||||||
init_value = f"[{', '.join(valid_inits)}]"
|
# Si todos los valores son iguales y es un array grande, podríamos usar notación x(value)
|
||||||
|
# Simplificación: por ahora, listar todos
|
||||||
|
init_value_scl = f"[{', '.join(valid_inits)}]"
|
||||||
|
elif array_elements: # Si había elementos pero todos formatearon a None
|
||||||
|
print(
|
||||||
|
f"Advertencia: Todos los valores iniciales para array '{var_name_scl}' son None o inválidos."
|
||||||
|
)
|
||||||
|
|
||||||
# ---- Structs ----
|
# ---- Structs ----
|
||||||
elif children:
|
elif children:
|
||||||
# No añadir comentario // Struct aquí, es redundante
|
# El valor inicial de un struct se maneja recursivamente dentro
|
||||||
scl_lines.append(declaration_line) # Añadir línea de declaración base
|
# Añadir comentario? Puede ser redundante.
|
||||||
|
scl_lines.append(
|
||||||
|
declaration_line
|
||||||
|
) # Añadir línea de declaración base STRUCT
|
||||||
scl_lines.append(f"{indent}STRUCT")
|
scl_lines.append(f"{indent}STRUCT")
|
||||||
|
# Llamada recursiva para los miembros internos
|
||||||
scl_lines.extend(generate_scl_declarations(children, indent_level + 1))
|
scl_lines.extend(generate_scl_declarations(children, indent_level + 1))
|
||||||
scl_lines.append(f"{indent}END_STRUCT;")
|
scl_lines.append(f"{indent}END_STRUCT;")
|
||||||
if var_comment:
|
if var_comment: # Comentario después de END_STRUCT
|
||||||
scl_lines.append(f"{indent}// {var_comment}")
|
scl_lines.append(f"{indent}// {var_comment}")
|
||||||
scl_lines.append("") # Línea extra
|
scl_lines.append("") # Línea extra para legibilidad
|
||||||
continue # Saltar resto para Struct
|
continue # Saltar el resto de la lógica para este struct
|
||||||
|
|
||||||
# ---- Tipos Simples ----
|
# ---- Tipos Simples ----
|
||||||
else:
|
else:
|
||||||
if start_value is not None:
|
if start_value is not None:
|
||||||
init_value = format_scl_start_value(start_value, var_dtype)
|
try:
|
||||||
|
init_value_scl = format_scl_start_value(
|
||||||
|
start_value, base_type_for_init
|
||||||
|
) # Usar tipo base
|
||||||
|
except Exception as e_fmt_simple:
|
||||||
|
print(
|
||||||
|
f"ERROR: Falló formateo para valor simple de '{var_name_scl}'. Valor: {start_value}. Error: {e_fmt_simple}"
|
||||||
|
)
|
||||||
|
init_value_scl = f"/*ERR_FMT_SIMPLE*/" # Placeholder
|
||||||
|
|
||||||
|
# Añadir inicialización si existe y no es None
|
||||||
|
if init_value_scl is not None:
|
||||||
|
declaration_line += f" := {init_value_scl}"
|
||||||
|
|
||||||
# Añadir inicialización si existe
|
|
||||||
if init_value:
|
|
||||||
declaration_line += f" := {init_value}"
|
|
||||||
declaration_line += ";"
|
declaration_line += ";"
|
||||||
|
|
||||||
|
# Añadir comentario si existe
|
||||||
if var_comment:
|
if var_comment:
|
||||||
declaration_line += f" // {var_comment}"
|
declaration_line += f" // {var_comment}"
|
||||||
|
|
||||||
scl_lines.append(declaration_line)
|
scl_lines.append(declaration_line)
|
||||||
|
|
||||||
return scl_lines
|
return scl_lines
|
||||||
|
@ -243,7 +384,7 @@ def generate_scl_declarations(variables, indent_level=1):
|
||||||
|
|
||||||
# --- Función Principal de Generación SCL ---
|
# --- Función Principal de Generación SCL ---
|
||||||
def generate_scl(processed_json_filepath, output_scl_filepath):
|
def generate_scl(processed_json_filepath, output_scl_filepath):
|
||||||
"""Genera un archivo SCL a partir del JSON procesado (FC/FB o DB)."""
|
"""Genera un archivo SCL a partir del JSON procesado (FC/FB/OB o DB).""" # Actualizado
|
||||||
|
|
||||||
if not os.path.exists(processed_json_filepath):
|
if not os.path.exists(processed_json_filepath):
|
||||||
print(
|
print(
|
||||||
|
@ -263,33 +404,41 @@ def generate_scl(processed_json_filepath, output_scl_filepath):
|
||||||
# --- Extracción de Información del Bloque (Común) ---
|
# --- Extracción de Información del Bloque (Común) ---
|
||||||
block_name = data.get("block_name", "UnknownBlock")
|
block_name = data.get("block_name", "UnknownBlock")
|
||||||
block_number = data.get("block_number")
|
block_number = data.get("block_number")
|
||||||
block_lang_original = data.get("language", "Unknown") # Será "DB" para Data Blocks
|
# block_lang_original = data.get("language", "Unknown") # Lenguaje original (SCL, LAD, DB...)
|
||||||
block_type = data.get("block_type", "Unknown") # FC, FB, GlobalDB
|
block_type = data.get(
|
||||||
|
"block_type", "Unknown"
|
||||||
|
) # Tipo de bloque (FC, FB, GlobalDB, OB) <-- Usar este
|
||||||
block_comment = data.get("block_comment", "")
|
block_comment = data.get("block_comment", "")
|
||||||
scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
|
scl_block_name = format_variable_name(block_name) # Nombre SCL seguro
|
||||||
print(
|
print(
|
||||||
f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name}, Lang: {block_lang_original})"
|
f"Generando SCL para: {block_type} '{scl_block_name}' (Original: {block_name})" # Quitado lenguaje original del log
|
||||||
)
|
)
|
||||||
scl_output = []
|
scl_output = []
|
||||||
|
|
||||||
# --- GENERACIÓN PARA DATA BLOCK (DB) ---
|
# --- MODIFICADO: GENERACIÓN PARA DATA BLOCK (GlobalDB) ---
|
||||||
if block_lang_original == "DB":
|
if block_type == "GlobalDB": # <-- Comprobar tipo de bloque
|
||||||
print("Modo de generación: DATA_BLOCK")
|
print("Modo de generación: DATA_BLOCK")
|
||||||
scl_output.append(f"// Block Type: {block_type}")
|
scl_output.append(f"// Block Type: {block_type}")
|
||||||
scl_output.append(f"// Block Name (Original): {block_name}")
|
scl_output.append(f"// Block Name (Original): {block_name}")
|
||||||
if block_number:
|
if block_number:
|
||||||
scl_output.append(f"// Block Number: {block_number}")
|
scl_output.append(f"// Block Number: {block_number}")
|
||||||
if block_comment:
|
if block_comment:
|
||||||
scl_output.append(f"// Block Comment: {block_comment}")
|
# Dividir comentarios largos en múltiples líneas
|
||||||
|
comment_lines = block_comment.splitlines()
|
||||||
|
scl_output.append(f"// Block Comment:")
|
||||||
|
for line in comment_lines:
|
||||||
|
scl_output.append(f"// {line}")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
scl_output.append(f'DATA_BLOCK "{scl_block_name}"')
|
scl_output.append(f'DATA_BLOCK "{scl_block_name}"')
|
||||||
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
|
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
|
||||||
scl_output.append("VERSION : 0.1")
|
scl_output.append("VERSION : 0.1")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
interface_data = data.get("interface", {})
|
interface_data = data.get("interface", {})
|
||||||
|
# En DBs, la sección relevante suele ser 'Static'
|
||||||
static_vars = interface_data.get("Static", [])
|
static_vars = interface_data.get("Static", [])
|
||||||
if static_vars:
|
if static_vars:
|
||||||
scl_output.append("VAR")
|
scl_output.append("VAR")
|
||||||
|
# Usar la función recursiva para generar declaraciones
|
||||||
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
|
scl_output.extend(generate_scl_declarations(static_vars, indent_level=1))
|
||||||
scl_output.append("END_VAR")
|
scl_output.append("END_VAR")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
@ -297,182 +446,288 @@ def generate_scl(processed_json_filepath, output_scl_filepath):
|
||||||
print(
|
print(
|
||||||
"Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB."
|
"Advertencia: No se encontró sección 'Static' o está vacía en la interfaz del DB."
|
||||||
)
|
)
|
||||||
|
# Añadir bloque VAR vacío si no hay variables
|
||||||
scl_output.append("VAR")
|
scl_output.append("VAR")
|
||||||
scl_output.append("END_VAR")
|
scl_output.append("END_VAR")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
scl_output.append("BEGIN")
|
scl_output.append("BEGIN")
|
||||||
scl_output.append("")
|
scl_output.append(
|
||||||
|
" // Los Data Blocks no tienen código ejecutable en BEGIN/END"
|
||||||
|
)
|
||||||
scl_output.append("END_DATA_BLOCK")
|
scl_output.append("END_DATA_BLOCK")
|
||||||
|
|
||||||
# --- GENERACIÓN PARA FUNCTION BLOCK / FUNCTION (FC/FB) ---
|
# --- MODIFICADO: GENERACIÓN PARA FC/FB/OB ---
|
||||||
else:
|
else:
|
||||||
print("Modo de generación: FUNCTION_BLOCK / FUNCTION")
|
# Determinar palabra clave SCL
|
||||||
scl_block_keyword = "FUNCTION_BLOCK" if block_type == "FB" else "FUNCTION"
|
scl_block_keyword = "FUNCTION_BLOCK" # Default
|
||||||
|
if block_type == "FC":
|
||||||
|
scl_block_keyword = "FUNCTION"
|
||||||
|
elif block_type == "OB":
|
||||||
|
scl_block_keyword = "ORGANIZATION_BLOCK"
|
||||||
|
elif block_type == "FB":
|
||||||
|
scl_block_keyword = "FUNCTION_BLOCK"
|
||||||
|
else: # Fallback
|
||||||
|
print(
|
||||||
|
f"Advertencia: Tipo de bloque desconocido '{block_type}', usando FUNCTION_BLOCK."
|
||||||
|
)
|
||||||
|
scl_block_keyword = "FUNCTION_BLOCK" # O quizás lanzar error?
|
||||||
|
|
||||||
|
print(f"Modo de generación: {scl_block_keyword}")
|
||||||
|
|
||||||
# Cabecera del Bloque
|
# Cabecera del Bloque
|
||||||
scl_output.append(f"// Block Type: {block_type}")
|
scl_output.append(f"// Block Type: {block_type}")
|
||||||
scl_output.append(f"// Block Name (Original): {block_name}")
|
scl_output.append(f"// Block Name (Original): {block_name}")
|
||||||
if block_number:
|
if block_number:
|
||||||
scl_output.append(f"// Block Number: {block_number}")
|
scl_output.append(f"// Block Number: {block_number}")
|
||||||
scl_output.append(f"// Original Language: {block_lang_original}")
|
# Indicar lenguaje original de las redes si es relevante
|
||||||
|
original_net_langs = set(
|
||||||
|
n.get("language", "Unknown") for n in data.get("networks", [])
|
||||||
|
)
|
||||||
|
scl_output.append(
|
||||||
|
f"// Original Network Languages: {', '.join(l for l in original_net_langs if l != 'Unknown')}"
|
||||||
|
)
|
||||||
if block_comment:
|
if block_comment:
|
||||||
scl_output.append(f"// Block Comment: {block_comment}")
|
comment_lines = block_comment.splitlines()
|
||||||
|
scl_output.append(f"// Block Comment:")
|
||||||
|
for line in comment_lines:
|
||||||
|
scl_output.append(f"// {line}")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
# Manejar tipo de retorno para FUNCTION
|
|
||||||
|
# Manejar tipo de retorno para FUNCTION (FC)
|
||||||
return_type = "Void" # Default
|
return_type = "Void" # Default
|
||||||
interface_data = data.get("interface", {})
|
interface_data = data.get("interface", {})
|
||||||
if scl_block_keyword == "FUNCTION" and interface_data.get("Return"):
|
if scl_block_keyword == "FUNCTION" and interface_data.get("Return"):
|
||||||
return_member = interface_data["Return"][
|
# Asumir un solo valor de retorno
|
||||||
0
|
return_member = interface_data["Return"][0]
|
||||||
] # Asumir un solo valor de retorno
|
|
||||||
return_type_raw = return_member.get("datatype", "Void")
|
return_type_raw = return_member.get("datatype", "Void")
|
||||||
|
# Limpiar comillas si es UDT/String
|
||||||
return_type = (
|
return_type = (
|
||||||
return_type_raw.strip('"')
|
return_type_raw[1:-1]
|
||||||
if return_type_raw.startswith('"') and return_type_raw.endswith('"')
|
if isinstance(return_type_raw, str)
|
||||||
|
and return_type_raw.startswith('"')
|
||||||
|
and return_type_raw.endswith('"')
|
||||||
else return_type_raw
|
else return_type_raw
|
||||||
)
|
)
|
||||||
# Añadir comillas si es UDT
|
# Añadir comillas si es UDT y no las tenía
|
||||||
if return_type != return_type_raw:
|
if (
|
||||||
|
return_type != return_type_raw
|
||||||
|
and not return_type_raw.lower().startswith("array")
|
||||||
|
):
|
||||||
return_type = f'"{return_type}"'
|
return_type = f'"{return_type}"'
|
||||||
|
else: # Mantener raw si es tipo básico o ya tenía comillas
|
||||||
|
return_type = return_type_raw
|
||||||
|
|
||||||
scl_output.append(
|
# Línea de declaración del bloque
|
||||||
f'{scl_block_keyword} "{scl_block_name}" : {return_type}'
|
if scl_block_keyword == "FUNCTION":
|
||||||
if scl_block_keyword == "FUNCTION"
|
scl_output.append(f'{scl_block_keyword} "{scl_block_name}" : {return_type}')
|
||||||
else f'{scl_block_keyword} "{scl_block_name}"'
|
else: # FB y OB
|
||||||
)
|
scl_output.append(f'{scl_block_keyword} "{scl_block_name}"')
|
||||||
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
|
|
||||||
|
# Atributos y versión
|
||||||
|
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Asumir optimizado
|
||||||
scl_output.append("VERSION : 0.1")
|
scl_output.append("VERSION : 0.1")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
|
||||||
# Declaraciones de Interfaz FC/FB
|
# Declaraciones de Interfaz (Input, Output, InOut, Static, Temp, Constant)
|
||||||
section_order = [
|
# Orden estándar SCL
|
||||||
"Input",
|
section_order = ["Input", "Output", "InOut", "Static", "Temp", "Constant"]
|
||||||
"Output",
|
declared_temps = set() # Para rastrear temps ya declaradas
|
||||||
"InOut",
|
has_declarations = False
|
||||||
"Static",
|
|
||||||
"Temp",
|
|
||||||
"Constant",
|
|
||||||
] # Return ya está en cabecera
|
|
||||||
declared_temps = set()
|
|
||||||
for section_name in section_order:
|
for section_name in section_order:
|
||||||
vars_in_section = interface_data.get(section_name, [])
|
vars_in_section = interface_data.get(section_name, [])
|
||||||
if vars_in_section:
|
if vars_in_section:
|
||||||
|
has_declarations = True
|
||||||
|
# Mapeo de nombres de sección JSON a palabras clave SCL VAR_
|
||||||
scl_section_keyword = f"VAR_{section_name.upper()}"
|
scl_section_keyword = f"VAR_{section_name.upper()}"
|
||||||
if section_name == "Static":
|
if section_name == "Static":
|
||||||
scl_section_keyword = "VAR_STAT"
|
scl_section_keyword = "VAR_STAT" # Para FBs
|
||||||
if section_name == "Temp":
|
if section_name == "Temp":
|
||||||
scl_section_keyword = "VAR_TEMP"
|
scl_section_keyword = "VAR_TEMP"
|
||||||
if section_name == "Constant":
|
if section_name == "Constant":
|
||||||
scl_section_keyword = "CONSTANT"
|
scl_section_keyword = "CONSTANT" # CONSTANT no usa VAR_
|
||||||
|
|
||||||
scl_output.append(scl_section_keyword)
|
scl_output.append(scl_section_keyword)
|
||||||
|
# Usar la función recursiva para generar declaraciones
|
||||||
scl_output.extend(
|
scl_output.extend(
|
||||||
generate_scl_declarations(vars_in_section, indent_level=1)
|
generate_scl_declarations(vars_in_section, indent_level=1)
|
||||||
)
|
)
|
||||||
|
# Añadir END_VAR (o END_CONSTANT)
|
||||||
|
scl_output.append(
|
||||||
|
"END_VAR" if section_name != "Constant" else "END_CONSTANT"
|
||||||
|
)
|
||||||
|
scl_output.append("") # Línea en blanco
|
||||||
|
|
||||||
|
# Guardar nombres de Temp declarados explícitamente
|
||||||
if section_name == "Temp":
|
if section_name == "Temp":
|
||||||
declared_temps.update(
|
declared_temps.update(
|
||||||
format_variable_name(v.get("name"))
|
format_variable_name(v.get("name"))
|
||||||
for v in vars_in_section
|
for v in vars_in_section
|
||||||
if v.get("name")
|
if v.get("name")
|
||||||
)
|
)
|
||||||
scl_output.append("END_VAR")
|
# Declaraciones VAR_TEMP adicionales (auto-detectadas)
|
||||||
scl_output.append("")
|
# Buscar variables que empiecen con #_temp_ en el SCL generado
|
||||||
|
temp_vars_detected = set()
|
||||||
# Declaraciones VAR_TEMP adicionales detectadas
|
# Patrón para encontrar #variable o "#variable"
|
||||||
temp_vars = set()
|
|
||||||
temp_pattern = re.compile(
|
temp_pattern = re.compile(
|
||||||
r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?'
|
r'"?(#\w+)"?'
|
||||||
)
|
) # Busca # seguido de caracteres alfanuméricos
|
||||||
|
|
||||||
for network in data.get("networks", []):
|
for network in data.get("networks", []):
|
||||||
for instruction in network.get("logic", []):
|
for instruction in network.get("logic", []):
|
||||||
|
# Revisar el SCL final y el SCL de actualización de memoria si existe
|
||||||
scl_code = instruction.get("scl", "")
|
scl_code = instruction.get("scl", "")
|
||||||
edge_update_code = instruction.get("_edge_mem_update_scl", "")
|
edge_update_code = instruction.get(
|
||||||
|
"_edge_mem_update_scl", ""
|
||||||
|
) # Para flancos
|
||||||
code_to_scan = (
|
code_to_scan = (
|
||||||
(scl_code if scl_code else "")
|
(scl_code if scl_code else "")
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ (edge_update_code if edge_update_code else "")
|
+ (edge_update_code if edge_update_code else "")
|
||||||
)
|
)
|
||||||
|
|
||||||
if code_to_scan:
|
if code_to_scan:
|
||||||
|
# Usar findall para encontrar todas las ocurrencias
|
||||||
found_temps = temp_pattern.findall(code_to_scan)
|
found_temps = temp_pattern.findall(code_to_scan)
|
||||||
for temp_tuple in found_temps:
|
for temp_name in found_temps:
|
||||||
temp_name = next((t for t in temp_tuple if t), None)
|
# findall devuelve el grupo capturado (#...)
|
||||||
if temp_name:
|
if temp_name:
|
||||||
temp_vars.add(
|
temp_vars_detected.add(temp_name)
|
||||||
"#" + temp_name
|
|
||||||
if not temp_name.startswith("#")
|
# Filtrar las que ya estaban declaradas
|
||||||
else temp_name
|
additional_temps = sorted(list(temp_vars_detected - declared_temps))
|
||||||
)
|
|
||||||
additional_temps = sorted(list(temp_vars - declared_temps))
|
|
||||||
if additional_temps:
|
if additional_temps:
|
||||||
if not interface_data.get("Temp"):
|
print(f"INFO: Detectadas {len(additional_temps)} VAR_TEMP adicionales.")
|
||||||
|
# Si no se declaró la sección Temp antes, añadirla ahora
|
||||||
|
if "Temp" not in interface_data or not interface_data["Temp"]:
|
||||||
scl_output.append("VAR_TEMP")
|
scl_output.append("VAR_TEMP")
|
||||||
for var_name in additional_temps:
|
|
||||||
scl_name = format_variable_name(var_name)
|
for temp_name in additional_temps:
|
||||||
inferred_type = "Bool" # Asumir Bool
|
# Formatear por si acaso, aunque el patrón ya debería dar #nombre
|
||||||
|
scl_name = format_variable_name(temp_name)
|
||||||
|
# Inferir tipo (Bool es lo más común para temporales internos)
|
||||||
|
# Se podría mejorar si el nombre da pistas (ej. _temp_r para Real)
|
||||||
|
inferred_type = "Bool" # Asumir Bool por defecto
|
||||||
scl_output.append(
|
scl_output.append(
|
||||||
f" {scl_name} : {inferred_type}; // Auto-generated temporary"
|
f" {scl_name} : {inferred_type}; // Auto-generated temporary"
|
||||||
)
|
)
|
||||||
if not interface_data.get("Temp"):
|
|
||||||
|
# Si abrimos la sección aquí, cerrarla
|
||||||
|
if "Temp" not in interface_data or not interface_data["Temp"]:
|
||||||
scl_output.append("END_VAR")
|
scl_output.append("END_VAR")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
|
|
||||||
# Cuerpo del Bloque FC/FB
|
# --- Cuerpo del Bloque (BEGIN...END) ---
|
||||||
scl_output.append("BEGIN")
|
scl_output.append("BEGIN")
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
# Iterar por redes y lógica (como antes, incluyendo manejo STL Markdown)
|
# Iterar por redes y lógica (incluyendo manejo STL/SCL crudo)
|
||||||
for i, network in enumerate(data.get("networks", [])):
|
for i, network in enumerate(data.get("networks", [])):
|
||||||
network_title = network.get("title", f'Network {network.get("id")}')
|
network_title = network.get(
|
||||||
|
"title", f'Network {network.get("id", i+1)}'
|
||||||
|
) # Usar i+1 si falta ID
|
||||||
network_comment = network.get("comment", "")
|
network_comment = network.get("comment", "")
|
||||||
network_lang = network.get("language", "LAD")
|
network_lang = network.get("language", "LAD") # Lenguaje original de la red
|
||||||
scl_output.append(
|
scl_output.append(
|
||||||
f" // Network {i+1}: {network_title} (Original Language: {network_lang})"
|
f" // Network {i+1}: {network_title} (Original Language: {network_lang})"
|
||||||
)
|
)
|
||||||
if network_comment:
|
if network_comment:
|
||||||
|
# Indentar comentarios de red
|
||||||
for line in network_comment.splitlines():
|
for line in network_comment.splitlines():
|
||||||
scl_output.append(f" // {line}")
|
scl_output.append(f" // {line}")
|
||||||
scl_output.append("")
|
scl_output.append("") # Línea en blanco antes del código de red
|
||||||
|
|
||||||
network_has_code = False
|
network_has_code = False
|
||||||
|
logic_in_network = network.get("logic", [])
|
||||||
|
|
||||||
|
if not logic_in_network:
|
||||||
|
scl_output.append(f" // Network {i+1} has no logic elements.")
|
||||||
|
scl_output.append("")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# --- Manejo Especial Redes STL ---
|
||||||
if network_lang == "STL":
|
if network_lang == "STL":
|
||||||
|
# Asumir que la lógica STL está en el primer elemento como RAW_STL_CHUNK
|
||||||
|
if logic_in_network[0].get("type") == "RAW_STL_CHUNK":
|
||||||
network_has_code = True
|
network_has_code = True
|
||||||
if (
|
raw_stl_code = logic_in_network[0].get(
|
||||||
network.get("logic")
|
|
||||||
and network["logic"][0].get("type") == "RAW_STL_CHUNK"
|
|
||||||
):
|
|
||||||
raw_stl_code = network["logic"][0].get(
|
|
||||||
"stl", "// ERROR: STL code missing"
|
"stl", "// ERROR: STL code missing"
|
||||||
)
|
)
|
||||||
scl_output.append(f" {'//'} ```STL")
|
# Incrustar STL como comentario multi-línea o delimitado
|
||||||
|
scl_output.append(f" // --- BEGIN STL Network {i+1} ---")
|
||||||
|
# Comentar cada línea STL
|
||||||
for stl_line in raw_stl_code.splitlines():
|
for stl_line in raw_stl_code.splitlines():
|
||||||
scl_output.append(f" {stl_line}")
|
scl_output.append(f" // {stl_line}")
|
||||||
scl_output.append(f" {'//'} ```")
|
scl_output.append(f" // --- END STL Network {i+1} ---")
|
||||||
|
scl_output.append("") # Línea en blanco después
|
||||||
else:
|
else:
|
||||||
scl_output.append(" // ERROR: Contenido STL inesperado.")
|
scl_output.append(
|
||||||
else: # LAD, FBD, SCL, etc.
|
f" // ERROR: Contenido STL inesperado en Network {i+1}."
|
||||||
for instruction in network.get("logic", []):
|
)
|
||||||
|
scl_output.append("")
|
||||||
|
|
||||||
|
# --- Manejo Redes SCL/LAD/FBD procesadas ---
|
||||||
|
else:
|
||||||
|
# Iterar por las instrucciones procesadas
|
||||||
|
for instruction in logic_in_network:
|
||||||
instruction_type = instruction.get("type", "")
|
instruction_type = instruction.get("type", "")
|
||||||
scl_code = instruction.get("scl", "")
|
scl_code = instruction.get("scl", "")
|
||||||
is_grouped = instruction.get("grouped", False)
|
is_grouped = instruction.get("grouped", False)
|
||||||
|
|
||||||
|
# Saltar instrucciones agrupadas (su lógica está en el IF)
|
||||||
if is_grouped:
|
if is_grouped:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Incluir SCL si la instrucción fue procesada o es un chunk crudo/error/placeholder
|
||||||
if (
|
if (
|
||||||
instruction_type.endswith(SCL_SUFFIX)
|
instruction_type.endswith(SCL_SUFFIX)
|
||||||
or instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]
|
or instruction_type
|
||||||
|
in [
|
||||||
|
"RAW_SCL_CHUNK",
|
||||||
|
"UNSUPPORTED_LANG",
|
||||||
|
"UNSUPPORTED_CONTENT",
|
||||||
|
"PARSING_ERROR",
|
||||||
|
]
|
||||||
|
or "_error" in instruction_type # Incluir errores comentados
|
||||||
) and scl_code:
|
) and scl_code:
|
||||||
|
|
||||||
|
# Comprobar si el SCL es solo un comentario (a menos que sea un bloque IF)
|
||||||
is_only_comment = all(
|
is_only_comment = all(
|
||||||
line.strip().startswith("//")
|
line.strip().startswith("//")
|
||||||
for line in scl_code.splitlines()
|
for line in scl_code.splitlines()
|
||||||
if line.strip()
|
if line.strip()
|
||||||
)
|
)
|
||||||
is_if_block = scl_code.strip().startswith("IF")
|
is_if_block = scl_code.strip().startswith("IF")
|
||||||
if not is_only_comment or is_if_block:
|
|
||||||
|
# Añadir el SCL indentado si no es solo un comentario (o si es un IF/Error)
|
||||||
|
if (
|
||||||
|
not is_only_comment
|
||||||
|
or is_if_block
|
||||||
|
or "_error" in instruction_type
|
||||||
|
or instruction_type
|
||||||
|
in [
|
||||||
|
"UNSUPPORTED_LANG",
|
||||||
|
"UNSUPPORTED_CONTENT",
|
||||||
|
"PARSING_ERROR",
|
||||||
|
]
|
||||||
|
):
|
||||||
network_has_code = True
|
network_has_code = True
|
||||||
for line in scl_code.splitlines():
|
for line in scl_code.splitlines():
|
||||||
scl_output.append(f" {line}")
|
scl_output.append(f" {line}") # Indentar código
|
||||||
if network_has_code:
|
# Añadir línea en blanco después de cada bloque SCL para legibilidad
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
else:
|
|
||||||
scl_output.append(f" // Network did not produce printable SCL code.")
|
# Si la red no produjo código SCL imprimible (ej. solo lógica interna)
|
||||||
|
if (
|
||||||
|
not network_has_code and network_lang != "STL"
|
||||||
|
): # No añadir para STL ya comentado
|
||||||
|
scl_output.append(
|
||||||
|
f" // Network {i+1} did not produce printable SCL code."
|
||||||
|
)
|
||||||
scl_output.append("")
|
scl_output.append("")
|
||||||
# Fin del bloque FC/FB
|
|
||||||
scl_output.append(f"END_{scl_block_keyword}")
|
# Fin del bloque FC/FB/OB
|
||||||
|
scl_output.append(f"END_{scl_block_keyword}") # <-- Usar keyword determinada
|
||||||
|
|
||||||
# --- Escritura del Archivo SCL (Común) ---
|
# --- Escritura del Archivo SCL (Común) ---
|
||||||
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
|
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
|
||||||
|
@ -492,7 +747,7 @@ if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback # Asegurarse que traceback está importado si se usa en generate_scl
|
import traceback # Asegurarse que traceback está importado
|
||||||
|
|
||||||
# Configurar ArgumentParser para recibir la ruta del XML original obligatoria
|
# Configurar ArgumentParser para recibir la ruta del XML original obligatoria
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
@ -511,7 +766,6 @@ if __name__ == "__main__":
|
||||||
print(
|
print(
|
||||||
f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado."
|
f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON procesado."
|
||||||
)
|
)
|
||||||
# No salir necesariamente.
|
|
||||||
|
|
||||||
# Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL)
|
# Derivar nombres de archivos de entrada (JSON procesado) y salida (SCL)
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
||||||
|
@ -521,8 +775,9 @@ if __name__ == "__main__":
|
||||||
input_json_file = os.path.join(
|
input_json_file = os.path.join(
|
||||||
base_dir, f"{xml_filename_base}_simplified_processed.json"
|
base_dir, f"{xml_filename_base}_simplified_processed.json"
|
||||||
)
|
)
|
||||||
|
# Cambiar extensión de salida a .scl
|
||||||
output_scl_file = os.path.join(
|
output_scl_file = os.path.join(
|
||||||
base_dir, f"{xml_filename_base}_simplified_processed.scl"
|
base_dir, f"{xml_filename_base}_generated.scl" # Cambiado nombre de salida
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
|
@ -540,13 +795,13 @@ if __name__ == "__main__":
|
||||||
sys.exit(1) # Salir si el archivo necesario no está
|
sys.exit(1) # Salir si el archivo necesario no está
|
||||||
else:
|
else:
|
||||||
# Llamar a la función principal de generación SCL del script
|
# Llamar a la función principal de generación SCL del script
|
||||||
# Asumiendo que tu función principal se llama generate_scl(input_json_path, output_scl_path)
|
|
||||||
try:
|
try:
|
||||||
generate_scl(input_json_file, output_scl_file)
|
generate_scl(input_json_file, output_scl_file)
|
||||||
|
sys.exit(0) # Salir con éxito explícitamente
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}"
|
f"Error Crítico (x3) durante la generación de SCL desde '{input_json_file}': {e}"
|
||||||
)
|
)
|
||||||
# traceback ya debería estar importado si generate_scl lo necesita
|
# traceback ya debería estar importado
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1) # Salir con error si la función principal falla
|
sys.exit(1) # Salir con error si la función principal falla
|
||||||
|
|
Loading…
Reference in New Issue