用SketchUp替換文本內(nèi)容
SketchUp 中的 ruby 控制臺作為自動化操作的利器,自然也可以進(jìn)行最基本的文檔內(nèi)容修改任務(wù)。因此本篇使用 SketchUp 自帶的 ruby 控制臺制作一個文本文檔的替換小工具。
編程無疑是將自己從重復(fù)性工作中解放出來的好辦法,但是對于完全的新手來說,某些編程工具在安裝這一步就相當(dāng)讓人迷惑,以至于一開始就勸退很多人。就像在Windows中,誰都可以直接使用一些MS-DOS批處理指令或VBS腳本,對于安裝了 SketchUp 的用戶來說,ruby 控制臺也是一個拿來即用的編程工具?;蛟S SketchUp 里的 ruby 只是一個簡陋的版本,但這的確節(jié)省了不少在安裝編程環(huán)境上的時間。
*這是“SketchUp不務(wù)正業(yè)系列”中的一篇,文中淺紫色文字為“不務(wù)正業(yè)”對“正業(yè)”的借鑒參考。
| 【本期目錄】 | |
一、替換文本的流程 二、正則表達(dá)式 三、代碼實現(xiàn) | 這是一個極其簡單的功能,幾乎完全不需要自己設(shè)計任務(wù)邏輯。 |
一、替換文本的流程
文本替換的流程是這樣的:
①確定原文件的路徑、替換文本的規(guī)則以及結(jié)果保存的路徑;
②讀取文件內(nèi)容;
③根據(jù)設(shè)置的規(guī)則替換內(nèi)容;
④將替換后的內(nèi)容保存到新的文件中。
新舊文件路徑的選擇可以使用最原始的輸入地址的方法,但是對于文件路徑比較復(fù)雜的情況來說,直接在窗口中選擇文件還是要方便一些。因此這一步需要使用 SketchUp 提供的打開和保存文件的對話框:
filename=UI.openpanel("title")#>>?filename為選擇的文件名#???如果取消filename則為nil
執(zhí)行以上代碼可以打開系統(tǒng)默認(rèn)的“打開”對話框,窗口的標(biāo)題可以自己更改:

保存路徑的選擇也類似于“打開”對話框:
filename=UI.savepanel("title")#>>?filename為選擇的文件名
二者的區(qū)別在于“保存”對話框可以設(shè)置新文件,且選擇已有的文件會有覆蓋警告;而“打開”對話框只能選擇存在的文件。
確定了文件路徑后是讀取文件,可以使用以下代碼:
str1=File.read(input_filename)#正常運行后str1將保存文件中的所有內(nèi)容#類型為String
將字符串保存到新的文件中,則需要使用如下的代碼:
f=File.open(filename,"w")f.write(str2)f.close()
以上代碼中:第1行?.open 方法表示創(chuàng)建文件,其中的第二個參數(shù)?"w" 表示允許覆蓋創(chuàng)建。第2行的?.write 方法表示將?str2 寫入文件中,第3行的?.close 方法保存文件。
最后是文本替換部分,這本是一個很復(fù)雜的任務(wù)邏輯,但是ruby有自帶的方法能夠?qū)崿F(xiàn)這個功能,唯一要做的就是了解字符串類的?.gsub 方法:
str2?=?str1.gsub(op,np)簡單來說,這個方法就是以參數(shù)規(guī)定的規(guī)則替換?str1?中的特定內(nèi)容,然后將結(jié)果返回給?str2。如果兩個參數(shù)都是字符串,那么規(guī)則就是將文本中所有?op 替換成 np。如果參數(shù)?op 是正則表達(dá)式,那么替換的規(guī)則將更加復(fù)雜且靈活。
二、正則表達(dá)式
關(guān)于正則表達(dá)式,這里只提供一些感性的例子,詳細(xì)的語法規(guī)則和使用方法不是本篇文章能夠概括的??梢詤⒖家韵聝蓚€網(wǎng)址:
https://ruby-doc.org/core-2.7.1/String.html#method-i-gsub
https://www.runoob.com/regexp/regexp-tutorial.html
(1)?簡易替換
這種用法和系統(tǒng)自帶的記事本替換功能沒有區(qū)別,但是需要規(guī)避某些有特殊含義的符號:

圖中的?"\xe7\x94\xa8\xe5\x9c\xb0" 表示“用地”,這么寫是因為 ruby 控制臺的輸入不是 UTF-8 編碼,所以會出現(xiàn)編碼錯誤。如果需要使用中文則需要使用?load 方法調(diào)用寫好的 *.rb 文件
(2) 編號識別替換
對于有明確規(guī)則的編號,正則表達(dá)式可以找出這些編號并加以利用。
s1="aapid=000022?aibuebcfw@#RD#?pid=002014?ksheFW$#?pid=B36A24"s1.gsub(/pid=([0-9,A-F]{6})/,'^([\1])')#>>?aa^([000022])?aibuebcfw@#RD#?^([002014])?ksheFW$#?^([B36A24])
以上代碼中的正則表達(dá)式?/pid=([0-9,A-F]{6})/?含義為:以 “pid=” 開始包含 6 位十六進(jìn)制數(shù)位([0-9,A-F])的片段,?{6} 表示前一個方括號表示的字符的重復(fù)次數(shù)為 6。圓括號則用于替換,第一個圓括號所包含的匹配字段用?\1 表示,如果有更多圓括號,則以此類推使用?\2、?\3、?\4……所以最終的效果便是將諸如?“pid=000022” 的片段替換為?“^([000022])”。
另外需要注意,例子中第二個參數(shù)使用了單引號,雙引號則需要寫成?"^([\\1])" ,因為雙引號字符串中的反斜杠會被當(dāng)做轉(zhuǎn)義符號解讀。
(3) 斷行處理
斷行是文本文檔與表格文件對接的重要字符,找到符合條件的斷點非常關(guān)鍵:
s2="?nah?1200.0?9h?lazarus?3235.1?6h?requiem?1023.5?7h"s2.gsub(/(\s[0-9]+h)/,'\1'+"\r\n")#>>?nah?1200.0?9h#>> lazarus?3235.1?6h#>>?requiem?1023.5?7h
以上代碼中的?'\1'+"\r\n"?使用了兩種引號就是為了在適當(dāng)?shù)臅r候使用轉(zhuǎn)義符號,也可以直接使用 "\\1\r\n" 這樣的表達(dá)。表達(dá)式?/(\s[0-9]+h)/?表示以空格符號(\s)開始以字符 “h” 結(jié)尾,中間有一個或多個(+)數(shù)字 0-9 的文本片段。
(4)?調(diào)換順序
還可以根據(jù)具體要求調(diào)換局部文本的順序:
s3="27.3324N?116.2132E?42.3324N?102.2132E?10.9323E?79.2214W"s3.gsub(/([0-9\.]+)([NEWS])/,'\2=\1')#>>?N=27.3324?E=116.2132?N=42.3324?E=102.2132?E=10.9323?W=79.2214
以上代碼中正則表達(dá)式包含兩個圓括號,因此替換字符串可以同時引用?\1 和 \2。表達(dá)式?/([0-9\.]+)([NEWS])/ 表示以一個或多個(+)數(shù)字 0-9 開始,后跟隨小數(shù)點(\.),而后以?NEWS 的其中一個字符結(jié)尾的文本片段。
(5) 數(shù)字識別處理
使用正則表達(dá)式還可以很方便的提取出數(shù)字文本:
s4="1200.0?1212.843?3235.1?4413.31?1023.5?"s4.gsub(/\.([0-9]{,2})\s/,'.\10?').gsub(/\.([0-9]{,2})\s/,'.\10?')#>>?1200.000?1212.843?3235.100?4413.310?1023.500s5="lingling 40 hours 3424"s5.gsub(/([0-9]+)(\shours)/,'\1 minutes')#>> lingling 40 minutes 3424
以上代碼第2行通過連續(xù)兩次?.gsub 方法將小數(shù)點后位數(shù)追加到三位。表達(dá)式?/\.([0-9]{0,2})\s/?表示的是以小數(shù)點(\.)開始以空格符號(\s)結(jié)尾,且中間包含不多于2個({,2})的數(shù)字 0-9,識別小數(shù)部分后在最后追加一個 “0”,以達(dá)到補(bǔ)足小數(shù)位數(shù)的效果。其中的?{,2} 表示前一個方括號代表的字符可以重復(fù)2次及以下。第6行代碼則識別具體的數(shù)量單位并替換。
(6)?單詞替換
正則表達(dá)式也可以可以根據(jù)單詞長度或者其他規(guī)則自制填空題:
s6="A?String?object?holds?and?manipulates?an?arbitrary?sequence?"+"of bytes, typically representing characters. String objects "+"may be created using ::new or as literals."#》s6.gsub(/::[^\s]+/,'___')#>> A String object holds and manipulates an arbitrary sequence#>> of bytes, typically representing characters. String objects#>>?may?be?created?using?___?or?as?literals.
文中的表達(dá)式?/::[^\s]+/ 表示以 “::” 開頭到空格符號截止的一段文本。其中的?[^\s] 表示除了空格符號(\s)以外的所有符號,符號 “+” 表示一個或更多,相當(dāng)于?{1,} 的表達(dá)。
(7) 序列
甚至可以對長文本序列進(jìn)行特定數(shù)位的截斷:
s6="please?call?01189998819991197253"s6.gsub(/([0-9]{3})/,'\1-')#>>?please?call?011-899-988-199-911-972-53
根據(jù)正則表達(dá)式的匹配原則,符合條件的文本中的部分片段不會再重新匹配,因此可以實現(xiàn)每隔固定的位數(shù)插入其他字符。
三、代碼實現(xiàn)
最后將整個過程封裝在?ApiglioToolBox 模塊中,由于ruby控制臺不支持UTF8輸入,所以直接復(fù)制進(jìn)控制臺時不能使用帶漢字的代碼,所以這里直接使用了轉(zhuǎn)義符號:
module ApiglioToolBoxdef self.file_rep_func(input,output,old,new)str1=File.read(input)str2=str1.gsub(old,new)f=File.open(output,"w")f.write(str2)f.close()enddef self.file_rep()open_tip="\xe9\x80\x89\xe6\x8b\xa9\xe9\x9c\x80\xe8\xa6\x81\xe6\x9b\xbf\xe6\x8d\xa2\xe5\x86\x85\xe5\xae\xb9\xe7\x9a\x84\xe6\x96\x87\xe6\xa1\xa3"#選擇需要替換內(nèi)容的文檔save_tip="\xe6\x9b\xbf\xe6\x8d\xa2\xe7\xbb\x93\xe6\x9e\x9c\xe4\xbf\x9d\xe5\xad\x98\xe4\xb8\xba"#替換結(jié)果保存為inpu_tip="\xe6\x9b\xbf\xe6\x8d\xa2\xe9\x80\x89\xe9\xa1\xb9\xef\xbc\x88\xe6\xad\xa3\xe5\x88\x99\xe8\xa1\xa8\xe8\xbe\xbe\xe5\xbc\x8f\xef\xbc\x89"#替換選項(正則表達(dá)式)op="\xe6\x9f\xa5\xe6\x89\xbe\xef\xbc\x9a"#查找:np="\xe6\x9b\xbf\xe6\x8d\xa2\xe4\xb8\xba\xef\xbc\x9a"#替換為:mp="\xe6\xa8\xa1\xe5\xbc\x8f\xef\xbc\x9a"#模式:mo_ord="\xe6\x99\xae\xe9\x80\x9a\xe6\xa8\xa1\xe5\xbc\x8f"#普通模式mo_reg="\xe6\xad\xa3\xe5\x88\x99\xe8\xa1\xa8\xe8\xbe\xbe\xe5\xbc\x8f"#正則表達(dá)式raise RuntimeError.new("Dialog Cancelled") unless filename=UI.openpanel("FileReplace: "+open_tip)raise RuntimeError.new("Dialog Cancelled") unless targetname=UI.savepanel("FileReplace: "+save_tip)patterns=UI.inputbox([op,np,mp],["","",mo_ord],["","",mo_ord+"|"+mo_reg],inpu_tip)raise RuntimeError.new("Dialog Cancelled") unless patternsreturn false if patterns[0]==""case patterns[2]when mo_regpatterns[0]=patterns[0]+"/" if patterns[0][-1]!="/"patterns[0]="/"+patterns[0] if patterns[0][0]!="/"puts "ApiglioToolBox.file_rep_func('"+filename+"','"+targetname+"',"+patterns[0]+",'"+patterns[1]+"')"patterns[0]=eval(patterns[0])when mo_ordputs "ApiglioToolBox.file_rep_func('"+filename+"','"+targetname+"',\""+patterns[0]+"\",\""+patterns[1]+"\")"endfile_rep_func(filename,targetname,*patterns[0..1])trueendend
使用這個替換工具只需要在控制臺輸入:
ApiglioToolBox.file_rep而后只需要根據(jù)彈出的對話框進(jìn)行相應(yīng)的設(shè)置即可。當(dāng)然彈出對話框也未必總是那么方便,因此還可以通過調(diào)用?.file_rep_func 方法,通過給定參數(shù)來避免在對話框中設(shè)置參數(shù):
ApiglioToolBox.file_rep_func(input,output,old_pattern,new_pattern)#input 輸入的文本文檔#output 輸出的文本文檔#old_pattern?需要替換的內(nèi)容(用雙引號包括)#????????????需要替換內(nèi)容的正則表達(dá)式(用斜杠包括)#new_pattern?替換后的內(nèi)容(如不想使用轉(zhuǎn)義符號則使用單引號)#????????????替換內(nèi)容可以增加\1?\2?\3?來引用匹配到的文本
以下是文本替換工具的使用效果:

替換前后文件的內(nèi)容如下:

?正業(yè)? 這是一個與建模完全無關(guān)的小工具,只是利用 SketchUp 中的 ruby 控制臺的圖靈完備進(jìn)行一些簡易的編程。其存在的意義在于:很多自動化任務(wù)只需要非常簡單的技術(shù)手段,但是為此 beginner 卻需要安裝整套的語言環(huán)境,這是相當(dāng)繁瑣甚至沒有必要的。ruby 控制臺完全省去了安裝開發(fā)環(huán)境的門檻,不需要額外進(jìn)行任何準(zhǔn)備,再加上 ruby 本身的語法特點,使得這類小功能可以很輕松很愉悅的實現(xiàn),對于本就安裝了 SketchUp 的用戶來說十分便利。
另外,這個工具使用了 UI 模塊中的幾個對話框方法(其實就是系統(tǒng)的對話框)來串聯(lián)整個工具的邏輯,甚至可以給它設(shè)置一個 Toolbar 使之變成一個按鍵,成為典型意義上的插件功能。但是這種連續(xù)跳好幾個對話框的處理方式還是太過冗雜,更合適的做法是使用 UI:: HtmlDialog 做類似于ArcToolBox 的窗口,UI 邏輯會明顯清晰得多。正如本系列創(chuàng)作的初衷,雖然名為“不務(wù)正業(yè)”,但怪異旁門的方法也能夠給“正統(tǒng)”的使用方式以參考借鑒。
(都看到這了,點個贊唄)
本篇文章是“SketchUp不務(wù)正業(yè)系列”的其中一篇,編號為SU-2021-S02,更多與 SketchUp Ruby有關(guān)的其他文章可以點擊公眾號菜單中的“SU Ruby”選項獲得文章的目錄。
