需求:將xmind檔案轉為Excel檔案,並新增UI介面操作以降低操作難度。
這個需求一句話就講清楚了,但實際上還需要做很多工作:
1,瞭解Xmind檔案結構
2,提取Xmind檔案分支內容(重點)
3,UI介面(非必要)
一,瞭解Xmind檔案結構
1,xmind檔案形式:樹形分支結構(可以先思考如何提取各分支內容)。
Excel提取為何種形式:主幹與分支連線,用“\”號連線。(瞭解原理後也可以寫成其他你想要的形式)
舉例:
https\Tunnel to含義\Tunnel(隧道),中間實體
https\Tunnel to含義\隱藏:規則-隱藏連線
二,提取Xmind檔案分支內容(重點)
那麼如何解析上述為具體的資料結構呢?用“from xmindparser import xmind_to_dict”,用xmindparser包將其轉化為字典格式再做處理。
再舉個例子:
這個xmind檔案通過xmind_to_dict可以轉換為什麼樣呢?
{'title': 'A', 'topics': [{'title': 'B1'}, {'title': 'B2', 'topics': [{'title': 'C1'}, {'title': 'C2'}]}, {'title': 'B3'}]}
還是不夠直觀,用json分析器展開再看:
[
{
'title': '畫布 1',
'topic': {
'title': 'A',
'topics': [
{
'title': 'B1'
},
{
'title': 'B2',
'topics': [
{
'title': 'C1'
},
{
'title': 'C2'
}
]
},
{
'title': 'B3'
}
]
},
'structure': 'org.xmind.ui.map.unbalanced'
}
]
內容裝在一個list裡,字典最外層title為“畫布1”,topic裝有我們需要的內容(可以看出其他位置叫topics),這是xmind檔案中沒有的,經驗證這是個固定值,可以忽略。然後通過取list,A是字典中value值,key為title,其子節點裝在topics的分支中。處理後是這樣,最外層是dict:
那麼問題來了,如何將其處理為想要的形式呢?一層層的取key、value嗎?
當然不是,那工作量將非常龐大,而且不利於閱讀和修改。還記得剛才的思考內容嗎?這裡我們就需要用到你所想的,沒錯,遞迴。
為什麼要用遞迴,我們仔細看看結構:
A的三個子節點分別為B1,B2,B3,B2 的子節點為C1C2,參看上圖你想到了什麼?
1,標題和子節點放在不同的item裡,即topic放根節點,那topics則放其子節點。
2,topics無子節點呢?像C1C2那樣的點沒有子節點,我們發現,它也沒有topics;同時,沒有子節點證明它是這條分支的最後一層了;試想,如果A沒子節點,就只有它一個呢?正如你所想,它只有一個title,沒有topics。
3,A的子節點是一個list,list可以有多個子節點,子節點若還有子節點則有topics,若沒有則沒有topics。
4,title永遠裝的是string,而topics可以裝list,list裡還有dict,也可以沒有topics。
看了四點還是覺得很亂?
我們需要一層層取子節點才可以得到我們想要的A\B2\C1、A\B2\C2,節點值永遠在title裡,而子節點卻在dict、list裡的dict裡,而且子節點裡有的只有一個title,有的有一個title加一個topics,前文已經說過,有topics的是還有其子節點的。
那麼現在應該清晰點了吧?還不清楚?那虛擬碼再來一遍:
如果 輸入字典:
當字典只有有title時:
操作1
當字典有title和topics時:
操作2
如果 輸入list:
遍歷list裡的字典:
對每個字典進行操作3
如果 輸入的既不是字典也不是list:
輸出值
思路框架已經搭建了,那操作1、2、3是什麼呢?其實就是本框架的重複操作,呼叫下自己就可以了,不嚴謹的說這就是遞迴的通俗解釋。
我們貼下程式碼:
1 def TraversalXmind(root,rootstring): 2 if isinstance(root, dict): 3 if len(root) == 2: 4 TraversalXmind(root['topics'], str(rootstring) ) 5 if len(root) == 1: 6 TraversalXmind(root['title'],str(rootstring) ) 7 8 elif isinstance(root, list): 9 for sonroot in root: 10 TraversalXmind(sonroot, str(rootstring) + "\\" + sonroot['title']) 11 12 elif isinstance(root,str): 13 print(str(rootstring) ) 14 15 # TraversalXmind(root, "\\AA")
完整程式碼是什麼呢?
將上述轉換過程解除安裝model.py中:
1 # -*-coding:utf-8 -*- 2 # Author : zhengyong 3 # Time : 2020/11/5 23:06 4 # FileName: model.py 5 6 from xmindparser import xmind_to_dict 7 import os,csv 8 # filepath1 = os.path.abspath(os.path.dirname(__file__)) 9 # print(filepath1) 10 11 # filepath = "D:\\test.xmind" 12 # inputedXmind = xmind_to_dict(filepath) 13 # root = inputedXmind[0]['topic'] 14 15 def traversalXmind(root, rootstring,lisitcontainer): 16 """ 17 功能:遞迴dictionary檔案得到容易寫入Excel形式的格式。 18 注意:rootstring都用str來處理中文字元 19 @param root: 將xmind處理後的dictionary檔案 20 @param rootstring: xmind根標題 21 """ 22 if isinstance(root, dict): 23 if len(root) == 2: 24 traversalXmind(root['topics'], str(rootstring),lisitcontainer) 25 if len(root) == 1: 26 traversalXmind(root['title'], str(rootstring),lisitcontainer) 27 28 elif isinstance(root, list): 29 for sonroot in root: 30 traversalXmind(sonroot, str(rootstring) + "\\" + sonroot['title'],lisitcontainer) 31 32 elif isinstance(root, str): 33 lisitcontainer.append(str(rootstring)) 34 35 def getCase(root): 36 rootstring = root['title'] 37 lisitcontainer = [] 38 traversalXmind(root, rootstring,lisitcontainer) 39 # print(lisitcontainer) 40 return lisitcontainer 41 42 # def getTestCase(filename,lisitcontainer,directory,group,runType,testcaseType,testType): 43 # header = [ 44 # '測試案例路徑', 45 # '測試案例名稱', 46 # '測試案例描述', 47 # '計劃執行時長(分鐘)', 48 # '步驟描述', 49 # '預期結果', 50 # '優先順序', 51 # '小組', 52 # '執行方式', 53 # '測試模組', 54 # '版本號', 55 # '用例型別', 56 # '測試型別', 57 # '關聯故事卡片ID', 58 # ] 59 # 60 # with open(filename,'w',newline='') as f: 61 # writer = csv.DictWriter(f,fieldnames=header), 62 # writer.writeheader() 63 # for row in lisitcontainer: 64 # writer.writerow({ 65 # '測試案例路徑': directory, 66 # '測試案例名稱': row, 67 # '小組': group, 68 # '執行方式': runType, 69 # '用例型別':testcaseType, 70 # '測試型別': testType, 71 # })
三,UI介面
UI介面用tkinter寫的,直接上程式碼:
1 #-*-coding:utf-8 -*- 2 # Author : zhengyong 3 # Time : 2020/11/14 23:54 4 # FileName: main1.py 5 # https://github.com/ndnmonkey/transXmindToCSV.git 6 7 import model 8 from xmindparser import xmind_to_dict 9 import tkinter 10 from tkinter import filedialog 11 from tkinter import * 12 import os,csv 13 14 15 window = tkinter.Tk() 16 window.title("用例匯出工具") 17 # 設定視窗圖示 18 # window.iconbitmap("D:\Code\Python\XmindToExcel\image\icon.ico") 19 # window.iconbitmap("image\ifavicon.ico") 20 window.geometry("400x370+200+50") 21 22 # 標題 23 labeltitle = tkinter.Label(window,text = "用例匯出工具",font = ('幼圓',20)).pack() 24 25 # 建立選單欄 26 MenuBar = tkinter.Menu(window) 27 # 將選單欄放到主視窗 28 window.config(menu=MenuBar) 29 # 建立檔案選單,不顯示分窗 30 fileBar = tkinter.Menu(MenuBar, tearoff=0) 31 # 新增檔案選單項 32 fileBar.add_command(label="用法說明") 33 # fileBar.add_command(label="聯絡作者") 34 # 建立分割線 35 # fileBar.add_separator() 36 fileBar.add_command(label="退出", command=window.destroy) 37 # 將檔案選單新增到選單欄 38 MenuBar.add_cascade(label="選單", menu=fileBar) 39 40 xlabe = 20 41 xtext = 80 42 y =10 43 step = 30 44 textwidth = 250 45 46 inputtext1 = tkinter.StringVar(value='\常規版本用例\當前版本名\姓名\需求名') 47 labe1 = tkinter.Label(window, text='用例路徑:').place(x=xlabe,y = y + step) 48 text1 = tkinter.Entry(window, show=None, textvariable=inputtext1) 49 text1.place(width=textwidth,x=xtext+ 1*step,y=y + step) 50 51 inputtext2 = tkinter.StringVar(value='小組長姓名') 52 labe2 = tkinter.Label(window, text='組長姓名:').place(x=xlabe,y=y + 2*step) 53 text2 = tkinter.Entry(window, show=None, textvariable=inputtext2) 54 text2.place(width=textwidth,x=xtext+ 1*step,y=y + 2*step) 55 56 inputtext3 = tkinter.StringVar(value='手工測試') 57 labe3 = tkinter.Label(window, text='執行方式:').place(x=xlabe,y=y + 3*step) 58 text3 = tkinter.Entry(window, show=None, textvariable=inputtext3) 59 text3.place(width=textwidth,x=xtext+ 1*step,y=y + 3*step) 60 61 inputtext4 = tkinter.StringVar(value='系統用例') 62 labe4 = tkinter.Label(window, text='用例型別:').place(x=xlabe,y=y + 4*step) 63 text4 = tkinter.Entry(window, show=None, textvariable=inputtext4) 64 text4.place(width=textwidth,x=xtext+ 1*step,y=y + 4*step) 65 66 inputtext5 = tkinter.StringVar(value='手工測試') 67 labe5 = tkinter.Label(window, text='測試型別:').place(x=xlabe,y=y + 5*step) 68 text5 = tkinter.Entry(window, show=None, textvariable=inputtext5) 69 text5.place(width=textwidth,x=xtext+ 1*step,y=y + 5*step) 70 71 def getTextValues(): 72 templist = [] 73 var1 = text1.get();templist.append(var1) 74 var2 = text2.get();templist.append(var2) 75 var3 = text3.get();templist.append(var3) 76 var4 = text4.get();templist.append(var4) 77 var5 = text5.get();templist.append(var5) 78 # print("1",templist) 79 return templist 80 81 casebutton1 = tkinter.Button(window,text = '1,提交用例資訊',width=5,height=1,command=getTextValues) 82 casebutton1.place(x=110,y=y + 6*step) 83 casebutton1.configure(width = 34, height = 2) 84 85 def open_file(): 86 templist = getTextValues() 87 # print("2",templist) 88 filename = filedialog.askopenfilename(title='開啟Xmind檔案', filetypes=[('xmind', '*.xmind')]) 89 90 entry_filename.delete(0, END) 91 entry_filename.insert('insert', filename) 92 # print(entry_filename,type(entry_filename)) 93 94 # print(filename) 95 fname = entry_filename.get() #用get提取entry中的內容 96 fname = str(fname).replace('/','\\\\') 97 # print("fname",fname) 98 99 # filepath = "D:\\test.xmind" 100 # inputedXmind = xmind_to_dict(filepath) 101 # root = inputedXmind[0]['topic'] 102 # 103 inputedXmind = xmind_to_dict(fname) 104 root = inputedXmind[0]['topic'] 105 lisitcontainer = model.getCase(root) 106 # print(lisitcontainer) 107 108 # templist 109 directory = templist[0] 110 group = templist[1] 111 runType = templist[2] 112 testcaseType = templist[3] 113 testType = templist[4] 114 # filename = 'testcase.csv' 115 header = [ 116 '測試案例路徑', 117 '測試案例名稱', 118 '測試案例描述', 119 '計劃執行時長(分鐘)', 120 '步驟描述', 121 '預期結果', 122 '優先順序', 123 '小組', 124 '執行方式', 125 '測試模組', 126 '版本號', 127 '用例型別', 128 '測試型別', 129 '關聯故事卡片ID', 130 ] 131 # print(filename) #D:\\test.xmind 132 savepath = fname.split('.')[0] + ".csv" 133 # print("savepath:",savepath) 134 135 with open(savepath, 'w', newline='') as f: 136 writer = csv.DictWriter(f, fieldnames=header) 137 writer.writeheader() 138 for row in lisitcontainer: 139 writer.writerow({ 140 '測試案例路徑': directory, 141 '測試案例名稱': row, 142 '小組': group, 143 '執行方式': runType, 144 '用例型別': testcaseType, 145 '測試型別': testType, 146 }) 147 148 # 設定button按鈕接受功能 149 importbutton = tkinter.Button(window, text="2,匯入Xmind檔案", command=open_file) 150 importbutton.place(x=110,y=y + 9*step) 151 importbutton.configure(width =34,height=2) 152 153 # 設定entry 154 entry_filename = tkinter.Entry(window, width=30, font=("宋體", 10, 'bold')) 155 entry_filename.place(width=textwidth,x=xtext+ 1*step,y=y + 8*step) 156 157 # 版權頁 158 labelright = tkinter.Label(window,text = "version 0.3 bug修復:zhengyong731@pingan.com.cn",font = ('宋體',8)).place(x=60,y=350) 159 # Tk().iconbitmap('D:\Code\Python\XmindToExcel\image\icon.ico') 160 window.mainloop()
那麼這時候還有一個問題,執行之後的結果是什麼?
用pyinstaller就可以打包程式碼檔案變成可執行檔案:
方法:
1,安裝pyinstaller(不具體介紹)
2,使用:
命令列:
pyinstaller -F 主檔名.py
執行可執行檔案有黑框怎麼辦?
pyinstaller -F -w mycode.py (-w就是取消視窗)
3,生成的可執行檔案在哪?
在主檔案同級資料夾dist中,
4,具體檔案結構:
四,宣告
所有程式碼均為原創,因為實在找不到可用的教程,瞭解到找教程的艱辛,所以拋磚引玉,本人水平有限,如果文章和程式碼有表述不當之處,還請不吝賜教,可以交流討論。
歡迎轉載,請註明以下內容:
本人在cnblogs上的ID為puppet洛洛,部落格地址為https://www.cnblogs.com/two-peanuts/所有包含原創宣告的部落格均為本人原創作品。部落格的內容除已註明的引用文獻外均為本人獨立研究成果。除特殊註明外均採用 知識共享 署名-非商業性使用-相同方式共享 3.0 中國大陸 許可協議進行許可。
五,github地址
具體的檔案結構已傳至github,github地址:https://github.com/ndnmonkey/transXmindToCSV ,歡迎star。