This commit is contained in:
parent
60fea74ebf
commit
6a06f32176
97
x0_main.py
97
x0_main.py
|
@ -5,6 +5,7 @@ import sys
|
|||
import locale
|
||||
import glob
|
||||
|
||||
|
||||
# (Función get_console_encoding y variable CONSOLE_ENCODING como antes)
|
||||
def get_console_encoding():
|
||||
"""Obtiene la codificación preferida de la consola, con fallback."""
|
||||
|
@ -12,11 +13,13 @@ def get_console_encoding():
|
|||
return locale.getpreferredencoding(False)
|
||||
except Exception:
|
||||
# Fallback común en Windows si falla getpreferredencoding
|
||||
return "cp1252" # O prueba con 'utf-8' si cp1252 da problemas
|
||||
return "cp1252" # O prueba con 'utf-8' si cp1252 da problemas
|
||||
|
||||
|
||||
CONSOLE_ENCODING = get_console_encoding()
|
||||
# print(f"Detected console encoding: {CONSOLE_ENCODING}")
|
||||
|
||||
|
||||
# (Función run_script como antes, usando CONSOLE_ENCODING)
|
||||
def run_script(script_name, xml_arg):
|
||||
"""Runs a given script with the specified XML file argument."""
|
||||
|
@ -24,17 +27,21 @@ def run_script(script_name, 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
|
||||
command = [
|
||||
python_executable,
|
||||
script_path,
|
||||
xml_arg,
|
||||
] # Usar la ruta absoluta de python
|
||||
print(f"\n--- Running {script_name} with argument: {xml_arg} ---")
|
||||
try:
|
||||
# Ejecutar el proceso hijo
|
||||
result = subprocess.run(
|
||||
command,
|
||||
check=True, # Lanza excepción si el script falla (return code != 0)
|
||||
capture_output=True,# Captura stdout y stderr
|
||||
text=True, # Decodifica stdout/stderr como texto
|
||||
encoding=CONSOLE_ENCODING, # Usa la codificación detectada
|
||||
errors='replace' # Reemplaza caracteres no decodificables
|
||||
check=True, # Lanza excepción si el script falla (return code != 0)
|
||||
capture_output=True, # Captura stdout y stderr
|
||||
text=True, # Decodifica stdout/stderr como texto
|
||||
encoding=CONSOLE_ENCODING, # Usa la codificación detectada
|
||||
errors="replace", # Reemplaza caracteres no decodificables
|
||||
)
|
||||
|
||||
# Imprimir stdout y stderr si no están vacíos
|
||||
|
@ -45,20 +52,28 @@ def run_script(script_name, xml_arg):
|
|||
print(stdout_clean)
|
||||
if stderr_clean:
|
||||
# Imprimir stderr claramente para errores del script hijo
|
||||
print(f"--- Stderr ({script_name}) ---", file=sys.stderr) # Imprimir en stderr
|
||||
print(
|
||||
f"--- Stderr ({script_name}) ---", file=sys.stderr
|
||||
) # Imprimir en stderr
|
||||
print(stderr_clean, file=sys.stderr)
|
||||
print("--------------------------", file=sys.stderr)
|
||||
|
||||
print(f"--- {script_name} finished successfully ---")
|
||||
return True # Indicar éxito
|
||||
return True # Indicar éxito
|
||||
|
||||
except FileNotFoundError:
|
||||
# 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)
|
||||
print(
|
||||
f"Error: Script '{script_path}' or Python executable '{python_executable}' not found.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Error si el script hijo devuelve un código de error (ej., sys.exit(1))
|
||||
print(f"Error running {script_name}: Script returned non-zero exit code {e.returncode}.", file=sys.stderr)
|
||||
print(
|
||||
f"Error running {script_name}: Script returned non-zero exit code {e.returncode}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# Decodificar e imprimir stdout/stderr del proceso fallido
|
||||
stdout_decoded = e.stdout.strip() if e.stdout else ""
|
||||
|
@ -71,14 +86,18 @@ def run_script(script_name, xml_arg):
|
|||
print(f"--- Stderr ({script_name}) ---", file=sys.stderr)
|
||||
print(stderr_decoded, file=sys.stderr)
|
||||
print("--------------------------", file=sys.stderr)
|
||||
return False # Indicar fallo
|
||||
return False # Indicar fallo
|
||||
except Exception as e:
|
||||
# Otros errores inesperados
|
||||
print(f"An unexpected error occurred while running {script_name}: {e}", file=sys.stderr)
|
||||
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
|
||||
return False # Indicar fallo
|
||||
|
||||
|
||||
# --- NO SE NECESITA select_xml_file() si procesamos todos ---
|
||||
|
@ -95,20 +114,27 @@ if __name__ == "__main__":
|
|||
|
||||
# Verificar si el directorio 'XML Project' existe
|
||||
if not os.path.isdir(xml_project_dir):
|
||||
print(f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio.", file=sys.stderr)
|
||||
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(
|
||||
f"Error: El directorio '{xml_project_dir}' no existe o no es un directorio.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
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
|
||||
|
||||
# Buscar todos los archivos .xml recursivamente
|
||||
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
||||
xml_files_found = glob.glob(search_pattern, recursive=True)
|
||||
|
||||
if not xml_files_found:
|
||||
print(f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios.")
|
||||
sys.exit(0) # Salir limpiamente si no hay archivos
|
||||
print(
|
||||
f"No se encontraron archivos XML en '{xml_project_dir}' o sus subdirectorios."
|
||||
)
|
||||
sys.exit(0) # Salir limpiamente si no hay archivos
|
||||
|
||||
print(f"Se encontraron {len(xml_files_found)} archivos XML para procesar:")
|
||||
xml_files_found.sort() # Ordenar para consistencia
|
||||
xml_files_found.sort() # Ordenar para consistencia
|
||||
for xml_file in xml_files_found:
|
||||
print(f" - {os.path.relpath(xml_file, script_dir)}")
|
||||
|
||||
|
@ -128,24 +154,35 @@ if __name__ == "__main__":
|
|||
|
||||
# Usar la ruta absoluta para los scripts hijos
|
||||
absolute_xml_filepath = os.path.abspath(xml_filepath)
|
||||
|
||||
|
||||
# Derivar nombres esperados para archivos intermedios (para depuración)
|
||||
xml_base_name = os.path.splitext(os.path.basename(absolute_xml_filepath))[0]
|
||||
xml_dir = os.path.dirname(absolute_xml_filepath)
|
||||
parsing_dir = os.path.join(xml_dir, "parsing")
|
||||
expected_json_file = os.path.join(parsing_dir, f"{xml_base_name}.json")
|
||||
expected_processed_json = os.path.join(parsing_dir, f"{xml_base_name}_processed.json")
|
||||
expected_processed_json = os.path.join(
|
||||
parsing_dir, f"{xml_base_name}_processed.json"
|
||||
)
|
||||
|
||||
# Ejecutar los scripts en secuencia
|
||||
success = True
|
||||
if not run_script(script1, absolute_xml_filepath):
|
||||
print(f"\nPipeline falló en el script '{script1}' para el archivo: {relative_path}", file=sys.stderr)
|
||||
print(
|
||||
f"\nPipeline falló en el script '{script1}' para el archivo: {relative_path}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
success = False
|
||||
elif not run_script(script2, absolute_xml_filepath):
|
||||
print(f"\nPipeline falló en el script '{script2}' para el archivo: {relative_path}", file=sys.stderr)
|
||||
print(
|
||||
f"\nPipeline falló en el script '{script2}' para el archivo: {relative_path}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
success = False
|
||||
elif not run_script(script3, absolute_xml_filepath):
|
||||
print(f"\nPipeline falló en el script '{script3}' para el archivo: {relative_path}", file=sys.stderr)
|
||||
print(
|
||||
f"\nPipeline falló en el script '{script3}' para el archivo: {relative_path}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
success = False
|
||||
|
||||
# Actualizar contadores y mostrar estado
|
||||
|
@ -154,12 +191,16 @@ if __name__ == "__main__":
|
|||
processed_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
print(f"--- Pipeline falló para: {relative_path} ---", file=sys.stderr) # Indicar fallo
|
||||
print(
|
||||
f"--- Pipeline falló para: {relative_path} ---", file=sys.stderr
|
||||
) # Indicar fallo
|
||||
|
||||
# --- PARTE 3: RESUMEN FINAL ---
|
||||
print("\n--- Resumen Final del Procesamiento ---")
|
||||
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("---------------------------------------")
|
||||
|
||||
|
@ -169,4 +210,4 @@ if __name__ == "__main__":
|
|||
else:
|
||||
sys.exit(0)
|
||||
|
||||
# --- FIN: Se elimina la lógica redundante que venía después del bucle ---
|
||||
# --- FIN: Se elimina la lógica redundante que venía después del bucle ---
|
||||
|
|
406
x1_to_json.py
406
x1_to_json.py
|
@ -24,15 +24,16 @@ except ImportError as e:
|
|||
|
||||
# --- NUEVAS FUNCIONES DE PARSEO para UDT y Tag Table ---
|
||||
|
||||
|
||||
def parse_udt(udt_element):
|
||||
"""Parsea un elemento <SW.Types.PlcStruct> (UDT)."""
|
||||
print(" -> Detectado: PlcStruct (UDT)")
|
||||
block_data = {
|
||||
"block_name": "UnknownUDT",
|
||||
"block_type": "PlcUDT", # Identificador para x3
|
||||
"language": "UDT", # Lenguaje específico
|
||||
"block_type": "PlcUDT", # Identificador para x3
|
||||
"language": "UDT", # Lenguaje específico
|
||||
"interface": {},
|
||||
"networks": [], # Los UDTs no tienen redes
|
||||
"networks": [], # Los UDTs no tienen redes
|
||||
"block_comment": "",
|
||||
}
|
||||
|
||||
|
@ -43,66 +44,87 @@ def parse_udt(udt_element):
|
|||
name_node = attr_list.xpath("./Name/text()")
|
||||
block_data["block_name"] = name_node[0].strip() if name_node else "UnknownUDT"
|
||||
# Comentario del UDT
|
||||
comment_node_list = udt_element.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']")
|
||||
comment_node_list = udt_element.xpath(
|
||||
"./ObjectList/MultilingualText[@CompositionName='Comment']"
|
||||
)
|
||||
if comment_node_list:
|
||||
block_data["block_comment"] = get_multilingual_text(comment_node_list[0])
|
||||
else: # Fallback
|
||||
comment_attr_node = attr_list.xpath("../ObjectList/MultilingualText[@CompositionName='Comment']") # Buscar desde el padre
|
||||
if comment_attr_node :
|
||||
block_data["block_comment"] = get_multilingual_text(comment_attr_node[0])
|
||||
|
||||
block_data["block_comment"] = get_multilingual_text(comment_node_list[0])
|
||||
else: # Fallback
|
||||
comment_attr_node = attr_list.xpath(
|
||||
"../ObjectList/MultilingualText[@CompositionName='Comment']"
|
||||
) # Buscar desde el padre
|
||||
if comment_attr_node:
|
||||
block_data["block_comment"] = get_multilingual_text(
|
||||
comment_attr_node[0]
|
||||
)
|
||||
|
||||
# Extraer interfaz (miembros)
|
||||
# La interfaz de un UDT suele estar directamente en <Interface><Sections><Section Name="None">
|
||||
interface_node_list = udt_element.xpath(
|
||||
"./AttributeList/Interface/iface:Sections/iface:Section[@Name='None']", namespaces=ns
|
||||
"./AttributeList/Interface/iface:Sections/iface:Section[@Name='None']",
|
||||
namespaces=ns,
|
||||
)
|
||||
if interface_node_list:
|
||||
section_node = interface_node_list[0]
|
||||
members_in_section = section_node.xpath("./iface:Member", namespaces=ns)
|
||||
if members_in_section:
|
||||
# Usar la función existente para parsear miembros
|
||||
block_data["interface"]["None"] = parse_interface_members(members_in_section)
|
||||
block_data["interface"]["None"] = parse_interface_members(
|
||||
members_in_section
|
||||
)
|
||||
else:
|
||||
print(f"Advertencia: Sección 'None' encontrada en UDT '{block_data['block_name']}' pero sin miembros.")
|
||||
print(
|
||||
f"Advertencia: Sección 'None' encontrada en UDT '{block_data['block_name']}' pero sin miembros."
|
||||
)
|
||||
else:
|
||||
# Intentar buscar interfaz directamente si no está en AttributeList (menos común)
|
||||
interface_node_direct = udt_element.xpath(
|
||||
".//iface:Interface/iface:Sections/iface:Section[@Name='None']", namespaces=ns
|
||||
)
|
||||
if interface_node_direct:
|
||||
section_node = interface_node_direct[0]
|
||||
members_in_section = section_node.xpath("./iface:Member", namespaces=ns)
|
||||
if members_in_section:
|
||||
block_data["interface"]["None"] = parse_interface_members(members_in_section)
|
||||
else:
|
||||
print(f"Advertencia: Sección 'None' encontrada directamente en UDT '{block_data['block_name']}' pero sin miembros.")
|
||||
else:
|
||||
print(f"Advertencia: No se encontró la sección 'None' de la interfaz para UDT '{block_data['block_name']}'.")
|
||||
|
||||
# Intentar buscar interfaz directamente si no está en AttributeList (menos común)
|
||||
interface_node_direct = udt_element.xpath(
|
||||
".//iface:Interface/iface:Sections/iface:Section[@Name='None']",
|
||||
namespaces=ns,
|
||||
)
|
||||
if interface_node_direct:
|
||||
section_node = interface_node_direct[0]
|
||||
members_in_section = section_node.xpath("./iface:Member", namespaces=ns)
|
||||
if members_in_section:
|
||||
block_data["interface"]["None"] = parse_interface_members(
|
||||
members_in_section
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Advertencia: Sección 'None' encontrada directamente en UDT '{block_data['block_name']}' pero sin miembros."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Advertencia: No se encontró la sección 'None' de la interfaz para UDT '{block_data['block_name']}'."
|
||||
)
|
||||
|
||||
if not block_data["interface"]:
|
||||
print(f"Advertencia: No se pudo extraer la interfaz del UDT '{block_data['block_name']}'.")
|
||||
print(
|
||||
f"Advertencia: No se pudo extraer la interfaz del UDT '{block_data['block_name']}'."
|
||||
)
|
||||
|
||||
return block_data
|
||||
|
||||
|
||||
def parse_tag_table(tag_table_element):
|
||||
"""Parsea un elemento <SW.Tags.PlcTagTable>."""
|
||||
print(" -> Detectado: PlcTagTable")
|
||||
table_data = {
|
||||
"block_name": "UnknownTagTable",
|
||||
"block_type": "PlcTagTable", # Identificador para x3
|
||||
"language": "TagTable", # Lenguaje específico
|
||||
"block_type": "PlcTagTable", # Identificador para x3
|
||||
"language": "TagTable", # Lenguaje específico
|
||||
"tags": [],
|
||||
"networks": [], # Las Tag Tables no tienen redes
|
||||
"block_comment": "", # Las tablas de tags no suelen tener comentario de bloque
|
||||
"networks": [], # Las Tag Tables no tienen redes
|
||||
"block_comment": "", # Las tablas de tags no suelen tener comentario de bloque
|
||||
}
|
||||
|
||||
# Extraer nombre de la tabla
|
||||
attribute_list_node = tag_table_element.xpath("./AttributeList")
|
||||
if attribute_list_node:
|
||||
name_node = attribute_list_node[0].xpath("./Name/text()")
|
||||
table_data["block_name"] = name_node[0].strip() if name_node else "UnknownTagTable"
|
||||
table_data["block_name"] = (
|
||||
name_node[0].strip() if name_node else "UnknownTagTable"
|
||||
)
|
||||
|
||||
# Extraer tags
|
||||
tag_elements = tag_table_element.xpath("./ObjectList/SW.Tags.PlcTag")
|
||||
|
@ -112,7 +134,7 @@ def parse_tag_table(tag_table_element):
|
|||
"name": "UnknownTag",
|
||||
"datatype": "Unknown",
|
||||
"address": None,
|
||||
"comment": ""
|
||||
"comment": "",
|
||||
}
|
||||
tag_attr_list = tag_elem.xpath("./AttributeList")
|
||||
if tag_attr_list:
|
||||
|
@ -125,7 +147,9 @@ def parse_tag_table(tag_table_element):
|
|||
tag_info["address"] = addr_node[0].strip() if addr_node else None
|
||||
|
||||
# Extraer comentario del tag
|
||||
comment_node_list = tag_elem.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']")
|
||||
comment_node_list = tag_elem.xpath(
|
||||
"./ObjectList/MultilingualText[@CompositionName='Comment']"
|
||||
)
|
||||
if comment_node_list:
|
||||
tag_info["comment"] = get_multilingual_text(comment_node_list[0])
|
||||
|
||||
|
@ -133,6 +157,7 @@ def parse_tag_table(tag_table_element):
|
|||
|
||||
return table_data
|
||||
|
||||
|
||||
# --- Cargador Dinámico de Parsers (sin cambios) ---
|
||||
def load_parsers(parsers_dir="parsers"):
|
||||
"""
|
||||
|
@ -145,7 +170,7 @@ def load_parsers(parsers_dir="parsers"):
|
|||
parsers_dir_path = os.path.join(script_dir, parsers_dir)
|
||||
if not os.path.isdir(parsers_dir_path):
|
||||
print(f"Error: Directorio de parsers no encontrado: '{parsers_dir_path}'")
|
||||
return parser_map # Devuelve mapa vacío
|
||||
return parser_map # Devuelve mapa vacío
|
||||
|
||||
print(f"Cargando parsers desde: '{parsers_dir_path}'")
|
||||
parsers_package = os.path.basename(parsers_dir)
|
||||
|
@ -158,8 +183,10 @@ def load_parsers(parsers_dir="parsers"):
|
|||
and filename.endswith(".py")
|
||||
and filename not in ["__init__.py", "parser_utils.py"]
|
||||
):
|
||||
module_name_rel = filename[:-3] # Nombre sin .py (e.g., parse_lad_fbd)
|
||||
full_module_name = f"{parsers_package}.{module_name_rel}" # e.g., parsers.parse_lad_fbd
|
||||
module_name_rel = filename[:-3] # Nombre sin .py (e.g., parse_lad_fbd)
|
||||
full_module_name = (
|
||||
f"{parsers_package}.{module_name_rel}" # e.g., parsers.parse_lad_fbd
|
||||
)
|
||||
try:
|
||||
# Importar el módulo dinámicamente
|
||||
module = importlib.import_module(full_module_name)
|
||||
|
@ -181,7 +208,7 @@ def load_parsers(parsers_dir="parsers"):
|
|||
if isinstance(languages, list) and callable(parser_func):
|
||||
# Añadir la función al mapa para cada lenguaje que soporta
|
||||
for lang in languages:
|
||||
lang_upper = lang.upper() # Usar mayúsculas como clave
|
||||
lang_upper = lang.upper() # Usar mayúsculas como clave
|
||||
if lang_upper in parser_map:
|
||||
print(
|
||||
f" Advertencia: Parser para '{lang_upper}' en {full_module_name} sobrescribe definición anterior."
|
||||
|
@ -213,22 +240,25 @@ def load_parsers(parsers_dir="parsers"):
|
|||
print(f"Lenguajes soportados: {list(parser_map.keys())}")
|
||||
return parser_map
|
||||
|
||||
|
||||
# --- Función Principal de Conversión (MODIFICADA) ---
|
||||
def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
||||
"""Convierte XML a JSON, detectando tipo de bloque (FC/FB/OB/DB/UDT/TagTable)."""
|
||||
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
||||
if not os.path.exists(xml_filepath):
|
||||
print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'")
|
||||
return False # Indicar fallo
|
||||
return False # Indicar fallo
|
||||
|
||||
try:
|
||||
print("Paso 1: Parseando archivo XML...")
|
||||
parser = etree.XMLParser(remove_blank_text=True, recover=True) # recover=True puede ayudar
|
||||
parser = etree.XMLParser(
|
||||
remove_blank_text=True, recover=True
|
||||
) # recover=True puede ayudar
|
||||
tree = etree.parse(xml_filepath, parser)
|
||||
root = tree.getroot()
|
||||
print("Paso 1: Parseo XML completado.")
|
||||
|
||||
result = None # Inicializar resultado
|
||||
result = None # Inicializar resultado
|
||||
|
||||
# --- Detección del tipo de bloque/objeto principal ---
|
||||
print("Paso 2: Detectando tipo de objeto principal...")
|
||||
|
@ -240,7 +270,9 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
|||
|
||||
# Buscar Tag Table si no es UDT
|
||||
if result is None:
|
||||
tag_table_element = root.find(".//SW.Tags.PlcTagTable", namespaces=root.nsmap)
|
||||
tag_table_element = root.find(
|
||||
".//SW.Tags.PlcTagTable", namespaces=root.nsmap
|
||||
)
|
||||
if tag_table_element is not None:
|
||||
result = parse_tag_table(tag_table_element)
|
||||
|
||||
|
@ -258,42 +290,73 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
|||
if block_list:
|
||||
the_block = block_list[0]
|
||||
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"
|
||||
elif block_tag_name == "SW.Blocks.OB": block_type_found = "OB"
|
||||
print(f"Paso 2b: Bloque {block_tag_name} (Tipo: {block_type_found}) encontrado (ID={the_block.get('ID')}).")
|
||||
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"
|
||||
elif block_tag_name == "SW.Blocks.OB":
|
||||
block_type_found = "OB"
|
||||
print(
|
||||
f"Paso 2b: Bloque {block_tag_name} (Tipo: {block_type_found}) encontrado (ID={the_block.get('ID')})."
|
||||
)
|
||||
else:
|
||||
print("Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC/FB/GlobalDB/OB>) ni UDT ni Tag Table.")
|
||||
return False # Fallo si no se encuentra ningún objeto principal
|
||||
print(
|
||||
"Error Crítico: No se encontró el elemento raíz del bloque (<SW.Blocks.FC/FB/GlobalDB/OB>) ni UDT ni Tag Table."
|
||||
)
|
||||
return False # Fallo si no se encuentra ningún objeto principal
|
||||
|
||||
# --- Si es FC/FB/OB/DB, continuar con el parseo original ---
|
||||
if the_block is not None:
|
||||
print("Paso 3: Extrayendo atributos del bloque...")
|
||||
# (Extracción de atributos Name, Number, Language como antes...)
|
||||
attribute_list_node = the_block.xpath("./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",
|
||||
)
|
||||
if attribute_list_node:
|
||||
attr_list = attribute_list_node[0]
|
||||
name_node = attr_list.xpath("./Name/text()")
|
||||
block_name_val = name_node[0].strip() if name_node else block_name_val
|
||||
block_name_val = (
|
||||
name_node[0].strip() if name_node else block_name_val
|
||||
)
|
||||
num_node = attr_list.xpath("./Number/text()")
|
||||
try: block_number_val = int(num_node[0]) if num_node else None
|
||||
except (ValueError, TypeError): block_number_val = None
|
||||
try:
|
||||
block_number_val = int(num_node[0]) if num_node else None
|
||||
except (ValueError, TypeError):
|
||||
block_number_val = None
|
||||
lang_node = attr_list.xpath("./ProgrammingLanguage/text()")
|
||||
block_lang_val = (lang_node[0].strip() if lang_node else ("DB" if block_type_found == "GlobalDB" else "Unknown"))
|
||||
print(f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje Bloque='{block_lang_val}'")
|
||||
block_lang_val = (
|
||||
lang_node[0].strip()
|
||||
if lang_node
|
||||
else ("DB" if block_type_found == "GlobalDB" else "Unknown")
|
||||
)
|
||||
print(
|
||||
f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje Bloque='{block_lang_val}'"
|
||||
)
|
||||
else:
|
||||
print(f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}.")
|
||||
if block_type_found == "GlobalDB": block_lang_val = "DB"
|
||||
print(
|
||||
f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}."
|
||||
)
|
||||
if block_type_found == "GlobalDB":
|
||||
block_lang_val = "DB"
|
||||
|
||||
# (Extracción de comentario como antes...)
|
||||
block_comment_val = ""
|
||||
comment_node_list = the_block.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']")
|
||||
if comment_node_list: block_comment_val = get_multilingual_text(comment_node_list[0])
|
||||
else: # Fallback
|
||||
comment_attr_node = the_block.xpath("./AttributeList/Comment") # Buscar desde AttributeList
|
||||
if comment_attr_node : block_comment_val = get_multilingual_text(comment_attr_node[0])
|
||||
comment_node_list = the_block.xpath(
|
||||
"./ObjectList/MultilingualText[@CompositionName='Comment']"
|
||||
)
|
||||
if comment_node_list:
|
||||
block_comment_val = get_multilingual_text(comment_node_list[0])
|
||||
else: # Fallback
|
||||
comment_attr_node = the_block.xpath(
|
||||
"./AttributeList/Comment"
|
||||
) # Buscar desde AttributeList
|
||||
if comment_attr_node:
|
||||
block_comment_val = get_multilingual_text(comment_attr_node[0])
|
||||
|
||||
print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'")
|
||||
|
||||
|
@ -305,34 +368,64 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
|||
"block_type": block_type_found,
|
||||
"block_comment": block_comment_val,
|
||||
"interface": {},
|
||||
"networks": [], # Inicializar networks aquí
|
||||
"networks": [], # Inicializar networks aquí
|
||||
}
|
||||
|
||||
# (Extracción de interfaz como antes...)
|
||||
print("Paso 4: Extrayendo la interfaz del bloque...")
|
||||
interface_node_list = attribute_list_node[0].xpath("./Interface") if attribute_list_node else []
|
||||
interface_node_list = (
|
||||
attribute_list_node[0].xpath("./Interface")
|
||||
if attribute_list_node
|
||||
else []
|
||||
)
|
||||
if interface_node_list:
|
||||
interface_node = interface_node_list[0]
|
||||
all_sections = interface_node.xpath(".//iface:Section", namespaces=ns)
|
||||
all_sections = interface_node.xpath(
|
||||
".//iface:Section", namespaces=ns
|
||||
)
|
||||
if all_sections:
|
||||
processed_sections = set()
|
||||
for section in all_sections:
|
||||
section_name = section.get("Name")
|
||||
if not section_name or section_name in processed_sections: continue
|
||||
members_in_section = section.xpath("./iface:Member", namespaces=ns)
|
||||
if not section_name or section_name in processed_sections:
|
||||
continue
|
||||
members_in_section = section.xpath(
|
||||
"./iface:Member", namespaces=ns
|
||||
)
|
||||
if members_in_section:
|
||||
result["interface"][section_name] = parse_interface_members(members_in_section)
|
||||
result["interface"][section_name] = (
|
||||
parse_interface_members(members_in_section)
|
||||
)
|
||||
processed_sections.add(section_name)
|
||||
else: print("Advertencia: Nodo Interface no contiene secciones <iface:Section>.")
|
||||
if not result["interface"]: print("Advertencia: Interface encontrada pero sin secciones procesables.")
|
||||
else:
|
||||
print(
|
||||
"Advertencia: Nodo Interface no contiene secciones <iface:Section>."
|
||||
)
|
||||
if not result["interface"]:
|
||||
print(
|
||||
"Advertencia: Interface encontrada pero sin secciones procesables."
|
||||
)
|
||||
elif block_type_found == "GlobalDB":
|
||||
static_members = the_block.xpath(".//iface:Section[@Name='Static']/iface:Member", namespaces=ns)
|
||||
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 (sin nodo Interface).")
|
||||
result["interface"]["Static"] = parse_interface_members(static_members)
|
||||
else: print("Advertencia: No se encontró sección 'Static' para GlobalDB.")
|
||||
else: print(f"Advertencia: No se encontró <Interface> para bloque {block_type_found}.")
|
||||
if not result["interface"]: print("Advertencia: No se pudo extraer información de la interfaz.")
|
||||
print(
|
||||
"Paso 4: Encontrada sección Static para GlobalDB (sin nodo Interface)."
|
||||
)
|
||||
result["interface"]["Static"] = parse_interface_members(
|
||||
static_members
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"Advertencia: No se encontró sección 'Static' para GlobalDB."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Advertencia: No se encontró <Interface> para bloque {block_type_found}."
|
||||
)
|
||||
if not result["interface"]:
|
||||
print("Advertencia: No se pudo extraer información de la interfaz.")
|
||||
|
||||
# (Procesamiento de redes como antes, SOLO si NO es GlobalDB)
|
||||
if block_type_found != "GlobalDB":
|
||||
|
@ -341,82 +434,147 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
|||
result["networks"] = []
|
||||
object_list_node = the_block.xpath("./ObjectList")
|
||||
if object_list_node:
|
||||
compile_units = object_list_node[0].xpath("./SW.Blocks.CompileUnit")
|
||||
print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.")
|
||||
compile_units = object_list_node[0].xpath(
|
||||
"./SW.Blocks.CompileUnit"
|
||||
)
|
||||
print(
|
||||
f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit."
|
||||
)
|
||||
|
||||
# Bucle de parseo de redes (igual que antes)
|
||||
for network_elem in compile_units:
|
||||
networks_processed_count += 1
|
||||
network_id = network_elem.get("ID")
|
||||
if not network_id: continue
|
||||
if not network_id:
|
||||
continue
|
||||
network_lang = "LAD"
|
||||
net_attr_list = network_elem.xpath("./AttributeList")
|
||||
if net_attr_list:
|
||||
lang_node = net_attr_list[0].xpath("./ProgrammingLanguage/text()")
|
||||
if lang_node: network_lang = lang_node[0].strip()
|
||||
print(f" - Procesando Red ID={network_id}, Lenguaje Red={network_lang}")
|
||||
lang_node = net_attr_list[0].xpath(
|
||||
"./ProgrammingLanguage/text()"
|
||||
)
|
||||
if lang_node:
|
||||
network_lang = lang_node[0].strip()
|
||||
print(
|
||||
f" - Procesando Red ID={network_id}, Lenguaje Red={network_lang}"
|
||||
)
|
||||
parser_func = parser_map.get(network_lang.upper())
|
||||
parsed_network_data = None
|
||||
if parser_func:
|
||||
try:
|
||||
parsed_network_data = parser_func(network_elem)
|
||||
except Exception as e_parse:
|
||||
print(f" ERROR durante el parseo de Red {network_id} ({network_lang}): {e_parse}")
|
||||
print(
|
||||
f" ERROR durante el parseo de Red {network_id} ({network_lang}): {e_parse}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
parsed_network_data = {"id": network_id, "language": network_lang, "logic": [], "error": f"Parser failed: {e_parse}"}
|
||||
parsed_network_data = {
|
||||
"id": network_id,
|
||||
"language": network_lang,
|
||||
"logic": [],
|
||||
"error": f"Parser failed: {e_parse}",
|
||||
}
|
||||
else:
|
||||
print(f" Advertencia: Lenguaje de red '{network_lang}' no soportado.")
|
||||
parsed_network_data = {"id": network_id, "language": network_lang, "logic": [], "error": f"Unsupported language: {network_lang}"}
|
||||
print(
|
||||
f" Advertencia: Lenguaje de red '{network_lang}' no soportado."
|
||||
)
|
||||
parsed_network_data = {
|
||||
"id": network_id,
|
||||
"language": network_lang,
|
||||
"logic": [],
|
||||
"error": f"Unsupported language: {network_lang}",
|
||||
}
|
||||
|
||||
if parsed_network_data:
|
||||
title_element = network_elem.xpath(".//iface:MultilingualText[@CompositionName='Title']",namespaces=ns)
|
||||
parsed_network_data["title"] = (get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}")
|
||||
comment_elem_net = network_elem.xpath("./ObjectList/MultilingualText[@CompositionName='Comment']", namespaces=ns)
|
||||
if not comment_elem_net: comment_elem_net = network_elem.xpath(".//MultilingualText[@CompositionName='Comment']", namespaces=ns) # Fallback
|
||||
parsed_network_data["comment"] = (get_multilingual_text(comment_elem_net[0]) if comment_elem_net else "")
|
||||
title_element = network_elem.xpath(
|
||||
".//iface:MultilingualText[@CompositionName='Title']",
|
||||
namespaces=ns,
|
||||
)
|
||||
parsed_network_data["title"] = (
|
||||
get_multilingual_text(title_element[0])
|
||||
if title_element
|
||||
else f"Network {network_id}"
|
||||
)
|
||||
comment_elem_net = network_elem.xpath(
|
||||
"./ObjectList/MultilingualText[@CompositionName='Comment']",
|
||||
namespaces=ns,
|
||||
)
|
||||
if not comment_elem_net:
|
||||
comment_elem_net = network_elem.xpath(
|
||||
".//MultilingualText[@CompositionName='Comment']",
|
||||
namespaces=ns,
|
||||
) # Fallback
|
||||
parsed_network_data["comment"] = (
|
||||
get_multilingual_text(comment_elem_net[0])
|
||||
if comment_elem_net
|
||||
else ""
|
||||
)
|
||||
result["networks"].append(parsed_network_data)
|
||||
|
||||
if networks_processed_count == 0: print(f"Advertencia: ObjectList para {block_type_found} sin SW.Blocks.CompileUnit.")
|
||||
else: print(f"Advertencia: No se encontró ObjectList para el bloque {block_type_found}.")
|
||||
else: # Es GlobalDB
|
||||
if networks_processed_count == 0:
|
||||
print(
|
||||
f"Advertencia: ObjectList para {block_type_found} sin SW.Blocks.CompileUnit."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Advertencia: No se encontró ObjectList para el bloque {block_type_found}."
|
||||
)
|
||||
else: # Es GlobalDB
|
||||
print("Paso 5: Saltando procesamiento de redes para GlobalDB.")
|
||||
|
||||
|
||||
# --- Escritura del JSON (si se encontró un objeto) ---
|
||||
if result:
|
||||
print("Paso 6: Escribiendo el resultado en el archivo JSON...")
|
||||
# Validaciones finales
|
||||
if result.get("block_type") not in ["PlcUDT", "PlcTagTable"] and not result["interface"]:
|
||||
print("ADVERTENCIA FINAL: 'interface' está vacía en el JSON.")
|
||||
if result.get("block_type") not in ["PlcUDT", "PlcTagTable", "GlobalDB"] and not result["networks"]:
|
||||
print("ADVERTENCIA FINAL: 'networks' está vacía en el JSON.")
|
||||
if (
|
||||
result.get("block_type") not in ["PlcUDT", "PlcTagTable"]
|
||||
and not result["interface"]
|
||||
):
|
||||
print("ADVERTENCIA FINAL: 'interface' está vacía en el JSON.")
|
||||
if (
|
||||
result.get("block_type") not in ["PlcUDT", "PlcTagTable", "GlobalDB"]
|
||||
and not result["networks"]
|
||||
):
|
||||
print("ADVERTENCIA FINAL: 'networks' está vacía en el JSON.")
|
||||
|
||||
try:
|
||||
with open(json_filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, indent=4, ensure_ascii=False)
|
||||
print("Paso 6: Escritura JSON completada.")
|
||||
print(f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'")
|
||||
return True # Indicar éxito
|
||||
print(
|
||||
f"Conversión finalizada. JSON guardado en: '{os.path.relpath(json_filepath)}'"
|
||||
)
|
||||
return True # Indicar éxito
|
||||
|
||||
except IOError as e: print(f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}"); return False
|
||||
except TypeError as e: print(f"Error Crítico: Problema al serializar a JSON. Error: {e}"); return False
|
||||
except IOError as e:
|
||||
print(
|
||||
f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}"
|
||||
)
|
||||
return False
|
||||
except TypeError as e:
|
||||
print(f"Error Crítico: Problema al serializar a JSON. Error: {e}")
|
||||
return False
|
||||
else:
|
||||
print("Error Crítico: No se pudo determinar el tipo de objeto principal en el XML.")
|
||||
return False
|
||||
|
||||
print(
|
||||
"Error Crítico: No se pudo determinar el tipo de objeto principal en el XML."
|
||||
)
|
||||
return False
|
||||
|
||||
except etree.XMLSyntaxError as e:
|
||||
print(f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}")
|
||||
return False # Indicar fallo
|
||||
print(
|
||||
f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}"
|
||||
)
|
||||
return False # Indicar fallo
|
||||
except Exception as e:
|
||||
print(f"Error Crítico: Error inesperado durante la conversión: {e}")
|
||||
traceback.print_exc()
|
||||
return False # Indicar fallo
|
||||
return False # Indicar fallo
|
||||
|
||||
|
||||
# --- Punto de Entrada Principal (__main__) ---
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert Simatic XML (FC/FB/OB/DB/UDT/TagTable) to simplified JSON using dynamic parsers." # Actualizado
|
||||
description="Convert Simatic XML (FC/FB/OB/DB/UDT/TagTable) to simplified JSON using dynamic parsers." # Actualizado
|
||||
)
|
||||
parser.add_argument(
|
||||
"xml_filepath",
|
||||
|
@ -426,15 +584,20 @@ if __name__ == "__main__":
|
|||
xml_input_file = args.xml_filepath
|
||||
|
||||
if not os.path.exists(xml_input_file):
|
||||
print(f"Error Crítico (x1): Archivo XML no encontrado: '{xml_input_file}'", file=sys.stderr)
|
||||
print(
|
||||
f"Error Crítico (x1): Archivo XML no encontrado: '{xml_input_file}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Cargar Parsers Dinámicamente ---
|
||||
loaded_parsers = load_parsers() # Carga parsers LAD/FBD/STL/SCL
|
||||
loaded_parsers = load_parsers() # Carga parsers LAD/FBD/STL/SCL
|
||||
if not loaded_parsers:
|
||||
# Continuar incluso sin parsers de red, ya que podríamos estar parseando UDT/TagTable
|
||||
print("Advertencia (x1): No se cargaron parsers de red. Se continuará para UDT/TagTable/DB.")
|
||||
#sys.exit(1) # Ya no salimos si no hay parsers de red
|
||||
print(
|
||||
"Advertencia (x1): No se cargaron parsers de red. Se continuará para UDT/TagTable/DB."
|
||||
)
|
||||
# sys.exit(1) # Ya no salimos si no hay parsers de red
|
||||
|
||||
# Derivar nombre de salida JSON
|
||||
xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0]
|
||||
|
@ -443,14 +606,19 @@ if __name__ == "__main__":
|
|||
os.makedirs(output_dir, exist_ok=True)
|
||||
json_output_file = os.path.join(output_dir, f"{xml_filename_base}.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 de conversión principal
|
||||
success = convert_xml_to_json(xml_input_file, json_output_file, loaded_parsers)
|
||||
|
||||
# Salir con código de error apropiado
|
||||
if success:
|
||||
sys.exit(0) # Éxito
|
||||
sys.exit(0) # Éxito
|
||||
else:
|
||||
print(f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.", file=sys.stderr)
|
||||
sys.exit(1) # Fallo
|
||||
print(
|
||||
f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1) # Fallo
|
||||
|
|
226
x2_process.py
226
x2_process.py
|
@ -7,15 +7,15 @@ import traceback
|
|||
import re
|
||||
import importlib
|
||||
import sys
|
||||
import sympy # Import sympy
|
||||
import sympy # Import sympy
|
||||
|
||||
# Import necessary components from processors directory
|
||||
from processors.processor_utils import (
|
||||
format_variable_name, # Keep if used outside processors
|
||||
sympy_expr_to_scl, # Needed for IF grouping and maybe others
|
||||
format_variable_name, # Keep if used outside processors
|
||||
sympy_expr_to_scl, # Needed for IF grouping and maybe others
|
||||
# get_target_scl_name might be used here? Unlikely.
|
||||
)
|
||||
from processors.symbol_manager import SymbolManager # Import the manager
|
||||
from processors.symbol_manager import SymbolManager # Import the manager
|
||||
|
||||
# --- Constantes y Configuración ---
|
||||
SCL_SUFFIX = "_sympy_processed"
|
||||
|
@ -25,6 +25,7 @@ SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script"
|
|||
# Global data dictionary
|
||||
data = {}
|
||||
|
||||
|
||||
# --- (process_group_ifs y load_processors SIN CAMBIOS) ---
|
||||
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
||||
"""
|
||||
|
@ -109,9 +110,15 @@ 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",
|
||||
"BLKMOV", # Added BLKMOV
|
||||
"TON", "TOF", "TP", "Se", "Sd", # Added timers
|
||||
"CTU", "CTD", "CTUD", # Added counters
|
||||
"BLKMOV", # Added BLKMOV
|
||||
"TON",
|
||||
"TOF",
|
||||
"TP",
|
||||
"Se",
|
||||
"Sd", # Added timers
|
||||
"CTU",
|
||||
"CTD",
|
||||
"CTUD", # Added counters
|
||||
]
|
||||
|
||||
for consumer_instr in network_logic:
|
||||
|
@ -137,7 +144,7 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
|||
# Check if consumer is groupable AND has its final SCL generated
|
||||
if (
|
||||
is_enabled_by_us
|
||||
and consumer_type.endswith(SCL_SUFFIX) # Check if processed
|
||||
and consumer_type.endswith(SCL_SUFFIX) # Check if processed
|
||||
and consumer_type_original in groupable_types
|
||||
):
|
||||
|
||||
|
@ -148,7 +155,7 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
|||
# If consumer SCL itself is an IF generated by EN, take the body
|
||||
if consumer_scl.strip().startswith("IF"):
|
||||
match = re.search(
|
||||
r"IF\s+.*?THEN\s*(.*?)\s*END_IF;", # More robust regex
|
||||
r"IF\s+.*?THEN\s*(.*?)\s*END_IF;", # More robust regex
|
||||
consumer_scl,
|
||||
re.DOTALL | re.IGNORECASE,
|
||||
)
|
||||
|
@ -203,18 +210,19 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
|
|||
|
||||
return made_change
|
||||
|
||||
|
||||
def load_processors(processors_dir="processors"):
|
||||
"""
|
||||
Escanea el directorio, importa módulos, construye el mapa y una lista
|
||||
ordenada por prioridad.
|
||||
"""
|
||||
processor_map = {}
|
||||
processor_list_unsorted = [] # Lista para guardar (priority, type_name, func)
|
||||
default_priority = 10 # Prioridad si no se define en get_processor_info
|
||||
processor_list_unsorted = [] # Lista para guardar (priority, type_name, func)
|
||||
default_priority = 10 # Prioridad si no se define en get_processor_info
|
||||
|
||||
if not os.path.isdir(processors_dir):
|
||||
print(f"Error: Directorio de procesadores no encontrado: '{processors_dir}'")
|
||||
return processor_map, [] # Devuelve mapa vacío y lista vacía
|
||||
return processor_map, [] # Devuelve mapa vacío y lista vacía
|
||||
|
||||
print(f"Cargando procesadores desde: '{processors_dir}'")
|
||||
processors_package = os.path.basename(processors_dir)
|
||||
|
@ -325,9 +333,15 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
print(f"Procesando bloque tipo: {block_type}")
|
||||
|
||||
# --- MODIFICADO: SALTAR PROCESAMIENTO PARA DB, UDT, TAG TABLE ---
|
||||
if block_type in ["GlobalDB", "PlcUDT", "PlcTagTable"]: # <-- Comprobar tipos a saltar
|
||||
if block_type in [
|
||||
"GlobalDB",
|
||||
"PlcUDT",
|
||||
"PlcTagTable",
|
||||
]: # <-- Comprobar tipos a saltar
|
||||
print(f"INFO: El bloque es {block_type}. Saltando procesamiento lógico de x2.")
|
||||
print(f"Guardando JSON de {block_type} (sin cambios lógicos) en: {output_json_filepath}")
|
||||
print(
|
||||
f"Guardando JSON de {block_type} (sin cambios lógicos) en: {output_json_filepath}"
|
||||
)
|
||||
try:
|
||||
with open(output_json_filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
@ -355,14 +369,26 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
current_access_map = {}
|
||||
for instr in network.get("logic", []):
|
||||
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:
|
||||
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
|
||||
for _, dest_list in instr.get("outputs", {}).items():
|
||||
if isinstance(dest_list, 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
|
||||
network_access_maps[net_id] = current_access_map
|
||||
|
||||
|
@ -391,7 +417,8 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
for network in data.get("networks", []):
|
||||
network_id = network["id"]
|
||||
network_lang = network.get("language", "LAD")
|
||||
if network_lang == "STL": continue
|
||||
if network_lang == "STL":
|
||||
continue
|
||||
|
||||
access_map = network_access_maps.get(network_id, {})
|
||||
network_logic = network.get("logic", [])
|
||||
|
@ -399,30 +426,51 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
instr_uid = instruction.get("instruction_uid")
|
||||
instr_type_current = instruction.get("type", "Unknown")
|
||||
|
||||
if (instr_type_current.endswith(SCL_SUFFIX) or "_error" in instr_type_current or instruction.get("grouped", False) or
|
||||
instr_type_current in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG", "UNSUPPORTED_CONTENT", "PARSING_ERROR"]):
|
||||
if (
|
||||
instr_type_current.endswith(SCL_SUFFIX)
|
||||
or "_error" in instr_type_current
|
||||
or instruction.get("grouped", False)
|
||||
or instr_type_current
|
||||
in [
|
||||
"RAW_STL_CHUNK",
|
||||
"RAW_SCL_CHUNK",
|
||||
"UNSUPPORTED_LANG",
|
||||
"UNSUPPORTED_CONTENT",
|
||||
"PARSING_ERROR",
|
||||
]
|
||||
):
|
||||
continue
|
||||
|
||||
lookup_key = instr_type_current.lower()
|
||||
effective_type_name = lookup_key
|
||||
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"
|
||||
if call_block_type == "FC":
|
||||
effective_type_name = "call_fc"
|
||||
elif call_block_type == "FB":
|
||||
effective_type_name = "call_fb"
|
||||
|
||||
if effective_type_name == current_type_name:
|
||||
try:
|
||||
changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data)
|
||||
changed = func_to_call(
|
||||
instruction, network_id, sympy_map, symbol_manager, data
|
||||
)
|
||||
if changed:
|
||||
made_change_in_base_pass = True
|
||||
num_sympy_processed_this_pass += 1
|
||||
except Exception as e:
|
||||
print(f"ERROR(SymPy Base) al procesar {instr_type_current} UID {instr_uid}: {e}")
|
||||
print(
|
||||
f"ERROR(SymPy Base) al procesar {instr_type_current} UID {instr_uid}: {e}"
|
||||
)
|
||||
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_current + "_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)
|
||||
if made_change_in_base_pass or passes == 1:
|
||||
|
@ -431,30 +479,58 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
for network in data.get("networks", []):
|
||||
network_id = network["id"]
|
||||
network_lang = network.get("language", "LAD")
|
||||
if network_lang == "STL": continue
|
||||
if network_lang == "STL":
|
||||
continue
|
||||
network_logic = network.get("logic", [])
|
||||
uids_in_network = sorted([instr.get("instruction_uid", "Z") for instr in network_logic if instr.get("instruction_uid")])
|
||||
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
|
||||
if instruction.get("grouped") or "_error" in instruction.get("type", ""): continue
|
||||
if instruction.get("type", "").endswith(SCL_SUFFIX):
|
||||
instruction = next(
|
||||
(
|
||||
instr
|
||||
for instr in network_logic
|
||||
if instr.get("instruction_uid") == uid_to_process
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not instruction:
|
||||
continue
|
||||
if instruction.get("grouped") or "_error" in instruction.get(
|
||||
"type", ""
|
||||
):
|
||||
continue
|
||||
if instruction.get("type", "").endswith(SCL_SUFFIX):
|
||||
try:
|
||||
group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data)
|
||||
group_changed = process_group_ifs(
|
||||
instruction, network_id, sympy_map, symbol_manager, data
|
||||
)
|
||||
if group_changed:
|
||||
made_change_in_group_pass = True
|
||||
num_grouped_this_pass += 1
|
||||
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()
|
||||
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ó
|
||||
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
|
||||
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..."
|
||||
)
|
||||
if passes == max_passes and not processing_complete:
|
||||
print(f"\n--- ADVERTENCIA: Límite de {max_passes} pases alcanzado...")
|
||||
|
||||
|
@ -464,58 +540,92 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
|||
print(f"\n--- Verificación Final de Instrucciones No Procesadas ({block_type}) ---")
|
||||
unprocessed_count = 0
|
||||
unprocessed_details = []
|
||||
ignored_types = ["raw_scl_chunk", "unsupported_lang", "raw_stl_chunk", "unsupported_content", "parsing_error"]
|
||||
ignored_types = [
|
||||
"raw_scl_chunk",
|
||||
"unsupported_lang",
|
||||
"raw_stl_chunk",
|
||||
"unsupported_content",
|
||||
"parsing_error",
|
||||
]
|
||||
for network in data.get("networks", []):
|
||||
network_id = network.get("id", "Unknown ID")
|
||||
network_title = network.get("title", f"Network {network_id}")
|
||||
network_lang = network.get("language", "LAD")
|
||||
if network_lang == "STL": continue
|
||||
if network_lang == "STL":
|
||||
continue
|
||||
for instruction in network.get("logic", []):
|
||||
instr_uid = instruction.get("instruction_uid", "Unknown UID")
|
||||
instr_type = instruction.get("type", "Unknown Type")
|
||||
is_grouped = instruction.get("grouped", False)
|
||||
if (not instr_type.endswith(SCL_SUFFIX) and "_error" not in instr_type and not is_grouped and instr_type.lower() not in ignored_types):
|
||||
if (
|
||||
not instr_type.endswith(SCL_SUFFIX)
|
||||
and "_error" not in instr_type
|
||||
and not is_grouped
|
||||
and instr_type.lower() not in ignored_types
|
||||
):
|
||||
unprocessed_count += 1
|
||||
unprocessed_details.append(f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), Instrucción UID: {instr_uid}, Tipo: '{instr_type}'")
|
||||
unprocessed_details.append(
|
||||
f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), Instrucción UID: {instr_uid}, Tipo: '{instr_type}'"
|
||||
)
|
||||
if unprocessed_count > 0:
|
||||
print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:")
|
||||
for detail in unprocessed_details: print(detail)
|
||||
else: print("INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas.")
|
||||
print(
|
||||
f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:"
|
||||
)
|
||||
for detail in unprocessed_details:
|
||||
print(detail)
|
||||
else:
|
||||
print(
|
||||
"INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas."
|
||||
)
|
||||
|
||||
print(f"\nGuardando JSON procesado ({block_type}) en: {output_json_filepath}")
|
||||
try:
|
||||
with open(output_json_filepath, "w", encoding="utf-8") as f:
|
||||
with open(output_json_filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
print("Guardado completado.")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error Crítico al guardar JSON procesado: {e}");
|
||||
except Exception as e:
|
||||
print(f"Error Crítico al guardar JSON procesado: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
# --- Ejecución (MODIFICADO) ---
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic. Expects original XML filepath as argument.")
|
||||
parser.add_argument("source_xml_filepath", help="Path to the original source XML file (passed from x0_main.py).")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Process simplified JSON to embed SCL logic. Expects original XML filepath as argument."
|
||||
)
|
||||
parser.add_argument(
|
||||
"source_xml_filepath",
|
||||
help="Path to the original source XML file (passed from x0_main.py).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
source_xml_file = args.source_xml_filepath
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
||||
base_dir = os.path.dirname(source_xml_file)
|
||||
parsing_dir = os.path.join(base_dir, "parsing")
|
||||
input_json_file = os.path.join(parsing_dir, f"{xml_filename_base}.json")
|
||||
output_json_file = os.path.join(parsing_dir, f"{xml_filename_base}_processed.json")
|
||||
|
||||
|
||||
os.makedirs(parsing_dir, exist_ok=True)
|
||||
|
||||
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)}'"
|
||||
)
|
||||
|
||||
if not os.path.exists(input_json_file):
|
||||
print(f"Error Fatal (x2): El archivo de entrada JSON 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)}'.")
|
||||
print(
|
||||
f"Error Fatal (x2): El archivo de entrada JSON 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)
|
||||
else:
|
||||
try:
|
||||
|
@ -525,6 +635,8 @@ if __name__ == "__main__":
|
|||
else:
|
||||
sys.exit(1)
|
||||
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}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
|
|
|
@ -13,6 +13,7 @@ try:
|
|||
from generators.generate_scl_code_block import generate_scl_for_code_block
|
||||
from generators.generate_md_udt import generate_udt_markdown
|
||||
from generators.generate_md_tag_table import generate_tag_table_markdown
|
||||
|
||||
# Importar format_variable_name (necesario para el nombre de archivo)
|
||||
from generators.generator_utils import format_variable_name
|
||||
except ImportError as e:
|
||||
|
@ -20,6 +21,7 @@ except ImportError as e:
|
|||
print("Asegúrate de que el directorio 'generators' y sus archivos .py existen.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# --- Función Principal de Generación (Despachador) ---
|
||||
def generate_scl_or_markdown(processed_json_filepath, output_directory):
|
||||
"""
|
||||
|
@ -35,15 +37,19 @@ def generate_scl_or_markdown(processed_json_filepath, output_directory):
|
|||
with open(processed_json_filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error al cargar/parsear JSON: {e}"); traceback.print_exc(); return
|
||||
print(f"Error al cargar/parsear JSON: {e}")
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
block_name = data.get("block_name", "UnknownBlock")
|
||||
block_type = data.get("block_type", "Unknown")
|
||||
scl_block_name = format_variable_name(block_name) # Nombre seguro para archivo
|
||||
scl_block_name = format_variable_name(block_name) # Nombre seguro para archivo
|
||||
output_content = []
|
||||
output_extension = ".scl" # Default
|
||||
output_extension = ".scl" # Default
|
||||
|
||||
print(f"Generando salida para: {block_type} '{scl_block_name}' (Original: {block_name})")
|
||||
print(
|
||||
f"Generando salida para: {block_type} '{scl_block_name}' (Original: {block_name})"
|
||||
)
|
||||
|
||||
# --- Selección del Generador y Extensión ---
|
||||
generation_function = None
|
||||
|
@ -63,8 +69,10 @@ def generate_scl_or_markdown(processed_json_filepath, output_directory):
|
|||
print(f" -> Modo de generación: {block_type} SCL")
|
||||
generation_function = generate_scl_for_code_block
|
||||
output_extension = ".scl"
|
||||
else: # Tipo desconocido
|
||||
print(f"Error: Tipo de bloque desconocido '{block_type}'. No se generará archivo.")
|
||||
else: # Tipo desconocido
|
||||
print(
|
||||
f"Error: Tipo de bloque desconocido '{block_type}'. No se generará archivo."
|
||||
)
|
||||
return
|
||||
|
||||
# --- Llamar a la función generadora ---
|
||||
|
@ -72,9 +80,11 @@ def generate_scl_or_markdown(processed_json_filepath, output_directory):
|
|||
try:
|
||||
output_content = generation_function(data)
|
||||
except Exception as gen_e:
|
||||
print(f"Error durante la generación de contenido para {block_type} '{scl_block_name}': {gen_e}")
|
||||
print(
|
||||
f"Error durante la generación de contenido para {block_type} '{scl_block_name}': {gen_e}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
return # No intentar escribir si la generación falla
|
||||
return # No intentar escribir si la generación falla
|
||||
|
||||
# --- Escritura del Archivo de Salida ---
|
||||
output_filename_base = f"{scl_block_name}{output_extension}"
|
||||
|
@ -91,20 +101,35 @@ def generate_scl_or_markdown(processed_json_filepath, output_directory):
|
|||
print(f"Error al escribir el archivo {output_extension.upper()}: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# --- Ejecución ---
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate final SCL or Markdown file.")
|
||||
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}'.")
|
||||
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}'."
|
||||
)
|
||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
||||
base_dir = os.path.dirname(source_xml_file)
|
||||
parsing_dir = os.path.join(base_dir, "parsing")
|
||||
input_json_file = os.path.join(parsing_dir, f"{xml_filename_base}_processed.json")
|
||||
output_dir = base_dir
|
||||
print(f"(x3) Generando SCL/MD desde: '{os.path.relpath(input_json_file)}' en directorio: '{os.path.relpath(output_dir)}'")
|
||||
print(
|
||||
f"(x3) Generando SCL/MD desde: '{os.path.relpath(input_json_file)}' en directorio: '{os.path.relpath(output_dir)}'"
|
||||
)
|
||||
if not os.path.exists(input_json_file):
|
||||
print(f"Error Fatal (x3): JSON procesado no encontrado: '{input_json_file}'"); sys.exit(1)
|
||||
print(f"Error Fatal (x3): JSON procesado no encontrado: '{input_json_file}'")
|
||||
sys.exit(1)
|
||||
else:
|
||||
try: generate_scl_or_markdown(input_json_file, output_dir); sys.exit(0)
|
||||
except Exception as e: print(f"Error Crítico (x3): {e}"); traceback.print_exc(); sys.exit(1)
|
||||
try:
|
||||
generate_scl_or_markdown(input_json_file, output_dir)
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Error Crítico (x3): {e}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
|
Loading…
Reference in New Issue