PYQT5:基于QsciScintilla的代码编辑器分析4--xml格式的工程文件与树状列表部件
工程文件可以对相关文件进行组织,保存相关设置,很有用。这里借用了《codeblocks》软件的工程文件,以xml语言创建,读取,修改。这些操作都和树状列表部件相关,通过树状部件显示,修改。这部分内容分2部分。
1.xml文件的创建、读取、修改
下面是一个工程文件的例子:
<?xml version="1.0" encoding="UTF-8"?>
<CodeBlocks_project_file>
<FileVersion major="1" minor="6"/>
<Project>
<Option title="testLED"/>
<Option pch_mode="2"/>
<Option compiler="sdcc"/>
<Compiler>
<Add option="--mmcs51"/>
<Add option="--model-small"/>
</Compiler>
<Linker>
<Add option="--xram-size 4096"/>
<Add option="--iram-size 256"/>
<Add option="--code-size 65536"/>
<Add option="--out-fmt-ihx"/>
</Linker>
<Unit filename="./readfile/main.c"/>
<Unit filename="./readfile/delay.c"/>
</Project>
</CodeBlocks_project_file>
在这个文件中,用到的元素就是“Add”“Unit”。“Add”元素包含了编译信息,在打开工程时要读入“–xram-size”的参数,这个参数可以通过“编译器设置窗口”修改。“Unit”元素包含了工程中参与编译的C源文件,H头文件;在需要时进行删除,增加。这些操作设计成一个类,名字为“Dom4cbp”,包含了以下的5个接口:
- CreatNewCbp
创建新的cbp文件,用于新建工程时创建工程文件。自动生成上面例子中除了“Unit”之外的全部内容,工程包含的c、h文件要手动添加。 - delectFileFromCbp
删除工程中包含的c、h文件。 - AddFile2cbp
添加c、h文件到工程中。 - SetOptionOfXram
更改–xram-size的参数。在“编译器设置窗口”中完成修改。 - getFileList_xdata_In_cbp
读取工程文件中的c、h文件,以及–xram-size参数。在打开工程时调用此函数,并将文件添加到工程视图的树状列表中。
下面给出源代码:
import os
import xml.dom.minidom as Dom
class Dom4cbp:
Max_id = 0
def __init__(self,parent, cbp_Name):
self.parent = parent #主窗口作为父窗口传参,方便利用“信息输出视图”显示异常情况
if self.Max_id == 1:
self.parent.text_browser.append('工程 已存在!')
return
self.cbpName = cbp_Name
[self.dirname,self.filename]=os.path.split(self.cbpName)
if not self.filename :
self.Max_id = 1
''' 使用DOM类操作xml文件,改动一次总会产生很多空行,用下面的函数来消除多余的空行。
'''
def filterBlankLine(self, filePath):
if filePath is None or os.path.exists(filePath)==False:
self.parent.text_browser.append('读入工程文件出错!')
return
with open(filePath,'r+',encoding='utf-8') as fo:
str0 = (fo.read())
fo.close()
deststr=''
for rec in str0.splitlines():
if len(rec) == 0:
continue
for i in range(len(rec)):
if rec[i] == '\t':
continue
else:
deststr += rec
deststr +='\n'
break
with open(filePath,'w+',encoding='utf-8') as fo:
fo.write(deststr)
fo.close()
def CreatNewCbp(self):
doc = Dom.Document()
#1.root = CodeBlocks_project_file
root_node = doc.createElement("CodeBlocks_project_file")
doc.appendChild(root_node)
#2.创建root的2个子项 FileVersion Project
FileVersion_node = doc.createElement("FileVersion")
FileVersion_node.setAttribute("major", "1")
FileVersion_node.setAttribute("minor", "6")
root_node.appendChild(FileVersion_node)
Project_node = doc.createElement("Project")
root_node.appendChild(Project_node)
#3.1增加project 子项 3个Option
Option_node1 = doc.createElement("Option")
Option_node1.setAttribute("title",os.path.splitext(self.filename)[0])
Option_node2 = doc.createElement("Option")
Option_node2.setAttribute("pch_mode","2")
Option_node3 = doc.createElement("Option")
Option_node3.setAttribute("compiler","sdcc")
Project_node.appendChild(Option_node1)
Project_node.appendChild(Option_node2)
Project_node.appendChild(Option_node3)
#3.2增加project 子项 Compiler
FileVersion_node = doc.createElement("Compiler")
#3.2.1增加Compiler子项2个Add
Option_node1 = doc.createElement("Add")
Option_node1.setAttribute("option","--mmcs51")
Option_node2 = doc.createElement("Add")
Option_node2.setAttribute("option","--model-small")
FileVersion_node.appendChild(Option_node1)
FileVersion_node.appendChild(Option_node2)
Project_node.appendChild(FileVersion_node)
#3.3增加project 子项 Linker
FileVersion_node = doc.createElement("Linker")
#3.3.1增加Linker子项 4个Add
Option_node1 = doc.createElement("Add")
Option_node1.setAttribute("option","--xram-size 4096")
Option_node2 = doc.createElement("Add")
Option_node2.setAttribute("option","--iram-size 256")
Option_node3 = doc.createElement("Add")
Option_node3.setAttribute("option","--code-size 65536")
Option_node4 = doc.createElement("Add")
Option_node4.setAttribute("option","--out-fmt-ihx")
FileVersion_node.appendChild(Option_node1)
FileVersion_node.appendChild(Option_node2)
FileVersion_node.appendChild(Option_node3)
FileVersion_node.appendChild(Option_node4)
Project_node.appendChild(FileVersion_node)
# 4.准备完毕,信息写人.cbp文件
try:
with open(self.cbpName,'w',encoding='UTF-8') as fh:
# writexml()第一个参数是目标文件对象,第二个参数是根节点的缩进格式,第三个参数是其他子节点的缩进格式,
# 第四个参数制定了换行格式,第五个参数制定了xml内容的编码。
doc.writexml(fh,indent='',addindent='\t',newl='\n',encoding='UTF-8')
fh.close()
#print('写入xml OK!')
except Exception as err:
self.parent.text_browser.append('创建.cbp工程文件失败!')
def delectFileFromCbp(self, _fileName):#所有文件名都在Unit 中
if not self.cbpName or os.path.exists(self.cbpName)==False:
self.parent.text_browser.append('%s 工程文件不存在!'%self.cbpName)
return
doc=Dom.parse(self.cbpName)
# 1.获取根节点
root=doc.documentElement
#2.读取根节点下面所有名字为Project的一级节点
Projects=root.getElementsByTagName('Project')#实际上只有一个Project节点
#3.读取Project节点下,所有名字为“Unit”的二级节点
Options = Projects[0].getElementsByTagName('Unit')
for Option in Options:
#4.判断属性值是否包含目标文件名,如果是,将该节点删除
if _fileName in Option.getAttribute('filename') :
Option.parentNode.removeChild(Option)
#5.更新工程文件
try:
with open(self.cbpName,'w',encoding='UTF-8') as fh:
doc.writexml(fh,indent='',addindent='\t',newl='\n',encoding='UTF-8')
fh.close()
self.filterBlankLine(self.cbpName)
except :
self.parent.text_browser.append('在.cbp工程文件中删除 <%s>失败!' % _fileName)
break
def AddFile2cbp(self, fullfile):
if not self.cbpName or os.path.exists(self.cbpName)==False:
self.parent.text_browser.append('%s 工程文件不存在!'%self.cbpName)
return
doc=Dom.parse(self.cbpName)
# 1.获取根节点
root=doc.documentElement
#2.读取根节点下面所有名字为Project的一级节点
Options=root.getElementsByTagName('Project')
[dirname,filename]=os.path.split(fullfile) #判断文件是否在工程目录下
if self.dirname not in fullfile:
self.parent.text_browser.append("所添加的文件不在工程目录下,添加失败!")
return
if dirname != self.dirname: #文件位于工程目录下,但是路径不完全相等,说明文件位于子文件夹里面
filename = fullfile[len(self.dirname)+1:]
#3.生成属性值为该文件名的 “Unit”项,添加到Project节点上
Uint_node = doc.createElement("Unit")
Uint_node.setAttribute("filename",filename)
Options[0].appendChild(Uint_node)
#4.更新工程文件
try:
with open(self.cbpName,'w',encoding='UTF-8') as fh:
doc.writexml(fh,indent='',addindent='\t',newl='\n',encoding='UTF-8')
fh.close()
self.filterBlankLine(self.cbpName)
except Exception as err:
self.parent.text_browser.append('在.cbp工程文件中添加 <%s>失败!' % filename)
def SetOptionOfXram(self, _xdata):#在工程文件中修改"Option_node1" "--xram-size 4096"
if not self.cbpName or os.path.exists(self.cbpName)==False:
self.parent.text_browser.append('%s 工程文件不存在!'%self.cbpName)
return
doc=Dom.parse(self.cbpName)
# 1.获取根节点
root=doc.documentElement
#2.读取根节点下面所有名字为Project的一级节点
Projects=root.getElementsByTagName('Project')
#3.读取Project节点下,所有名字为“Add”的二级节点
Options = Projects[0].getElementsByTagName('Add')
for Option in Options:
#4.查找“Add”节点中,属性值包含 --xram 的节点,设置为参数中的 _xdata
if '--xram' in Option.getAttribute('option') :
value = '--xram-size '+_xdata
Option.setAttribute("option",value)
#5.更新工程文件
try:
with open(self.cbpName,'w',encoding='UTF-8') as fh:
doc.writexml(fh,indent='',addindent='\t',newl='\n',encoding='UTF-8')
fh.close()
self.filterBlankLine(self.cbpName)
except :
self.parent.text_browser.append('在.cbp工程文件中设置 %s 参数失败!' % value)
break
def getFileList_xdata_In_cbp(self):
if not self.cbpName or os.path.exists(self.cbpName)==False:
self.parent.text_browser.append('%s 工程文件不存在!'%self.cbpName)
return None
fileList=[]
_xdata=''
doc=Dom.parse(self.cbpName)
#1. 获取根节点
root=doc.documentElement
#2.读取根节点下面所有名字为Project的一级节点
Projects=root.getElementsByTagName('Project')
#3.读取Unit 即包含文件名的项
Options = Projects[0].getElementsByTagName('Unit')
for Option in Options:
_fileName = Option.getAttribute('filename')
if _fileName !=None:
fileList.append(self.dirname+'/'+ _fileName)
#4.找到 --xram-size ,获取需要的参数
Options = Projects[0].getElementsByTagName('Add')
for Option in Options:
str1 = Option.getAttribute('option')
if 'xram' in str1:
_xdata = str1[-4:]
break
if len(_xdata) <2:#如果没有读到该参数,赋给默认值
_xdata = '8192'
return fileList, _xdata
2.树状列表的添加和删除
先来一张图:
工程视图的树状列表结构比较简单,头部标题隐藏了,因为这里只有一列。根节点为工程名称,2个一级节点,分别为“source”“header”,如其名,表示c文件夹和h文件夹。二级节点就是c、h文件;c文件还有三级节点,就是函数名。
2.1创建工程时只生成根节点和一级节点
root=QTreeWidgetItem(self.tree)
[dirname,filename]=os.path.split(self.fullfile)
root.setText(0,filename)
root.setIcon(0,QIcon(":/{0}.png".format("Project")))
#2.创建一级子节点source和 header
child1=QTreeWidgetItem(root)
child1.setText(0,'source')
child1.setIcon(0,QIcon(":/{0}.png".format("Folder")))
child2=QTreeWidgetItem(root)
child2.setText(0,'header')
child2.setIcon(0,QIcon(":/{0}.png".format("Folder")))
2.2打开已有工程时生成所有节点
生成函数名的源码分析在第6章《获取c文件的函数名列表》。
self.mycbp=Dom4cbp(self, self.fullfile)
#1设置根节点(project name)
root1=QTreeWidgetItem(self.tree)
[self.ProjectDir,filename]=os.path.split(self.fullfile)
root1.setText(0,filename)
root1.setIcon(0,QIcon(":/{0}.png".format("Project")))
#2.创建一级子节点source和 header
child1=QTreeWidgetItem(root1)
child1.setText(0,'source')
child1.setIcon(0,QIcon(":/{0}.png".format("Folder")))
child2=QTreeWidgetItem(root1)
child2.setText(0,'header')
child2.setIcon(0,QIcon(":/{0}.png".format("Folder")))
#3.从工程文件中读入文件列表 及 --xram-size 的参数
self.file_list,self.XdataLen = self.mycbp.getFileList_xdata_In_cbp()
for str1 in self.file_list: #遍历所有文件,c文件和h文件分开处理
#3.1 c文件,加入一级节点source
if str1[-2: ]=='.c' or str1[-2: ]=='.C' :
subchild1=QTreeWidgetItem(child1)
subchild1.setIcon(0,QIcon(":/{0}.png".format("Cfile")))
subDirect, _fname=os.path.split(str1)
subchild1.setText(0,_fname)
#3.1.1] C 文件还要添加函数名作为三级节点
c_getfunc =lexer_c_getfunc(str1)
_fname, _dict= c_getfunc.lexer_analysis()
self.fileFuncDict.setdefault(_fname, _dict)
funcs=list(_dict.keys())
for func in funcs:
subsubchild = QTreeWidgetItem(subchild1)
subsubchild.setText(0,func)
subsubchild.setIcon(0,QIcon(":/{0}.png".format("func2")))
#3.2 h文件,加入一级节点header
elif str1[-2: ]=='.h' or str1[-2: ]=='.H' :
subchild1=QTreeWidgetItem(child2)
subchild1.setIcon(0,QIcon(":/{0}.png".format("Hfile")))
subDirect, _fname=os.path.split(str1)
subchild1.setText(0,_fname)
self.tree.expandToDepth(1)
2.3向工程添加源文件、从工程删除文件
在工程窗口中,右击除了文件名以外的地方都默认为添加文件;不过最好准确的添加,比如c文件右击“source”添加到《source》,h文件右击“header”添加到《header》。
右键菜单要在初始化时关联槽函数:
#给树状文件列表添加右键菜单
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)#开放右键策略
self.tree.customContextMenuRequested.connect(self.OnTreeRightMenuShow)#关联右键槽函数
槽函数的实现:
def OnTreeRightMenuShow(self, point): #当点击“source”和“header” 显示添加文件菜单;点击具体文件名时,显示删除文件菜单
item=self.tree.currentItem() #获取鼠标所在的树状列表的项
self.tree.popMenu = QMenu()
if '.' in item.text(0) and '.cbp' not in item.text(0):#该项是文件名,添加右键[删除菜单]
delectFile4Proj=QAction('Delect file from project', self)
self.tree.popMenu.addAction(delectFile4Proj)
delectFile4Proj.triggered.connect(self.do_delectFile4Proj)
else: #该项非文件名,添加右键[添加菜单]
addFile2Proj=QAction('Add file to project', self)
self.tree.popMenu.addAction(addFile2Proj)
addFile2Proj.triggered.connect(self.do_addFile2Proj)
self.tree.popMenu.exec_(QCursor.pos()) #在鼠标位置显示
def do_delectFile4Proj(self):
item=self.tree.currentItem()
#1.工程文件中删除此文件
self.mycbp.delectFileFromCbp(item.text(0))
#2.从文件列表中删除
for i in range(len(self.file_list)):
if item.text(0) in self.file_list[i]:
del self.file_list[i]
break
#3.从窗口中删除
if item.parent() is not None:
item.parent().removeChild(item)
def do_addFile2Proj(self): #先把文件名添加到界面,再写入cbp文件
fullfile,filetype = QFileDialog.getOpenFileName(self, "Select source or header file",
self.ProjectDir,"files(*.c *.h)")
if not fullfile:
return
for file in self.file_list:
if file == fullfile: #文件已经存在于工程中,添加失败
self.text_browser.append("文件已经存在于工程中,不要重复添加。")
return
item=self.tree.currentItem() #os.path.basename(fullfile)
childItem = QTreeWidgetItem(item)
childItem.setIcon(0,QIcon(":/{0}.png".format("Cfile")))
childItem.setText(0, os.path.basename(fullfile))
self.file_list.append(fullfile) #1】.向文件列表增加文件
self.mycbp.AddFile2cbp(fullfile) #2】向工程文件增加文件子项
#3】如果是.c 文件生成函数列表
if fullfile[-1] =='c' or fullfile[-1] =='C':
c_getfunc =lexer_c_getfunc(self.file_list[-1])
_fname, _dict= c_getfunc.lexer_analysis()
self.fileFuncDict.setdefault(_fname, _dict)
funcs=list(_dict.keys())
for func in funcs: #将函数名添加到对应的文件名下的子项
subsubchild = QTreeWidgetItem(childItem)
subsubchild.setText(0,func)
subsubchild.setIcon(0,QIcon(":/{0}.png".format("func2")))