import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import xml.etree.ElementTree as ET
import os
import re
from xml.dom import minidom
from collections import OrderedDict, defaultdict
import tkinter.font as tkfont
class TemplateEditor:
def __init__(self, parent, main_window):
self.parent = parent
self.main_window = main_window
self.frame = ttk.Frame(parent)
self.column_names = [
"path", "type", "variable", "id", "data_type_id",
"subsystem_id", "parts", "parts_id", "attribute",
"attribute_id", "mapping"
]
self.required_columns = [col for col in self.column_names if col != "mapping"]
self.current_file = None
self.original_data = []
self.display_data = []
self.temp_data = []
self.row_counter = 1
self.type_options = set()
self.data_type_id_options = set()
self.is_searching = False
self.search_results_indices = []
self.search_text = ""
self.block_updates = False
self.create_widgets()
self.last_added_row = None
self.cell_editors = {} # 存储单元格编辑器
def create_widgets(self):
"""创建模板编辑界面"""
# 按钮区域
btn_frame = ttk.Frame(self.frame)
btn_frame.pack(fill=tk.X, padx=10, pady=10)
# 创建按钮
self.load_btn = ttk.Button(btn_frame, text="加载文件", command=self.load_xml)
self.add_btn = ttk.Button(btn_frame, text="添加记录", command=self.add_record)
self.delete_btn = ttk.Button(btn_frame, text="删除记录", command=self.delete_record)
self.export_btn = ttk.Button(btn_frame, text="导出文件", command=self.export_xml)
# 按钮布局
self.load_btn.pack(side=tk.LEFT, padx=5)
self.add_btn.pack(side=tk.LEFT, padx=5)
self.delete_btn.pack(side=tk.LEFT, padx=5)
self.export_btn.pack(side=tk.LEFT, padx=5)
# 搜索区域 - 调整按钮位置到右侧
search_frame = ttk.Frame(btn_frame)
search_frame.pack(side=tk.RIGHT, padx=5)
ttk.Label(search_frame, text="搜索列:").pack(side=tk.LEFT)
self.column_combo = ttk.Combobox(search_frame, values=["所有列"] + self.column_names, width=10)
self.column_combo.current(0)
self.column_combo.pack(side=tk.LEFT, padx=5)
ttk.Label(search_frame, text="搜索内容:").pack(side=tk.LEFT)
self.search_box = ttk.Entry(search_frame, width=20)
self.search_box.pack(side=tk.LEFT, padx=5)
# 搜索和清空按钮放在右侧
self.search_btn = ttk.Button(search_frame, text="搜索", command=self.search_records)
self.clear_btn = ttk.Button(search_frame, text="清空搜索", command=self.clear_search)
self.search_btn.pack(side=tk.LEFT, padx=5)
self.clear_btn.pack(side=tk.LEFT, padx=5)
# 表格区域
table_frame = ttk.Frame(self.frame)
table_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建滚动条
scroll_y = ttk.Scrollbar(table_frame, orient=tk.VERTICAL)
scroll_x = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL)
# 创建表格
self.table = ttk.Treeview(
table_frame,
columns=("序号",) + tuple(self.column_names),
show="headings",
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set
)
# 配置列 - 表头靠左对齐
columns = ["序号"] + self.column_names
for col in columns:
self.table.heading(col, text=col, anchor=tk.W) # 表头靠左
self.table.column(col, width=100, anchor=tk.W, stretch=True) # 自适应宽度
# 配置滚动条
scroll_y.config(command=self.table.yview)
scroll_x.config(command=self.table.xview)
# 布局
self.table.grid(row=0, column=0, sticky="nsew")
scroll_y.grid(row=0, column=1, sticky="ns")
scroll_x.grid(row=1, column=0, sticky="ew")
# 配置网格权重
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
# 状态栏
self.status_var = tk.StringVar()
status_bar = ttk.Label(self.frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 绑定单元格编辑事件
self.table.bind("<Double-1>", self.on_cell_double_click)
# 绑定列宽调整事件
self.table.bind("<Configure>", self.auto_resize_columns)
def auto_resize_columns(self, event=None):
"""优化版列宽调整 - 确保表头完整显示"""
# 获取当前字体
font = tkfont.nametofont("TkDefaultFont")
# 计算列标题宽度
header_widths = {}
for col in self.table["columns"]:
# 获取列标题文本
col_text = self.table.heading(col)["text"]
# 计算列标题宽度 + 额外边距
header_widths[col] = font.measure(col_text) + 30
# 计算单元格内容宽度
for col in self.table["columns"]:
# 初始宽度为列标题宽度
max_width = header_widths[col]
# 遍历所有单元格内容
for item in self.table.get_children():
cell_value = self.table.set(item, col)
cell_width = font.measure(cell_value) + 20
if cell_width > max_width:
max_width = cell_width
# 设置最小和最大宽度限制
min_width = 100 # 最小100像素确保表头可见
if max_width < min_width:
max_width = min_width
elif max_width > 400: # 最大400像素防止过宽
max_width = 400
# 设置列宽
self.table.column(col, width=max_width)
def on_cell_double_click(self, event):
"""修复单元格编辑保存问题"""
self.destroy_cell_editors()
region = self.table.identify("region", event.x, event.y)
if region == "cell":
column = self.table.identify_column(event.x)
item = self.table.focus()
# 获取列索引(0是序号列,不可编辑)
col_index = int(column[1:]) - 1
if col_index == 0: # 序号列不可编辑
return
# 获取列名
col_name = self.column_names[col_index - 1] if col_index > 0 else ""
# 获取当前值
current_value = self.table.item(item, "values")[col_index]
# 创建编辑框
x, y, width, height = self.table.bbox(item, column)
# 特殊列使用下拉框
if col_name in ["type", "data_type_id"]:
# 创建下拉框
values = list(self.type_options) if col_name == "type" else list(self.data_type_id_options)
editor = ttk.Combobox(self.table, values=values, width=width // 8)
editor.set(current_value)
else:
# 普通文本框
editor = ttk.Entry(self.table, width=width // 8)
editor.insert(0, current_value)
editor.select_range(0, tk.END)
editor.place(x=x, y=y, width=width, height=height)
editor.focus()
# 保存编辑器引用
self.cell_editors[item] = (editor, col_index, col_name)
def save_edit(event=None):
"""保存编辑后的值 - 修复数据同步问题"""
new_value = editor.get()
# 更新表格显示
values = list(self.table.item(item, "values"))
values[col_index] = new_value
self.table.item(item, values=values)
editor.destroy()
# 获取原始行索引 - 关键修复
row_index = self.table.index(item)
# 获取实际数据索引
if self.is_searching:
# 搜索状态下使用映射索引
if row_index < len(self.search_results_indices):
original_idx = self.search_results_indices[row_index]
else:
# 处理索引越界情况
return
else:
# 非搜索状态直接使用行索引
original_idx = row_index
# 确保索引在有效范围内
if original_idx < len(self.temp_data):
# 更新主数据源
self.temp_data[original_idx][col_name] = new_value
# 更新显示数据(如果是搜索状态)
if self.is_searching and row_index < len(self.display_data):
self.display_data[row_index][col_name] = new_value
# 重新应用样式
self.apply_cell_style(item, col_index, col_name, new_value)
# 更新选项集合
if col_name == "type" and new_value:
self.type_options.add(new_value)
elif col_name == "data_type_id" and new_value:
self.data_type_id_options.add(new_value)
# 移除编辑器引用
if item in self.cell_editors:
del self.cell_editors[item]
# 绑定事件
editor.bind("<Return>", save_edit)
editor.bind("<FocusOut>", save_edit)
editor.bind("<Escape>", lambda e: editor.destroy())
def destroy_cell_editors(self):
"""关闭所有活动的单元格编辑器"""
for item, (editor, col_index, col_name) in list(self.cell_editors.items()):
editor.destroy()
del self.cell_editors[item]
def apply_cell_style(self, item, col_index, col_name, value):
"""应用单元格样式"""
# 重置样式
self.table.tag_configure("normal", background="")
self.table.item(item, tags=("normal",))
# 检查必填项
if col_name in self.required_columns and not value.strip():
self.table.tag_configure("required_missing", background="#ffcccc")
self.table.item(item, tags=("required_missing",))
# 如果是搜索状态且包含搜索词
if self.is_searching and self.search_text and self.search_text in value.lower():
self.table.tag_configure("search_match", font=("TkDefaultFont", 9, "bold"))
self.table.item(item, tags=("search_match",))
def load_xml(self):
self.destroy_cell_editors()
file_path = filedialog.askopenfilename(
title="选择XML文件",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
tree = ET.parse(file_path)
root = tree.getroot()
self.current_file = file_path
# file_name = os.path.basename(file_path)
# self.main_window.title(f"Data编辑器 - {file_name}")
# 清空表格和数据
for item in self.table.get_children():
self.table.delete(item)
self.original_data = []
self.display_data = []
self.temp_data = []
self.row_counter = 1
self.type_options = set()
self.data_type_id_options = set()
self.is_searching = False
self.search_results_indices = []
self.search_text = ""
# 解析XML数据
for record in root.findall('Record'):
row_data = {}
for col_name in self.column_names:
value = record.get(col_name, '')
row_data[col_name] = value
if col_name == 'type' and value:
self.type_options.add(value)
elif col_name == 'data_type_id' and value:
self.data_type_id_options.add(value)
self.original_data.append(row_data)
self.temp_data = [row.copy() for row in self.original_data]
self.display_data = self.temp_data.copy()
self.populate_table()
self.status_var.set(f"成功加载文件: {file_path}")
self.auto_resize_columns() # 加载后自动调整列宽
except Exception as e:
messagebox.showerror("错误", f"加载XML文件失败:\n{str(e)}")
def populate_table(self, data=None, indices=None):
if data is None:
data = self.display_data
# 清空表格
for item in self.table.get_children():
self.table.delete(item)
# 填充数据 - 使用原始行号作为序号
for row_idx, record in enumerate(data):
# 使用原始行号作为序号
if self.is_searching and row_idx < len(self.search_results_indices):
original_idx = self.search_results_indices[row_idx]
seq_num = original_idx + 1
else:
seq_num = row_idx + 1
values = [str(seq_num)] # 序号列
for col_name in self.column_names:
value = record.get(col_name, '')
values.append(value)
item = self.table.insert("", tk.END, values=values)
# 应用样式
for col_idx, value in enumerate(values):
if col_idx > 0: # 跳过序号列
col_name = self.column_names[col_idx - 1]
self.apply_cell_style(item, col_idx, col_name, value)
# 自动调整列宽
self.auto_resize_columns()
def export_xml(self):
if not self.temp_data:
messagebox.showwarning("警告", "没有数据可导出")
return
# 检查必填字段
empty_fields = []
first_empty_row = None
for idx, record in enumerate(self.temp_data):
for col_name in self.required_columns:
if not record.get(col_name, '').strip():
empty_fields.append(f"行 {idx + 1} 的 '{col_name}' 列")
if first_empty_row is None:
first_empty_row = idx
break
if empty_fields:
error_msg = "以下必填项为空,请填写完整后导出:\n"
error_msg += "\n".join(empty_fields[:10]) # 最多显示10条
if len(empty_fields) > 10:
error_msg += f"\n...(共{len(empty_fields)}个错误)"
# 自动跳转到第一个空行
if first_empty_row is not None:
# 如果是搜索状态,找到对应的显示行
if self.is_searching:
for display_idx, original_idx in enumerate(self.search_results_indices):
if original_idx == first_empty_row:
first_empty_row = display_idx
break
# 跳转到该行
if first_empty_row < len(self.table.get_children()):
item = self.table.get_children()[first_empty_row]
self.table.selection_set(item)
self.table.focus(item)
self.table.see(item)
messagebox.showwarning("导出失败", error_msg)
return
file_path = filedialog.asksaveasfilename(
title="导出XML文件",
defaultextension=".xml",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
# 创建XML结构
root = ET.Element('DataRecords')
for record in self.temp_data:
record_elem = ET.SubElement(root, 'Record')
for key, value in record.items():
record_elem.set(key, value)
# 美化XML输出
rough_string = ET.tostring(root, 'utf-8')
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# 写入文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(pretty_xml)
self.status_var.set(f"成功导出到: {file_path}")
messagebox.showinfo("成功", "文件已成功导出")
except Exception as e:
messagebox.showerror("错误", f"导出XML文件失败:\n{str(e)}")
def add_record(self):
self.destroy_cell_editors()
# 确定插入位置
selected_items = self.table.selection()
insert_pos = len(self.table.get_children())
if selected_items:
# 在第一个选中项之后插入
insert_pos = self.table.index(selected_items[0]) + 1
# 创建新记录
new_record = {col: "" for col in self.column_names}
# 添加到数据 - 修复插入逻辑
if self.is_searching:
# 在搜索状态下,只插入到原始数据源
self.temp_data.insert(insert_pos, new_record)
# 更新显示数据
self.display_data = self.temp_data.copy()
# 重置搜索状态
self.is_searching = False
self.search_results_indices = []
else:
# 非搜索状态直接插入
self.temp_data.insert(insert_pos, new_record)
self.display_data = self.temp_data.copy()
# 更新表格
self.populate_table()
# 获取新添加的行并聚焦
if insert_pos < len(self.table.get_children()):
new_item = self.table.get_children()[insert_pos]
self.table.selection_set(new_item)
self.table.focus(new_item)
self.table.see(new_item) # 确保行在视图中可见
self.status_var.set("已添加新记录")
def delete_record(self):
selected_items = self.table.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择要删除的记录")
return
if not messagebox.askyesno("确认删除", f"确定要删除选中的 {len(selected_items)} 条记录吗?"):
return
# 按索引降序排序以便删除
indices = sorted([self.table.index(item) for item in selected_items], reverse=True)
for idx in indices:
# 从显示数据中删除
if idx < len(self.display_data):
del self.display_data[idx]
if self.is_searching and idx < len(self.search_results_indices):
# 从原始数据中删除
original_idx = self.search_results_indices[idx]
if original_idx < len(self.temp_data):
del self.temp_data[original_idx]
# 更新索引
self.search_results_indices = [i for i in self.search_results_indices if i != original_idx]
self.search_results_indices = [i if i < original_idx else i - 1 for i in self.search_results_indices]
elif not self.is_searching and idx < len(self.temp_data):
del self.temp_data[idx]
# 更新表格
self.populate_table()
self.status_var.set(f"已删除 {len(selected_items)} 条记录")
def search_records(self):
self.destroy_cell_editors()
search_text = self.search_box.get().strip()
selected_column = self.column_combo.get()
if not search_text:
messagebox.showwarning("警告", "请输入搜索内容")
return
self.is_searching = True
self.search_text = search_text.lower()
self.search_results_indices = []
matched_rows = []
# 搜索数据
for original_idx, record in enumerate(self.temp_data):
match_found = False
if selected_column == "所有列":
for col_name in self.column_names:
value = record.get(col_name, '').lower()
if self.search_text in value:
match_found = True
break
else:
value = record.get(selected_column, '').lower()
if self.search_text in value:
match_found = True
if match_found:
self.search_results_indices.append(original_idx)
matched_rows.append(record)
if matched_rows:
self.display_data = matched_rows
self.populate_table()
self.status_var.set(f"找到 {len(matched_rows)} 条匹配记录")
else:
messagebox.showinfo("搜索结果", "未找到匹配记录")
self.status_var.set("未找到匹配记录")
def clear_search(self):
"""清空搜索并刷新数据"""
self.destroy_cell_editors()
# 关键修复:直接使用主数据源,而不是创建副本
self.display_data = self.temp_data
self.is_searching = False
self.search_text = ""
self.search_box.delete(0, tk.END)
self.search_results_indices = []
self.populate_table()
self.status_var.set("已显示所有记录")
# 以下ConfigUpdaterApp类保持不变(为节省空间省略,实际代码中应保留)
class ConfigUpdaterApp:
def __init__(self, root):
self.root = root
self.root.title("ID统一配置文件自动更新工具")
self.root.geometry("1920x1080")
self.root.configure(bg="#f0f0f0")
# 创建样式
self.style = ttk.Style()
self.style.configure("TButton", padding=6, relief="flat", background="#4a7a8c", foreground="blue")
self.style.configure("Treeview", font=("Consolas", 10), rowheight=25)
self.style.configure("Treeview.Heading", font=("Arial", 11, "bold"))
self.style.configure("TNotebook", background="#f0f0f0")
self.style.configure("TNotebook.Tab", padding=(10, 5), font=("Arial", 10, "bold"))
self.style.configure("XMLText", font=("Consolas", 10), background="#f8f8f8")
# 创建主框架
self.create_widgets()
# 初始化数据结构
self.rawdata_template = []
self.rawdata_source = None
self.event_template = []
self.event_source = None
self.current_filter = ""
self.variable_mapping = {}
self.module_mapping = {}
self.init_module()
self.event_categories = []
self.special_mapping_id = {}
self.special_mapping_name = {}
self.init_special_mapping()
def init_module(self):
"""
初始化moduleID与SVEC模板中一致
:return:
"""
self.module_mapping["System"] = "00"
self.module_mapping["CryoPump"] = "00"
self.module_mapping["HeatExchanger"] = "00"
self.module_mapping["EFEM"] = "01"
self.module_mapping["LP1"] = "01"
self.module_mapping["LP2"] = "01"
self.module_mapping["LP3"] = "01"
self.module_mapping["Transfer"] = "02"
self.module_mapping["TC"] = "02"
self.module_mapping["Buffer"] = "03"
self.module_mapping["BF"] = "03"
self.module_mapping["Bufferr"] = "03"
self.module_mapping["LA"] = "11"
self.module_mapping["LB"] = "12"
self.module_mapping["LC"] = "13"
self.module_mapping["LD"] = "14"
self.module_mapping["LAB"] = "15"
self.module_mapping["LCD"] = "16"
self.module_mapping["Ch1"] = "21"
self.module_mapping["Ch2"] = "22"
self.module_mapping["Ch3"] = "23"
self.module_mapping["Ch4"] = "24"
self.module_mapping["Ch5"] = "25"
self.module_mapping["Ch6"] = "26"
self.module_mapping["ChA"] = "27"
self.module_mapping["ChB"] = "28"
self.module_mapping["ChC"] = "29"
self.module_mapping["ChD"] = "30"
self.module_mapping["ChE"] = "31"
self.module_mapping["ChF"] = "32"
def get_module_id(self, module_name):
"""
根据module_name和映射表获取id
:param module_name:
:return:
"""
if module_name not in self.module_mapping:
return None
else:
return self.module_mapping[module_name]
def create_widgets(self):
"""创建界面组件"""
# 创建选项卡
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 第一个选项卡:模板编辑器
self.template_editor = TemplateEditor(self.notebook, self.root)
self.notebook.add(self.template_editor.frame, text="TemplateConfig")
# 第二个选项卡:RawdataConfig
self.rawdata_frame = ttk.Frame(self.notebook)
self.notebook.add(self.rawdata_frame, text="RawDataConfig")
self.create_rawdata_tab(self.rawdata_frame)
# 第三个选项卡:EventConfig
self.event_frame = ttk.Frame(self.notebook)
self.notebook.add(self.event_frame, text="EventConfig")
self.create_event_tab(self.event_frame)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = ttk.Label(self.root, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W, padding=5)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def create_rawdata_tab(self, parent):
"""创建RawdataConfig标签页"""
# 顶部按钮区域
btn_frame = ttk.Frame(parent)
btn_frame.pack(fill=tk.X, padx=10, pady=10)
# RawdataConfig 按钮
ttk.Button(btn_frame, text="1.加载模板文件", command=self.load_rawdata_template).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="2.加载源文件", command=self.load_rawdata_source).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="3.新增ID属性", command=self.add_id_to_rawdata).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="4.筛选模板Variable", command=self.filter_template_vars).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="5.筛选源文件Variable", command=self.filter_source_vars).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="6.对比", command=self.compare_vars).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="7.导出标红内容", command=self.export_highlighted).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="8.填充ID属性", command=self.fill_ids).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="9.删除空ID节点", command=self.filter_empty_id_nodes).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="10.导出文件", command=self.export_rawdata).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="11.拆分文件并导出", command=self.split_export).pack(side=tk.LEFT, padx=5)
# 创建分割面板
paned_window = ttk.PanedWindow(parent, orient=tk.VERTICAL)
paned_window.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# 上半部分 - 模板显示
template_frame = ttk.LabelFrame(paned_window, text="模板显示")
paned_window.add(template_frame, weight=1)
# 模板表格
self.create_template_table(template_frame)
# 下半部分 - 源文件显示(XML原始格式)
source_frame = ttk.LabelFrame(paned_window, text="源文件显示(XML原始格式)")
paned_window.add(source_frame, weight=1)
# XML文本显示区域
self.rawdata_xml_text = scrolledtext.ScrolledText(
source_frame,
wrap=tk.WORD,
font=("Consolas", 10),
bg="#f8f8f8",
padx=10,
pady=10
)
self.rawdata_xml_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.rawdata_xml_text.config(state=tk.DISABLED) # 初始为只读
# 底部筛选区域
filter_frame = ttk.Frame(parent)
filter_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# 模板Variable筛选
ttk.Label(filter_frame, text="模板Variable筛选:").pack(side=tk.LEFT, padx=(0, 5))
self.template_filter_var = tk.StringVar()
self.template_filter_box = tk.Listbox(filter_frame, listvariable=self.template_filter_var,
height=6, width=30, selectmode=tk.EXTENDED)
self.template_filter_box.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.BOTH, expand=True)
# 源文件Variable筛选
ttk.Label(filter_frame, text="源文件Variable筛选:").pack(side=tk.LEFT, padx=(20, 5))
self.source_filter_var = tk.StringVar()
self.source_filter_box = tk.Listbox(filter_frame, listvariable=self.source_filter_var,
height=6, width=30, selectmode=tk.EXTENDED)
self.source_filter_box.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.BOTH, expand=True)
# 对比结果区域
self.compare_result = tk.Text(filter_frame, height=6, width=30, state=tk.DISABLED)
self.compare_result.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.BOTH, expand=True)
def create_template_table(self, parent):
"""创建Rawdata模板表格"""
# 创建滚动条
scroll_y = ttk.Scrollbar(parent, orient=tk.VERTICAL)
scroll_x = ttk.Scrollbar(parent, orient=tk.HORIZONTAL)
# 创建Treeview表格
columns = ("variable", "id", "mapping")
self.template_table = ttk.Treeview(
parent,
columns=columns,
show="headings",
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set,
selectmode="extended"
)
# 配置列标题
self.template_table.heading("variable", text="Variable", anchor=tk.W)
self.template_table.heading("id", text="ID", anchor=tk.W)
self.template_table.heading("mapping", text="Mapping", anchor=tk.W)
# 配置列宽度
self.template_table.column("variable", width=200, anchor=tk.W)
self.template_table.column("id", width=100, anchor=tk.W)
self.template_table.column("mapping", width=300, anchor=tk.W)
# 配置滚动条
scroll_y.config(command=self.template_table.yview)
scroll_x.config(command=self.template_table.xview)
# 布局
self.template_table.grid(row=0, column=0, sticky="nsew")
scroll_y.grid(row=0, column=1, sticky="ns")
scroll_x.grid(row=1, column=0, sticky="ew")
# 配置网格权重
parent.grid_rowconfigure(0, weight=1)
parent.grid_columnconfigure(0, weight=1)
def create_event_tab(self, parent):
"""创建EventConfig标签页"""
# 顶部按钮区域
btn_frame = ttk.Frame(parent)
btn_frame.pack(fill=tk.X, padx=10, pady=10)
# EventConfig 按钮
ttk.Button(btn_frame, text="1.加载模板文件", command=self.load_event_template).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="2.加载源文件", command=self.load_event_source).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="3.新增ID属性", command=self.add_id_to_event).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="4.填充ID属性", command=self.fill_event_attributes).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="5.导出文件", command=self.export_event).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="6.拆分文件并导出", command=self.split_export_event).pack(side=tk.LEFT, padx=5)
# 创建分割面板
paned_window = ttk.PanedWindow(parent, orient=tk.VERTICAL)
paned_window.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# 上半部分 - 模板显示
template_frame = ttk.LabelFrame(paned_window, text="模板显示")
paned_window.add(template_frame, weight=1)
# 模板表格
self.create_event_template_table(template_frame)
# 下半部分 - 源文件显示(XML原始格式)
source_frame = ttk.LabelFrame(paned_window, text="源文件显示(XML原始格式)")
paned_window.add(source_frame, weight=1)
# XML文本显示区域
self.event_xml_text = scrolledtext.ScrolledText(
source_frame,
wrap=tk.WORD,
font=("Consolas", 10),
bg="#f8f8f8",
padx=10,
pady=10
)
self.event_xml_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.event_xml_text.config(state=tk.DISABLED) # 初始为只读
def create_event_template_table(self, parent):
"""创建Event模板表格"""
# 创建滚动条
scroll_y = ttk.Scrollbar(parent, orient=tk.VERTICAL)
scroll_x = ttk.Scrollbar(parent, orient=tk.HORIZONTAL)
# 创建Treeview表格
columns = ("name", "id", "type", "mapping")
self.event_template_table = ttk.Treeview(
parent,
columns=columns,
show="headings",
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set,
selectmode="extended"
)
# 配置列标题
self.event_template_table.heading("name", text="Name", anchor=tk.W)
self.event_template_table.heading("id", text="ID", anchor=tk.W)
self.event_template_table.heading("type", text="Type", anchor=tk.W)
self.event_template_table.heading("mapping", text="Maping", anchor=tk.W)
# 配置列宽度
self.event_template_table.column("name", width=100, anchor=tk.W)
self.event_template_table.column("id", width=100, anchor=tk.W)
self.event_template_table.column("type", width=100, anchor=tk.W)
self.event_template_table.column("mapping", width=100, anchor=tk.W)
# 配置滚动条
scroll_y.config(command=self.event_template_table.yview)
scroll_x.config(command=self.event_template_table.xview)
# 布局
self.event_template_table.grid(row=0, column=0, sticky="nsew")
scroll_y.grid(row=0, column=1, sticky="ns")
scroll_x.grid(row=1, column=0, sticky="ew")
# 配置网格权重
parent.grid_rowconfigure(0, weight=1)
parent.grid_columnconfigure(0, weight=1)
def display_xml_content(self, xml_tree, text_widget):
"""在文本框中显示格式化的XML内容"""
try:
# 将XML树转换为字符串
xml_str = ET.tostring(xml_tree.getroot(), encoding='utf-8').decode('utf-8')
# 使用minidom进行格式化
dom = minidom.parseString(xml_str)
pretty_xml = dom.toprettyxml(indent=" ")
# 移除多余的换行
pretty_xml = "\n".join([line for line in pretty_xml.split("\n") if line.strip()])
# 更新文本框
text_widget.config(state=tk.NORMAL)
text_widget.delete(1.0, tk.END)
text_widget.insert(tk.END, pretty_xml)
text_widget.config(state=tk.DISABLED)
# 高亮空ID节点
self.highlight_empty_id_nodes(text_widget, pretty_xml)
except Exception as e:
messagebox.showerror("显示错误", f"格式化XML显示失败: {str(e)}")
def highlight_empty_id_nodes(self, text_widget, xml_content):
"""高亮显示id为空的data节点"""
if not xml_content:
return
# 查找所有id属性为空的data节点
pattern = r'(<data\b[^>]*\bid="")'
matches = re.finditer(pattern, xml_content)
# 设置高亮样式
text_widget.tag_configure("empty_id", background="#ffe6e6")
# 应用高亮
for match in matches:
start_index = f"1.0 + {match.start()} chars"
end_index = f"1.0 + {match.end()} chars"
text_widget.tag_add("empty_id", start_index, end_index)
# ================ RawdataConfig 功能 ================
def load_rawdata_template(self):
"""加载Rawdata模板文件(.txt)"""
file_path = filedialog.askopenfilename(
title="选择Rawdata模板文件",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
)
if not file_path:
return
try:
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
self.rawdata_template = []
self.variable_mapping = {}
# 清空表格
for item in self.template_table.get_children():
self.template_table.delete(item)
# 解析模板文件
for line in lines:
line = line.strip()
if not line:
continue
# 分割行数据
parts = line.split()
if len(parts) < 2:
continue
# 提取前四列
variable = parts[0]
id = parts[1] if len(parts) > 1 else ""
mapping_info = parts[2] if len(parts) > 2 else None
self.rawdata_template.append({
"variable": variable,
"id": id,
"mapping": mapping_info
})
if mapping_info:
mapping_parts = mapping_info.split(';')
for map in mapping_parts:
if map in self.variable_mapping:
messagebox.showwarning("信息有误", "请确认模板文件中mapping列是否有重复信息" + map)
return
else:
self.variable_mapping[map] = variable
# 添加到表格
self.template_table.insert("", tk.END, values=(variable, id, mapping_info))
self.status_var.set(f"已加载Rawdata模板: {os.path.basename(file_path)} - {len(self.rawdata_template)} 条记录")
except Exception as e:
messagebox.showerror("加载错误", f"加载Rawdata模板文件失败: {str(e)}")
def load_rawdata_source(self):
"""加载Rawdata源文件(.xml)"""
file_path = filedialog.askopenfilename(
title="选择Rawdata源文件",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
self.rawdata_tree = ET.parse(file_path)
self.rawdata_root = self.rawdata_tree.getroot()
self.rawdata_source = self.rawdata_tree
# 在文本框中显示XML内容
self.display_xml_content(self.rawdata_tree, self.rawdata_xml_text)
self.status_var.set(f"已加载Rawdata源文件: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("加载错误", f"加载Rawdata源文件失败: {str(e)}")
def add_id_to_rawdata(self):
"""为Rawdata源文件添加ID属性"""
if not self.rawdata_source:
messagebox.showwarning("无数据", "请先加载Rawdata源文件")
return
# 添加ID属性
count = 0
for data in self.rawdata_root.findall('.//data'):
if 'id' not in data.attrib:
# 在属性开头位置添加id属性
new_attrib = OrderedDict()
new_attrib['id'] = ''
for key, value in data.attrib.items():
new_attrib[key] = value
data.attrib.clear()
for key, value in new_attrib.items():
data.set(key, value)
count += 1
# 更新XML显示
self.display_xml_content(self.rawdata_tree, self.rawdata_xml_text)
self.status_var.set(f"已为{count}个data标签添加id属性")
def filter_template_vars(self):
"""筛选模板Variable"""
if not self.rawdata_template:
messagebox.showwarning("无数据", "请先加载Rawdata模板文件")
return
# 提取Variable后半部分并去重
var_parts = set()
for item in self.rawdata_template:
variable = item["variable"]
parts = variable.split('_', 1) # 只分割第一个下划线
if len(parts) > 1:
var_parts.add(parts[1])
# 更新列表
self.template_filter_var.set(tuple(sorted(var_parts)))
self.status_var.set(f"已筛选出 {len(var_parts)} 个模板Variable")
def filter_source_vars(self):
"""筛选源文件Variable"""
if not self.rawdata_source:
messagebox.showwarning("无数据", "请先加载Rawdata源文件")
return
# 提取Variable后半部分并去重
var_parts = set()
for data in self.rawdata_root.findall('.//data'):
variable = data.get('variable', '')
if variable:
parts = variable.split('_', 1) # 只分割第一个下划线
if len(parts) > 1:
var_parts.add(parts[1])
# 更新列表
self.source_filter_var.set(tuple(sorted(var_parts)))
self.status_var.set(f"已筛选出 {len(var_parts)} 个源文件Variable")
def compare_vars(self):
"""对比模板和源文件Variable"""
template_vars = self.template_filter_box.get(0, tk.END)
source_vars = self.source_filter_box.get(0, tk.END)
template_mapping_vars = self.variable_mapping.values();
if not template_vars or not source_vars:
messagebox.showwarning("无数据", "请先筛选模板和源文件Variable")
return
# 清空对比结果
self.compare_result.config(state=tk.NORMAL)
self.compare_result.delete(1.0, tk.END)
# 查找源文件中有但模板中没有的Variable
missing_in_template = set(source_vars) - set(template_vars) - set(template_mapping_vars)
# 更新源文件列表,标红缺失项
self.source_filter_box.delete(0, tk.END)
for var in sorted(source_vars):
self.source_filter_box.insert(tk.END, var)
if var in missing_in_template:
self.source_filter_box.itemconfig(tk.END, fg='red')
# 显示对比结果
self.compare_result.insert(tk.END, "对比结果:\n")
self.compare_result.insert(tk.END, f"模板Variable数量: {len(template_vars)}\n")
self.compare_result.insert(tk.END, f"源文件Variable数量: {len(source_vars)}\n")
self.compare_result.insert(tk.END, f"源文件中有但模板中缺失的数量: {len(missing_in_template)}\n\n")
if missing_in_template:
self.compare_result.insert(tk.END, "缺失的Variable:\n")
for var in sorted(missing_in_template):
self.compare_result.insert(tk.END, f" - {var}\n")
self.compare_result.config(state=tk.DISABLED)
self.status_var.set(f"对比完成: 发现 {len(missing_in_template)} 个缺失项")
def export_highlighted(self):
"""导出标红内容"""
# 获取标红的项
highlighted_items = []
for i in range(self.source_filter_box.size()):
if self.source_filter_box.itemcget(i, "fg") == "red":
highlighted_items.append(self.source_filter_box.get(i))
if not highlighted_items:
messagebox.showinfo("无数据", "没有标红的项可导出")
return
# 选择保存位置
file_path = filedialog.asksaveasfilename(
title="保存标红内容",
defaultextension=".txt",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
)
if not file_path:
return
# 写入文件
try:
with open(file_path, 'w', encoding='utf-8') as file:
file.write("标红的Variable项:\n")
for item in highlighted_items:
file.write(f"{item}\n")
self.status_var.set(f"已导出 {len(highlighted_items)} 个标红项到 {os.path.basename(file_path)}")
messagebox.showinfo("导出成功", "标红项已成功导出")
except Exception as e:
messagebox.showerror("导出错误", f"导出文件失败: {str(e)}")
def init_special_mapping(self):
self.special_mapping_id['LP1_BypassReadID'] = '201210000'
self.special_mapping_name['LP1_BypassReadID'] = 'EFEM_LP1BypassReadID'
self.special_mapping_id['LP1_LoadPortTout'] = '201210001'
self.special_mapping_name['LP1_LoadPortTout'] = 'EFEM_LP1LoadPortTout'
self.special_mapping_id['LP2_BypassReadID'] = '201211000'
self.special_mapping_name['LP2_BypassReadID'] = 'EFEM_LP2BypassReadID'
self.special_mapping_id['LP2_LoadPortTout'] = '201211001'
self.special_mapping_name['LP2_LoadPortTout'] = 'EFEM_LP2LoadPortTout'
self.special_mapping_id['LP3_BypassReadID'] = '201212000'
self.special_mapping_name['LP3_BypassReadID'] = 'EFEM_LP3BypassReadID'
self.special_mapping_id['LP3_LoadPortTout'] = '201212001'
self.special_mapping_name['LP3_LoadPortTout'] = 'EFEM_LP3LoadPortTout'
self.special_mapping_id['CJobSubstProcStatusList'] = '100000004'
self.special_mapping_name['CJobSubstProcStatusList'] = 'CJobSubstProcStatusList'
self.special_mapping_id['ToolState'] = '100000000'
self.special_mapping_name['ToolState'] = 'ToolState'
self.special_mapping_id['Aligner_AngleLA'] = '201209000'
self.special_mapping_name['Aligner_AngleLA'] = 'EFEM_AlignerAngleLA'
self.special_mapping_id['Aligner_AngleLB'] = '201209001'
self.special_mapping_name['Aligner_AngleLB'] = 'EFEM_AlignerAngleLB'
self.special_mapping_id['Aligner_AngleX'] = '201209002'
self.special_mapping_name['Aligner_AngleX'] = 'EFEM_AlignerAngleX'
self.special_mapping_id['Aligner_NotchSupport'] = '201209003'
self.special_mapping_name['Aligner_NotchSupport'] = 'EFEM_AlignerNotchSupport'
self.special_mapping_id['Aligner_Bypass'] = '201209004'
self.special_mapping_name['Aligner_Bypass'] = 'EFEM_AlignerBypass'
self.special_mapping_id['CryoPump_CompressorPressure'] = '101122000'
self.special_mapping_name['CryoPump_CompressorPressure'] = 'System_CryoPumpCompressorPressure'
self.special_mapping_id['CryoPump_CompressorDiffPressure'] = '101122001'
self.special_mapping_name['CryoPump_CompressorDiffPressure'] = 'System_CryoPumpCompressorDiffPressure'
self.special_mapping_id['CryoPump_CompressorSupplyPressure'] = '101122002'
self.special_mapping_name['CryoPump_CompressorSupplyPressure'] = 'System_CryoPumpCompressorSupplyPressure'
self.special_mapping_id['HeatExchanger_RS'] = '100409000'
self.special_mapping_name['HeatExchanger_RS'] = 'System_HeatExchangerRS'
def get_id(self, module_name, var):
if var in self.variable_mapping: # 来自mapping,含有前缀CHXXX
temp_var = self.variable_mapping[var]
variable = temp_var.replace('CHXXX', module_name) # Ch1_LotID
else:
temp_var = "CHXXX_" + var # 来自配置,不含有前缀CHXXX
variable = module_name + '_' + var # Ch1_LotID
module_id = self.get_module_id(module_name)
temp_id = self.get_id_by_variable(temp_var)
if temp_id and module_id:
if 'XX' in temp_id:
id = temp_id.replace('XX', str(module_id))
return variable.replace('CHXXX', module_name), id
else:
return None, None
def get_id_by_variable(self, variable):
for item in self.rawdata_template:
if item['variable'] == variable:
return item['id']
def fill_ids(self):
"""填充ID属性"""
if not self.rawdata_source:
messagebox.showwarning("无数据", "请先加载Rawdata源文件")
return
if not self.rawdata_root:
messagebox.showwarning("无数据", "请先加载Rawdata源文件")
return
if not self.rawdata_template:
messagebox.showwarning("无数据", "请先加载模板文件")
return
fill_count = 0
not_exist = []
not_exist_all = []
for data_elem in self.rawdata_tree.findall('.//data'):
variable = data_elem.get('variable')
parts = variable.split('_', 1)
if len(parts) < 2 or variable in self.special_mapping_id:
if variable in self.special_mapping_id and variable in self.special_mapping_name:
temp_var = self.special_mapping_name[variable]
temp_id = self.special_mapping_id[variable]
else:
continue
else:
module = parts[0]
var = parts[1]
temp_var, temp_id = self.get_id(module, var)
if temp_var and temp_id:
data_elem.set('variable', temp_var)
data_elem.set('id', temp_id)
fill_count += 1
else:
if temp_id is None and var not in not_exist:
not_exist.append(var)
if temp_id is None:
not_exist_all.append(variable)
# 更新XML显示
self.display_xml_content(self.rawdata_tree, self.rawdata_xml_text)
self.status_var.set(f"已为{fill_count}个data标签添加id属性,剩余{len(not_exist)}无对应值。总共剩余{len(not_exist_all)}个未设置")
def filter_empty_id_nodes(self):
"""删除id属性为空的data节点并刷新显示"""
if not self.rawdata_source:
messagebox.showwarning("无数据", "请先加载Rawdata源文件")
return
# 获取XML根节点
root = self.rawdata_tree.getroot()
# 查找所有id属性为空的data节点
empty_id_nodes = []
for data_node in root.iter('data'):
if data_node.get('id', '') == '':
empty_id_nodes.append(data_node)
if not empty_id_nodes:
messagebox.showinfo("无操作", "未找到id为空的data节点")
return
# 删除这些节点
for node in empty_id_nodes:
parent = node.find('..')
if parent is not None:
parent.remove(node)
# 刷新XML显示
self.display_xml_content(self.rawdata_tree, self.rawdata_xml_text)
# 更新状态
self.status_var.set(f"已删除 {len(empty_id_nodes)} 个id为空的data节点")
def export_rawdata(self):
"""导出Rawdata源文件"""
if not self.rawdata_source:
messagebox.showwarning("无数据", "没有可导出的数据")
return
file_path = filedialog.asksaveasfilename(
title="保存Rawdata文件",
defaultextension=".xml",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
self.rawdata_tree.write(file_path, encoding='utf-8', xml_declaration=True)
self.status_var.set(f"Rawdata文件已成功导出到: {os.path.basename(file_path)}")
messagebox.showinfo("导出成功", "Rawdata配置文件已成功导出")
except Exception as e:
messagebox.showerror("导出错误", f"导出文件失败: {str(e)}")
def split_export(self):
if not self.rawdata_source:
messagebox.showwarning("无数据", "没有可拆分的数据")
return
# 创建分组字典,前缀为data节点列表
groups = defaultdict(list)
# 遍历所有data节点
for data in self.rawdata_root.findall('.//data'):
variable = data.get('variable', '')
if '_' in variable:
prefix = variable.split('_', 1)[0]
groups[prefix].append(data)
# 为每个分组创建新的xml文件
for prefix, data in groups.items():
# 创建新根节点(复制原始根节点标签和属性)
new_root = ET.Element(self.rawdata_root.tag, attrib=self.rawdata_root.attrib)
# 复制原始根节点的命名空间
for ns, uri in self.rawdata_root.nsmap.items():
if ns:
ET.register_namespace(ns, uri)
# 添加原始非 data 节点
for child in self.rawdata_root:
if child.tag != 'data':
new_root.append(child)
# 添加分组中的 data 节点
for node in data:
new_root.append(node)
# 创建 XML 树并写入文件
new_tree = ET.ElementTree(new_root)
filename = f"RawData_{prefix}.xml"
new_tree.write(filename,
encoding='utf-8',
xml_declaration=True)
return
# ================ EventConfig 功能 ================
def load_event_template(self):
"""加载Event模板文件(.txt)"""
file_path = filedialog.askopenfilename(
title="选择Event模板文件",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
)
if not file_path:
return
try:
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
self.event_template = []
self.event_mapping = {}
# 清空表格
for item in self.event_template_table.get_children():
self.event_template_table.delete(item)
# 解析模板文件
for line in lines:
line = line.strip()
if not line:
continue
# 分割行数据
parts = line.split()
if len(parts) < 2:
continue
# 提取前两列
name = parts[0]
id = parts[1] if len(parts) > 1 else ""
mapping = parts[2] if len(parts) > 2 else None
# 提取type
name_parts = name.split('_', 1)
if len(name_parts) < 2:
continue
type = name_parts[1]
self.event_categories.append(type)
if mapping:
mapping_parts = mapping.split(';')
for map in mapping_parts:
if map in self.event_mapping:
messagebox.showwarning("数据有误", "请确认Event模板数据中mapping列是否有重复数据")
else:
self.event_mapping[map] = name
self.event_template.append({
"name": name,
"id": id,
"type": type,
"mapping": mapping
})
# 添加到表格
self.event_template_table.insert("", tk.END, values=(name, id, type, mapping))
self.event_categories = sorted(self.event_categories, key=len, reverse=True)
self.status_var.set(f"已加载Event模板: {os.path.basename(file_path)} - {len(self.event_template)} 条记录")
except Exception as e:
messagebox.showerror("加载错误", f"加载Event模板文件失败: {str(e)}")
def load_event_source(self):
"""加载Event源文件(.xml)"""
file_path = filedialog.askopenfilename(
title="选择Event源文件",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
self.event_tree = ET.parse(file_path)
self.event_root = self.event_tree.getroot()
self.event_source = self.event_tree
# 在文本框中显示XML内容
self.display_xml_content(self.event_tree, self.event_xml_text)
self.status_var.set(f"已加载Event源文件: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("加载错误", f"加载Event源文件失败: {str(e)}")
def add_id_to_event(self):
"""为Event源文件添加ID属性"""
if not self.event_source:
messagebox.showwarning("无数据", "请先加载Event源文件")
return
# 添加ID属性
count = 0
for event in self.event_root.findall('.//event'):
if 'id' not in event.attrib:
# 在属性开头位置添加id属性
new_attrib = OrderedDict()
new_attrib['id'] = ''
for key, value in event.attrib.items():
new_attrib[key] = value
event.attrib.clear()
for key, value in new_attrib.items():
event.set(key, value)
count += 1
# 更新XML显示
self.display_xml_content(self.event_tree, self.event_xml_text)
self.status_var.set(f"已为{count}个event元素添加ID属性")
def get_id_from_event_template(self, event):
for item in self.event_template:
if item['name'] == event:
return item['id']
def get_id_event(self, module, cat):
if cat in self.event_mapping:
temp_name = self.event_mapping[cat]
else:
temp_name = "CHXXX_" + cat
module_id = self.get_module_id(module)
temp_id = self.get_id_from_event_template(temp_name)
name = module + '_' + cat
if temp_id and module_id:
id = temp_id.replace('XX', str(module_id))
return name, id
else:
return None, None
def split_by_type(self, event_name):
if not self.event_categories:
return
for cat in self.event_categories:
if event_name.endswith(cat):
module = event_name[:-len(cat)]
return module, cat
def fill_event_attributes(self):
"""填充Event属性"""
if not self.event_source:
messagebox.showwarning("无数据", "请先加载EventConfig源文件")
return
if not self.event_template:
messagebox.showwarning("无数据", "请先加载EventConfig模板文件")
return
fill_count = 0
not_exist = []
not_exist_all = []
for data_elem in self.event_tree.findall('.//event'):
name = data_elem.get('name')
module, cat = self.split_by_type(name)
temp_name, temp_id = self.get_id_event(module, cat)
if temp_name and temp_id:
data_elem.set('name', temp_name)
data_elem.set('id', temp_id)
fill_count += 1
else:
if temp_id is None and cat not in not_exist:
not_exist.append(cat)
if temp_id is None:
not_exist_all.append(name)
self.display_xml_content(self.event_tree, self.event_xml_text)
self.status_var.set(f"已为{fill_count}个event补充id属性,剩余{len(not_exist)}个不在模板中,总共剩余{len(not_exist_all)}个")
def export_event(self):
"""导出Event源文件"""
if not self.event_source:
messagebox.showwarning("无数据", "没有可导出的数据")
return
file_path = filedialog.asksaveasfilename(
title="保存Event文件",
defaultextension=".xml",
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
)
if not file_path:
return
try:
self.event_tree.write(file_path, encoding='utf-8', xml_declaration=True)
self.status_var.set(f"Event文件已成功导出到: {os.path.basename(file_path)}")
messagebox.showinfo("导出成功", "Event配置文件已成功导出")
except Exception as e:
messagebox.showerror("导出错误", f"导出文件失败: {str(e)}")
def split_export_event(self):
return
if __name__ == "__main__":
root = tk.Tk()
app = ConfigUpdaterApp(root)
root.mainloop()为这个程序编写readme文档进行说明
最新发布