组态王导出的点表 与 excel 格式的相互转换
组态王 (KingView) 支持点表的导入导出,其格式为xml.
这种格式适合编程读写,但并不方便人工阅读.习惯用excel编写点表,于是,用python实现了将组态王的xml格式的点表转换为csv格式,可在excel中编辑,增删改查,然后再转换为xml格式,导回给组态王.
代码分2部分,分别为 xml-->csv 以及 csv-->xml

import os import re import tkinter as tk from tkinter import filedialog import datetime # 读入组态王导出的点表文件 root = tk.Tk() root.withdraw() 组态王导出的点表文件路径 = filedialog.askopenfilename() 组态王导出的点表文件路径 = 组态王导出的点表文件路径.replace('/',chr(92)) 输入文件 = open(组态王导出的点表文件路径,'r',encoding = "utf-16") 输入点表内容 = 输入文件.read() 输入文件.close() 布尔量标题栏 = '''变量名 变量类型 初始值 保存数值 保存参数 连接设备 地址 数据类型 读写属性 采集频率 允许DDE访问 报警组 优先级 开关量报警 报警类型 关文本 开文本 开关文本 关开文本 扩展区1 扩展区2 历史记录模式 每_分 生成事件 安全区 组路径 描述 数据签名 ''' 整数部分题栏 = '''变量名 变量类型 变化灵敏度 初始值 最小值 最大值 最小原始值 最大原始值 保存数值 保存参数 连接设备 寄存器 数据类型 读写属性 采集频率 转换方式 高级 查表 累计方式 累计最小值 累计最大值 允许DDE访问 报警组名 优先级 低低选 低低限 低低文 低选 低限 低文 高选 高限 高文 高高选 高高限 高高文 死区选 死区限 报警延迟选 报警延迟值 变化率选 变化率值 变化率单位 偏差值 小偏差选 小偏差值 大偏差选 大偏差值 死区选 死区值 扩展1 扩展2 记录方式 变化灵敏 定时间隔 生成事件 安全区 组路径 描述 数据签名 ''' 浮点数题栏 = '''变量名 变量类型 变化灵敏度 初始值 最小值 最大值 最小原始值 最大原始值 保存数值 保存参数 连接设备 寄存器 数据类型 读写属性 采集频率 转换方式 高级 查表 累计方式 累计最小值 累计最大值 允许DDE访问 报警组名 优先级 低低选 低低限 低低文 低选 低限 低文 高选 高限 高文 高高选 高高限 高高文 死区选 死区限 报警延迟选 报警延迟值 变化率选 变化率值 变化率单位 偏差值 小偏差选 小偏差值 大偏差选 大偏差值 死区选 死区值 扩展1 扩展2 记录方式 变化灵敏 定时间隔 生成事件 安全区 组路径 描述 数据签名 ''' # 布尔量部分 文件内容 = "" 布尔量部分 = re.search(r'<IOBool TagNumber[\s\S]+?</IOBool>',输入点表内容) if 布尔量部分: 布尔量部分 = 布尔量部分.group() 文件内容 = 布尔量标题栏 变量描述列表 = re.findall(r'<Tag VarName.+?/>',布尔量部分) for 变量描述 in 变量描述列表: 单个变量参数列表 = re.findall(r'".*?"',变量描述) for 单个变量参数 in 单个变量参数列表: 单个变量参数 = 单个变量参数.replace('"','') 文件内容 += 单个变量参数 + '\t' 文件内容 += '\n' 输出文件路径 = re.search(r'^.+\.',组态王导出的点表文件路径).group() 输出文件路径 = re.sub(r'\.$','_数字量_转换为电子表格' + datetime.datetime.now().strftime('%y_%m_%d_%H%M%S') + '.csv',输出文件路径) 输出文件 = open(输出文件路径,'w',encoding = "utf-16") 输出文件.write(文件内容) 输出文件.close() # 整数部分 文件内容 = "" 整数部分 = re.search(r'<IOInt TagNumber[\s\S]+?</IOInt>',输入点表内容) if 整数部分: 整数部分 = 整数部分.group() 文件内容 = 整数部分题栏 变量描述列表 = re.findall(r'<Tag VarName.+?/>',整数部分) for 变量描述 in 变量描述列表: 单个变量参数列表 = re.findall(r'".*?"',变量描述) for 单个变量参数 in 单个变量参数列表: 单个变量参数 = 单个变量参数.replace('"','') 文件内容 += 单个变量参数 + '\t' 文件内容 += '\n' 输出文件路径 = re.search(r'^.+\.',组态王导出的点表文件路径).group() 输出文件路径 = re.sub(r'\.$','_整数_转换为电子表格' + datetime.datetime.now().strftime('%y_%m_%d_%H%M%S') + '.csv',输出文件路径) 输出文件 = open(输出文件路径,'w',encoding = "utf-16") 输出文件.write(文件内容) 输出文件.close() # 浮点数部分 文件内容 = "" 浮点数部分 = re.search(r'<IOFloat TagNumber[\s\S]+?</IOFloat>',输入点表内容) if 浮点数部分: 浮点数部分 = 浮点数部分.group() 文件内容 = 布尔量标题栏 变量描述列表 = re.findall(r'<Tag VarName.+?/>',浮点数部分) for 变量描述 in 变量描述列表: 单个变量参数列表 = re.findall(r'".*?"',变量描述) for 单个变量参数 in 单个变量参数列表: 单个变量参数 = 单个变量参数.replace('"','') 文件内容 += 单个变量参数 + '\t' 文件内容 += '\n' 输出文件路径 = re.search(r'^.+\.',组态王导出的点表文件路径).group() 输出文件路径 = re.sub(r'\.$','_浮点数_转换为电子表格' + datetime.datetime.now().strftime('%y_%m_%d_%H%M%S') + '.csv',输出文件路径) 输出文件 = open(输出文件路径,'w',encoding = "utf-16") 输出文件.write(文件内容) 输出文件.close()

import os import re import tkinter as tk from tkinter import filedialog import datetime # 读入准备反转为组态王点表的csv文件 root = tk.Tk() root.withdraw() 组态王导出的点表文件路径 = filedialog.askopenfilename() 组态王导出的点表文件路径 = 组态王导出的点表文件路径.replace('/',chr(92)) 输入文件 = open(组态王导出的点表文件路径,'r',encoding = "utf-16") # 读数据 数据列表 = [] for 行文本 in 输入文件: 数据列表.append(行文本.replace('\n','').split('\t')) 输入文件.close() 文件头 = r'''<?xml version="1.0" encoding="UTF-16" standalone="yes"?> <TagDefine Version="7.5"> <BaseTag TagNumber="变量数目"> ''' 文件尾 = ''' </BaseTag> </TagDefine> ''' 数字量变量标签样本 = '''<Tag VarName="D风4开水泵请求" TagType="IOBool" InitialValue="0" SaveData="N" SaveParameter="N" DeviceName="JEC" RegisterName="V2019.3" DataType="Bit" RWAttribute="读写" CollectFrequency="1000" DDEAccess="N" AlarmGroup="RootNode" Priority="1" AlarmEnabled="N" AlarmType="0" OffAlarmText="关" OnAlarmText="开" OnOffAlarmText="开到关" OffOnAlarmText="关到开" ExternField1="" ExternField2="" HistoryRecordWay="0" RecordInterval="0" ProduceOperateEvent="N" SecurityArea="无" GroupPath="" Comment="" TagSignature="-112"/>''' 整数变量标签样本 = '''<Tag VarName="J高温制冷模式统计" TagType="IOInt" ChangeSensitivity="0" InitialValue="0" MinValue="-999999999" MaxValue="999999999" MinOriginalValue="-999999999" MaxOriginalValue="999999999" SaveData="N" SaveParameter="N" DeviceName="JEC" RegisterName="V206" DataType="SHORT" RWAttribute="读写" CollectFrequency="1000" CovertWay="1" AdvanceConvertWay="0" NonLinearTableName="" AddUpWay="" MinAccumulateValue="" MaxAccumulateValue="" DDEAccess="N" AlarmGroup="RootNode" Priority="1" LoLoEnabled="N" LoLoLimit="0" LoLoAlarmText="低低" LoEnabled="N" LoLimit="0" LoAlarmText="低" HiEnabled="N" HiLimit="0" HiAlarmText="高" HiHiEnabled="N" HiHiLimit="0" HiHiAlarmText="高高" LimitDeadEnabled="N" LimitDeadZone="0" AlarmDelayEnabled="N" AlarmDelay="0" ChangeRateEnabled="N" ChangeRate="0" ChangeRateUnit="0" OffsetTargetValue="0" SmallOffsetEnabled="N" SmallOffset="0" BigOffsetEnabled="N" BigOffset="0" OffsetDeadZoneEnabled="N" OffsetDeadZone="0" ExternField1="" ExternField2="" HistoryRecordWay="0" HistoryRecordSensitivity="1" RecordInterval="0" ProduceOperateEvent="N" SecurityArea="无" GroupPath="" Comment="" TagSignature="-112"/>''' 浮点数变量标签样本 = '''<Tag VarName="YA吸气压力上限" TagType="IOFloat" ChangeSensitivity="0" InitialValue="0" MinValue="0" MaxValue="999999999" MinOriginalValue="0" MaxOriginalValue="999999999" SaveData="N" SaveParameter="N" DeviceName="压A" RegisterName="V520" DataType="FLOAT" RWAttribute="读写" CollectFrequency="1000" CovertWay="1" AdvanceConvertWay="0" NonLinearTableName="" AddUpWay="" MinAccumulateValue="" MaxAccumulateValue="" DDEAccess="N" AlarmGroup="RootNode" Priority="1" LoLoEnabled="N" LoLoLimit="0" LoLoAlarmText="低低" LoEnabled="N" LoLimit="0" LoAlarmText="低" HiEnabled="N" HiLimit="0" HiAlarmText="高" HiHiEnabled="N" HiHiLimit="0" HiHiAlarmText="高高" LimitDeadEnabled="N" LimitDeadZone="0" AlarmDelayEnabled="N" AlarmDelay="0" ChangeRateEnabled="N" ChangeRate="0" ChangeRateUnit="0" OffsetTargetValue="0" SmallOffsetEnabled="N" SmallOffset="0" BigOffsetEnabled="N" BigOffset="0" OffsetDeadZoneEnabled="N" OffsetDeadZone="0" ExternField1="" ExternField2="" HistoryRecordWay="2" HistoryRecordSensitivity="0" RecordInterval="1" ProduceOperateEvent="N" SecurityArea="无" GroupPath="" Comment="" TagSignature="-112"/>''' # 提取数字量变量属性 数字量变量属性初步提取列表 = re.findall(r' .+?=',数字量变量标签样本) 数字量变量属性列表 = [] for 属性 in 数字量变量属性初步提取列表: 数字量变量属性列表.append(属性.replace(' ','').replace('=','')) # 提取整数变量属性 整数变量属性初步提取列表 = re.findall(r' .+?=',整数变量标签样本) 整数变量属性列表 = [] for 属性 in 整数变量属性初步提取列表: 整数变量属性列表.append(属性.replace(' ','').replace('=','')) # 提取浮点数变量属性 浮点数变量属性初步提取列表 = re.findall(r' .+?=',浮点数变量标签样本) 浮点数变量属性列表 = [] for 属性 in 浮点数变量属性初步提取列表: 浮点数变量属性列表.append(属性.replace(' ','').replace('=','')) 正文部分 = '' 变量数量计数 = 0 del 数据列表[0] 变量类型 = 数据列表[0][1] if 变量类型 == 'IOBool': for 数据行 in 数据列表: 变量数量计数 += 1 正文部分 += r' <Tag' 属性指针 = 0 for 属性 in 数字量变量属性列表: 正文部分 += ' ' + 属性 + r'="' + 数据行[属性指针] + r'"' 属性指针 += 1 正文部分 += r'/>' +'\n' 正文头 = r' <IOBool TagNumber="' + str(变量数量计数) +r'">' + '\n' 正文尾 = r' </IOBool>' + '\n' 文件头 = 文件头.replace(r'变量数目',str(变量数量计数)) 文件内容 =文件头 + 正文头 + 正文部分 + 正文尾 + 文件尾 输出文件路径 = re.search(r'^.+\.',组态王导出的点表文件路径).group() 输出文件路径 = re.sub(r'\.$','_还原' + datetime.datetime.now().strftime('%y_%m_%d_%H%M%S') + '.xml',输出文件路径) 输出文件 = open(输出文件路径,'w',encoding = "utf-16") 输出文件.write(文件内容) 输出文件.close()
为了方便使用,写了有界面的版本,改用 xml.dom 解析和生成xml,使用 openpyxl 读写Excel文件

from xml.dom.minidom import parse from openpyxl import Workbook from openpyxl import load_workbook as 读Excel文件 import xml.dom import os import re import tkinter as tk from tkinter import filedialog from tkinter import messagebox from tkinter import simpledialog import datetime def 读取点表文件(点表文件路径): '''等到图形化时,修改此函数''' # 点表 = parse('.\点表\点表_全部IO变量.xml') 点表模型 = parse(点表文件路径) return 点表模型 def 截取点表主体部分(点表模型): 主体部分 = 点表模型.getElementsByTagName('BaseTag')[0] return 主体部分 def 获取子节点列表(父节点): 原始数据 = list(父节点.childNodes) # 直接获取子节点时,会把文本部分一起获取,下面将其筛选剔除 子节点列表 = [] for 节点 in 原始数据: if str(type(节点)) != "<class 'xml.dom.minidom.Text'>": 子节点列表.append(节点) return 子节点列表 def 获取节点属性字典(节点): 属性字典 = dict(节点.attributes.items()) return 属性字典 def 获取变量类型属性名列表(父节点): 第一个子节点 = 获取子节点列表(父节点)[0] return list(获取节点属性字典(第一个子节点).keys()) def 主体部分转化为列表(主体部分): ''' 生成的列表将包含全部点表数据 列表格式如下: [ [类型1名称(如IOBOOL),[属性名列表],[变量1属性值列表],[变量2属性值列表]...] [类型2名称(如IOBOOL),[属性名列表],[变量1属性值列表],[变量2属性值列表]...]... ] 对应 工作表名称,第1行,第2行... 此列表便于生成xls文件 ''' 总列表 = [] 分类型节点列表 = 获取子节点列表(主体部分) for 类型节点列表 in 分类型节点列表: 子列表 = [] 子列表.append(类型节点列表.tagName) 属性名列表 = 获取变量类型属性名列表(类型节点列表) 子列表.append(属性名列表) 变量节点列表 = 获取子节点列表(类型节点列表) for 变量节点 in 变量节点列表: 属性值列表 = [] 属性字典 = 获取节点属性字典(变量节点) for 属性名 in 属性名列表: 属性值列表.append(属性字典[属性名]) 子列表.append(属性值列表) 总列表.append(子列表) return 总列表 def 生成xls文件(总列表, 输入文件路径): Excel文档 = Workbook() for 类型 in 总列表: 工作表 = Excel文档.create_sheet(类型[0]) for i in range(1, len(类型)): 工作表.append(类型[i]) del Excel文档['Sheet'] 输出文件路径 = 输入文件路径.replace(r'.xml', 'n.xlsx') Excel文档.save(输出文件路径) Excel文档.close() def xml转xls(输入文件路径): 点表 = 读取点表文件(输入文件路径) 主体部分 = 截取点表主体部分(点表) 总列表 = 主体部分转化为列表(主体部分) 生成xls文件(总列表, 输入文件路径) def xls转xml(输入文件路径): # Excel文件 = 读Excel文件('.\点表\点表_全部IO变量.xlsx') # 输入文件路径 = r'.\点表\日日日.xlsx' Excel文件 = 读Excel文件(输入文件路径) 表文件列表 = Excel文件.sheetnames 点表数据 = {} for 工作表名 in 表文件列表: 工作表 = Excel文件[工作表名] 工作表数据列表 = [] for 数据行 in 工作表.iter_rows(min_row=1): 行数据列表 = [] for 数据 in 数据行: # 从Excel中读出的空单元格,会读为 None,需要将其替换为空字符串 if str(数据.value) == 'None': 行数据列表.append('') else: 行数据列表.append(str(数据.value)) 工作表数据列表.append(行数据列表) 点表数据[工作表名] = 工作表数据列表 # 统计变量数量 变量统计字典 = {} 工作表名列表 = list(点表数据.keys()) for 工作表名 in 工作表名列表: 变量统计字典[工作表名] = len(点表数据[工作表名]) - 1 变量总数 = sum(list(变量统计字典.values())) xml生成器 = xml.dom.getDOMImplementation() xml文档模型 = xml生成器.createDocument(None, None, None) 节点TagDefine = xml文档模型.createElement("TagDefine") 节点TagDefine.setAttributeNode(xml文档模型.createAttribute("Version")) 节点TagDefine.setAttribute("Version","7.5") xml文档模型.appendChild(节点TagDefine) 节点BaseTag = xml文档模型.createElement("BaseTag") 节点BaseTag.setAttributeNode(xml文档模型.createAttribute("TagNumber")) 节点BaseTag.setAttribute("TagNumber",str(变量总数)) 节点TagDefine.appendChild(节点BaseTag) for 工作表名 in 工作表名列表: 类型节点 = xml文档模型.createElement(工作表名) 类型节点.setAttributeNode(xml文档模型.createAttribute("TagNumber")) 类型节点.setAttribute("TagNumber",str(变量统计字典[工作表名])) 节点BaseTag.appendChild(类型节点) 属性列表 = 点表数据[工作表名][0] for 变量序号 in range(1, 变量统计字典[工作表名] + 1): 变量节点 = xml文档模型.createElement("Tag") i = 0 for 属性 in 属性列表: 变量节点.setAttributeNode(xml文档模型.createAttribute(属性)) 变量节点.setAttribute(属性, 点表数据[工作表名][变量序号][i]) i += 1 类型节点.appendChild(变量节点) 节点BaseTag.appendChild(类型节点) with open("new.xml", 'w', encoding='utf-16') as writer: xml文档模型.writexml(writer, indent='\n', addindent='\t') # 重新读入文件,修改第一行,保证原汁原味 中间文件 = open("new.xml",'r',encoding = "utf-16") xml内容 = 中间文件.read().replace(r'<?xml version="1.0" ?>', r'<?xml version="1.0" encoding="UTF-16" standalone="yes"?>') 中间文件.close() 输出文件路径 = 输入文件路径.replace(r'.xlsx', r'') + 'N.xml' 输出文件 = open(输出文件路径,'w',encoding = "utf-16") 输出文件.write(xml内容) 输出文件.close() def 选取文件(): 文件路径 = filedialog.askopenfilename() 文件路径 = 文件路径.replace('/',chr(92)) return 文件路径 def 获取xml文件路径(): '''获取模板文件路径,并写入 模板文件路径输入框''' global xml文件路径 xml文件路径 = 选取文件() xml文件路径输入框.delete(0, 'end') xml文件路径输入框.insert(0, xml文件路径) def 按钮响应xml转xls(): global xml文件路径 xml文件路径 = xml文件路径输入框.get() xml转xls(xml文件路径) def 获取xlsx文件路径(): '''获取模板文件路径,并写入 模板文件路径输入框''' global xlsx文件路径 xlsx文件路径 = 选取文件() xlsx文件路径输入框.delete(0, 'end') xlsx文件路径输入框.insert(0, xlsx文件路径) def 按钮响应xls转xml(): global xlsx文件路径 xlsx文件路径 = xlsx文件路径输入框.get() xls转xml(xlsx文件路径) xml文件路径 = '' xlsx文件路径 = '' if __name__ == '__main__': 主窗口 = tk.Tk() 主窗口.title('组态王点表 xml 到 xls 互转') # 定义窗口大小 主窗口.geometry('1024x90') # 设置窗口颜色 主窗口['background'] = '#CCCCCC' 文件选择区1 = tk.Frame(主窗口) 文件选择区1.pack() tk.Label(文件选择区1, text = '组态王点表(.xml):').grid(row = 0, column = 0) xml文件路径输入框 = tk.Entry(文件选择区1, width = 100) xml文件路径输入框.grid(row = 0,column = 1, padx = '5px') xml文件路径输入框.delete(0, 'end') xml文件路径输入框.insert(0, '请输入xml文件路径,或用右侧按钮选取') xml文件路径选择按钮 = tk.Button(文件选择区1,text = '...', width = 5, command = 获取xml文件路径) xml文件路径选择按钮.grid(row = 0,column = 2, padx = '5px') xml文件转化按钮 = tk.Button(文件选择区1,text = '转xlsx', width = 8, command = 按钮响应xml转xls) xml文件转化按钮.grid(row = 0,column = 3, padx = '5px') 文件选择区2 = tk.Frame(主窗口) 文件选择区2.pack() tk.Label(文件选择区2, text = 'Excel点表(.xlsx):').grid(row = 0, column = 0) xlsx文件路径输入框 = tk.Entry(文件选择区2, width = 100) xlsx文件路径输入框.grid(row = 0,column = 1, padx = '5px') xlsx文件路径输入框.delete(0, 'end') xlsx文件路径输入框.insert(0, '请输入xlsx文件路径,或用右侧按钮选取') xlsx文件路径选择按钮 = tk.Button(文件选择区2,text = '...', width = 5, command = 获取xlsx文件路径) xlsx文件路径选择按钮.grid(row = 0,column = 2, padx = '5px') xlsx文件转化按钮 = tk.Button(文件选择区2,text = '转xml', width = 8, command = 按钮响应xls转xml) xlsx文件转化按钮.grid(row = 0,column = 3, padx = '5px') 主窗口.mainloop()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了