引言
個人平時在寫sql指令碼的時候會使用到SQL Prompt這款外掛程式,除了強大的智能提示和格式化sql語句功能,我還喜歡使用Snippets程式碼片段功能。比如我們可以在查下分析器輸入ssf後按Tab鍵,SQL Prompt就可以幫我們快速的輸入SELECT * FROM 。
但是個人不習慣看大寫的sql代碼,所以就想搗鼓著將程式碼片段輸出的代碼變成小寫。開啟程式碼片段管理介面,探索管理工具提供了編輯程式碼片段的功能,但是如果要一個個的編輯,自行轉換成小寫,再儲存,那顯然不是咱的風格。可以看到SQL Prompt存放程式碼片段的路徑:
找到路徑中的檔案開啟可以看到,程式碼片段檔案是一個副檔名為sqlpromptsnippet的xml檔案。
所以想著使用python來批量的將程式碼片段檔案中的代碼轉換成小寫。
一. xml操作——找到Code節點並擷取程式碼片段的sql語句
1. xml格式檔案節點類型詳細介紹可以參考 W3School教程
2. python中讀寫xml檔案可以使用mxl.dom.minidom模組,尋找Code節點代碼如下:
import xml.dom.minidomsnippet = xml.dom.minidom.parse(‘ssf.sqlpromptsnippet‘)root = snippet.documentElementprint(root.nodeType,root.nodeName,root.nodeValue)code = snippet.getElementsByTagName(‘Code‘)[0]print(code.nodeType,code.nodeName,code.nodeValue)
snippet = xml.dom.minidom.parse(‘ssf.sqlpromptsnippet‘) :表示開啟當前路徑中名為‘ssf.sqlpromptsnippet‘的xml檔案,並把xml檔案對象賦值給snippet對象。
root = snippet.documentElement :表示擷取snippet對象的文件項目(根節點),並把獲得的對象給root。
code = snippet.getElementsByTagName(‘Code‘)[0] :表示尋找root根節點下面所有名為Code的子項目,並將第一個子項目賦值給code對象。
執行結果:
1 CodeSnippets None1 Code None
因為CodeSnippets和Code節點都不是文本節點,所有其nodeValue屬性為None。Code節點為1個 CDATASection節點,其有以下屬性:
所以找到Code節點並擷取程式碼片段的sql語句的正確語句如下:
import xml.dom.minidomsnippet = xml.dom.minidom.parse(‘ssf.sqlpromptsnippet‘)root = snippet.documentElement#print(root.nodeType,root.nodeName,root.nodeValue)code = snippet.getElementsByTagName(‘Code‘)[0]#print(code.nodeType,code.nodeName,code.nodeValue)statement = code.firstChild.data # code的第1個(也是唯一的)子項目才是CDATASection節點
print (statement)
執行結果:
SELECT * FROM
二. sql代碼轉換操作——大寫轉小寫
1. sql語句大寫轉小寫,可以直接使用str類的lower函數即可:
statementlower = statement.lower()print (statementlower)
執行結果
select * from
2. SQL Prompt中有部分程式碼片段是含有預留位置的,預留位置的格式為”$CURSOR$”,而且其是區分大小寫,所以預留位置不能轉換成小寫。所以需要先將程式碼片段中個sql語句中的預留位置全部找出來,並儲存起來,在sql語句轉換成小寫之後替換回去。
因為預留位置都是以“$”開頭,也以“$”結尾,所以我們可以很方便的使用Regex來尋找sql語句中的所有預留位置。尋找出來之後先將佔位符和其小寫形式使用dict儲存起來。
import xml.dom.minidomimport resnippet = xml.dom.minidom.parse(‘ct.sqlpromptsnippet‘)root = snippet.documentElement#print(root.nodeType,root.nodeName,root.nodeValue)code = snippet.getElementsByTagName(‘Code‘)[0]#print(code.nodeType,code.nodeName,code.nodeValue)statement = code.firstChild.data # code的第1個(也是唯一的)子項目才是CDATASection節點print (statement) print (statement) # 輸出原語句# 正則尋找所有的預留位置keylist = re.findall("\$\w+\$",statement) # 將佔位符和其小寫形式儲存成字典placeholds = dict() for key in keylist: placeholds[key] = key.lower()print(placeholds)# 先將語句轉換成小寫statementlower = statement.lower()# 迴圈預留位置字典,替換回預留位置for k,v in placeholds.items(): statementlower = statementlower.replace(v,k)print (statementlower)
執行結果:
CREATE TABLE $table_name$( $CURSOR$){‘$table_name$‘: ‘$table_name$‘, ‘$CURSOR$‘: ‘$cursor$‘}create table $table_name$( $CURSOR$)
三. xml操作——將轉碼寫回xml檔案
xml寫操作使用的是writexml檔案,具體代碼如下:
import xml.dom.minidomimport resnippet = xml.dom.minidom.parse(‘ct.sqlpromptsnippet‘)root = snippet.documentElement#print(root.nodeType,root.nodeName,root.nodeValue)code = snippet.getElementsByTagName(‘Code‘)[0]#print(code.nodeType,code.nodeName,code.nodeValue)statement = code.firstChild.data # code的第1個(也是唯一的)子項目才是CDATASection節點print (statement) keylist = re.findall("\$\w+\$",statement)placeholds = dict()for key in keylist: placeholds[key] = key.lower()statementlower = statement.lower()for k,v in placeholds.items(): statementlower = statementlower.replace(v,k)#更新XML對象code.firstChild.data = statementlower# 開啟檔案對象,再寫入f = open(‘result\ct.sqlpromptsnippet‘, ‘w‘,encoding = ‘utf-8‘)snippet.writexml(f, addindent=‘‘, newl=‘‘,encoding=‘utf-8‘)f.close()
執行結果組建檔案對比:
四. 大量操作——迴圈程式碼片段檔案批量處理
1. 迴圈目錄下的檔案,使用的是os模組的listdir方法。
>>> import os>>> os.listdir()[‘DLLs‘, ‘Doc‘, ‘include‘, ‘Lib‘, ‘libs‘, ‘LICENSE.txt‘, ‘NEWS.txt‘, ‘python.exe‘, ‘python3.dll‘, ‘python35.dll‘, ‘pythonw.exe‘, ‘README.txt‘, ‘Scripts‘, ‘tcl‘, ‘Tools‘, ‘vcruntime140.dll‘]
2. 先將單個轉換封裝成方法sqllower,再迴圈讀取目錄下的程式碼片段檔案即可完成批量處理,完整代碼如下:
import xml.dom.minidomimport reimport osdef sqllower(name): snippet = xml.dom.minidom.parse(name) root = snippet.documentElement #print(root.nodeType,root.nodeName,root.nodeValue) code = snippet.getElementsByTagName(‘Code‘)[0] #print(code.nodeType,code.nodeName,code.nodeValue) statement = code.firstChild.data # code的第1個(也是唯一的)子項目才是CDATASection節點print (statement) #print (statement) keylist = re.findall("\$\w+\$",statement) placeholds = dict() for key in keylist: placeholds[key] = key.lower() #print(placeholds) statementlower = statement.lower() for k,v in placeholds.items(): statementlower = statementlower.replace(v,k) #print (statementlower) #更新XML對象 code.firstChild.data = statementlower f = open(‘result\\‘ + name, ‘w‘,encoding = ‘utf-8‘) snippet.writexml(f, addindent=‘‘, newl=‘‘,encoding=‘utf-8‘) f.close()# 迴圈進行轉換for f in os.listdir(): if f.endswith(‘.sqlpromptsnippet‘): print(‘正在轉換‘+ f) sqllower(f)print (‘所有轉換完成。‘)
五. 總結
本文從日常使用中提取出sql程式碼片段大小寫轉換的需求,將其使用Python實現。使用到了如下的模組:
1. xml.dom.minidom模組,用來讀寫xml檔案。
2. re模組,使用了Regex,查詢所有的預留位置。
3. os模組,使用listdir方法來迴圈目錄中個檔案。