0. 背景
之前公司的項目中,需要在嵌入式系統中實現一個http的網頁端內容,由於項目歷史遺留問題,公司是采用的將html文件轉成c語言頭文件的方式,每次修改頁面端都需要從新編譯一下程序,非常的繁瑣。
雖然繁瑣,但是因為歷史遺留問題,歷史遺留項目都采用這種方式做后面的升級維護。
入鄉隨俗嘛,用python寫了一個html和h文件互轉的小程序,程序編寫的過程和原理很簡單,以后有時間再另外發帖。(TODO)在此不做深入討論。
程序也很好用,但是最近將公司自己寫的程序使用gitblit本地倉庫的形式進行版本管理后,發現一個致命的問題。就是每次轉換成的h文件和公司歷史遺留的文件進行git diff 時候,滿屏都是不一樣的地方。這咋利於版本控制和驗證呢?
1. 問題分析
究竟是哪里不同呢?后來發現原來我寫的轉換腳本,和公司慣用的html to c腳本有着嚴重不同的地方在於:
公司舊版本程序是:

我轉換的程序是:

git diff 比對文件的時候是會比對空格的,而且是引號的位置不同,所以就是大段的內容是不一樣的。
這怎么辦呢?
這時候正則匹配就派上用場了。
2. 尋找方法
上述問題其實總結起來就是:“引號位置放錯了”。那么怎么知道應該在哪里放置引號呢?博主想到的笨辦法就是在把每行的內容單獨拎出來,然后分成三個部分,空格+內容+空格的方式,然后在組合成 空格+引號 +內容+引號+空格的方式。然后實際上就是提取出來了內容兩邊的東西。
talk is cheap , show codes.
假設我們有一個 test.html 文件:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 </head> 7 <body> 8 <h1>Hello World</h1> 9 </body> 10 </html>
我們讀取它的時候,要注意,每行實際在末尾有一個換行符\n
現在我們編寫一個 r.py 腳本
1 import re 2 with open('test.html') as f: 3 lines = f.readlines() # 獲取行列表信息 4 print(lines) # 打印行信息
我們在ipython中執行是這樣的:
1 In [31]: %run r.py 2 ['<!DOCTYPE html>\n', '<html lang="en">\n', '<head>\n', '\t<meta charset="UTF-8" 3 >\n', '\t<title>Document</title>\n', '</head>\n', '<body>\n', '\t<h1>Hello Wor 4 ld</h1>\n', '</body>\n', '</html>']
2,3,4 表示的是每行的信息,和我們上面的 test.html 文件是一致的。
將上面的列表整理一下:
1 # 整理列表 2 [ 3 '<!DOCTYPE html>\n', 4 '<html lang="en">\n', 5 '<head>\n', 6 '\t<meta charset="UTF-8">\n', 7 '\t<title>Document</title>\n', 8 '</head>\n', '<body>\n', 9 '\t<h1>Hello World</h1>\n', 10 '</body>\n', '</html>' 11 ]
可以看出,我們就是逐行打印了文件內容而已:
拿第6行舉例,我們需要匹配到\t 和 \n 並在合適的地方加上引號,程序就over了。
查閱正則內容(菜鳥教程Python正則表達式章節),可知道 \s 可以匹配任意空白字符。

於是,我們用行6字符串測試一下我們的處理代碼對不對:
In [54]: s = re.search(r'^(\s*)(.*)(\s*)$','\t"<title>Document</title>"\n') In [55]: s.group() Out[55]: '\t"<title>Document</title>"\n' In [56]: s.group(0) Out[56]: '\t"<title>Document</title>"\n' In [57]: s.group(1) Out[57]: '\t' In [58]: s.group(2) Out[58]: '"<title>Document</title>"' In [59]: s.group(3) Out[59]: '\n'

測試和之前的想法是一致的。括弧括起來的內容被捕獲出來。
3. 解決問題
由此,上述問題基本已經找到解決的頭緒,那么定下代碼編寫的流程:
- 讀取讀文件
- 行列表信息行處理
- 讀取寫文件
- 寫入處理后的行列表信息
於是編寫代碼:
1 import re # 引入正則庫 2 with open('test.html') as f: # 讀取讀文件 3 lines = f.readlines() # 讀取行信息 4 r = r'^(\s*)(.*)(\s*)$' # 正則 5 lines = [re.search(r,l).group(1) +'"'+ re.search(r,l).group(2)+'\\n"'+re.search(r,l).group(3) for l in lines] # 處理行信息 6 with open('test.h','w+') as f2: # 讀取寫文件 7 f2.writelines(lines) # 寫入行信息
其中第4行就是我們處理行信息的過程,這里用了一個列表推導式
所謂列表推導式,就是一種for循環的簡寫形式,可以從一個列表,經過一定的變換,快速生成一個列表。例如:
In[1] : a = [1,2,3,4] In[2] : print(a) Out[1] : [1,2,3,4] In[3] : print([i for i in a]) Out[2] : [1,2,3,4] In[4] : print([i*2+1 for i in a]) Out[3] : [3,5,7,9]
也就是,前面第4行的程序實際上就是將lines的數據單個處理,在捕獲內容中加入一些我們需要的字符,比如是雙引號,然后組成了新的列表。寫入到文件中。
問題解決。
4. 總結
這個測試腳本的重點就在於正則的捕獲,正則捕獲在文本文件、字符串處理中使用廣泛,需要不斷積累和總結,方能領悟其中的妙用。
