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个接口:

  1. CreatNewCbp
    创建新的cbp文件,用于新建工程时创建工程文件。自动生成上面例子中除了“Unit”之外的全部内容,工程包含的c、h文件要手动添加。
  2. delectFileFromCbp
    删除工程中包含的c、h文件。
  3. AddFile2cbp
    添加c、h文件到工程中。
  4. SetOptionOfXram
    更改–xram-size的参数。在“编译器设置窗口”中完成修改。
  5. 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")))
posted @ 2023-03-11 20:37  汉塘阿德  阅读(102)  评论(0编辑  收藏  举报  来源