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

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

圖中的?"\xe7\x94\xa8\xe5\x9c\xb0" 表示“用地”,這么寫是因?yàn)?ruby 控制臺(tái)的輸入不是 UTF-8 編碼,所以會(huì)出現(xiàn)編碼錯(cuò)誤。如果需要使用中文則需要使用?load 方法調(diào)用寫好的 *.rb 文件
(2) 編號(hào)識(shí)別替換
對(duì)于有明確規(guī)則的編號(hào),正則表達(dá)式可以找出這些編號(hào)并加以利用。
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} 表示前一個(gè)方括號(hào)表示的字符的重復(fù)次數(shù)為 6。圓括號(hào)則用于替換,第一個(gè)圓括號(hào)所包含的匹配字段用?\1 表示,如果有更多圓括號(hào),則以此類推使用?\2、?\3、?\4……所以最終的效果便是將諸如?“pid=000022” 的片段替換為?“^([000022])”。
另外需要注意,例子中第二個(gè)參數(shù)使用了單引號(hào),雙引號(hào)則需要寫成?"^([\\1])" ,因?yàn)殡p引號(hào)字符串中的反斜杠會(huì)被當(dāng)做轉(zhuǎn)義符號(hào)解讀。
(3) 斷行處理
斷行是文本文檔與表格文件對(duì)接的重要字符,找到符合條件的斷點(diǎn)非常關(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"?使用了兩種引號(hào)就是為了在適當(dāng)?shù)臅r(shí)候使用轉(zhuǎn)義符號(hào),也可以直接使用 "\\1\r\n" 這樣的表達(dá)。表達(dá)式?/(\s[0-9]+h)/?表示以空格符號(hào)(\s)開始以字符 “h” 結(jié)尾,中間有一個(gè)或多個(gè)(+)數(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á)式包含兩個(gè)圓括號(hào),因此替換字符串可以同時(shí)引用?\1 和 \2。表達(dá)式?/([0-9\.]+)([NEWS])/ 表示以一個(gè)或多個(gè)(+)數(shù)字 0-9 開始,后跟隨小數(shù)點(diǎn)(\.),而后以?NEWS 的其中一個(gè)字符結(jié)尾的文本片段。
(5) 數(shù)字識(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ù)點(diǎn)后位數(shù)追加到三位。表達(dá)式?/\.([0-9]{0,2})\s/?表示的是以小數(shù)點(diǎn)(\.)開始以空格符號(hào)(\s)結(jié)尾,且中間包含不多于2個(gè)({,2})的數(shù)字 0-9,識(shí)別小數(shù)部分后在最后追加一個(gè) “0”,以達(dá)到補(bǔ)足小數(shù)位數(shù)的效果。其中的?{,2} 表示前一個(gè)方括號(hào)代表的字符可以重復(fù)2次及以下。第6行代碼則識(shí)別具體的數(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]+/ 表示以 “::” 開頭到空格符號(hào)截止的一段文本。其中的?[^\s] 表示除了空格符號(hào)(\s)以外的所有符號(hào),符號(hào) “+” 表示一個(gè)或更多,相當(dāng)于?{1,} 的表達(dá)。
(7) 序列
甚至可以對(duì)長文本序列進(jìn)行特定數(shù)位的截?cái)啵?br>
s6="please?call?01189998819991197253"s6.gsub(/([0-9]{3})/,'\1-')#>>?please?call?011-899-988-199-911-972-53
根據(jù)正則表達(dá)式的匹配原則,符合條件的文本中的部分片段不會(huì)再重新匹配,因此可以實(shí)現(xiàn)每隔固定的位數(shù)插入其他字符。
三、代碼實(shí)現(xiàn)
最后將整個(gè)過程封裝在?ApiglioToolBox 模塊中,由于ruby控制臺(tái)不支持UTF8輸入,所以直接復(fù)制進(jìn)控制臺(tái)時(shí)不能使用帶漢字的代碼,所以這里直接使用了轉(zhuǎn)義符號(hào):
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"#替換選項(xiàng)(正則表達(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
使用這個(gè)替換工具只需要在控制臺(tái)輸入:
ApiglioToolBox.file_rep而后只需要根據(jù)彈出的對(duì)話框進(jìn)行相應(yīng)的設(shè)置即可。當(dāng)然彈出對(duì)話框也未必總是那么方便,因此還可以通過調(diào)用?.file_rep_func 方法,通過給定參數(shù)來避免在對(duì)話框中設(shè)置參數(shù):
ApiglioToolBox.file_rep_func(input,output,old_pattern,new_pattern)#input 輸入的文本文檔#output 輸出的文本文檔#old_pattern?需要替換的內(nèi)容(用雙引號(hào)包括)#????????????需要替換內(nèi)容的正則表達(dá)式(用斜杠包括)#new_pattern?替換后的內(nèi)容(如不想使用轉(zhuǎn)義符號(hào)則使用單引號(hào))#????????????替換內(nèi)容可以增加\1?\2?\3?來引用匹配到的文本
以下是文本替換工具的使用效果:

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

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