用SketchUp替换文本内容
SketchUp 中的 ruby 控制台作为自动化操作的利器,自然也可以进行最基本的文档内容修改任务。因此本篇使用 SketchUp 自带的 ruby 控制台制作一个文本文档的替换小工具。
编程无疑是将自己从重复性工作中解放出来的好办法,但是对于完全的新手来说,某些编程工具在安装这一步就相当让人迷惑,以至于一开始就劝退很多人。就像在Windows中,谁都可以直接使用一些MS-DOS批处理指令或VBS脚本,对于安装了 SketchUp 的用户来说,ruby 控制台也是一个拿来即用的编程工具。或许 SketchUp 里的 ruby 只是一个简陋的版本,但这的确节省了不少在安装编程环境上的时间。
*这是“SketchUp不务正业系列”中的一篇,文中浅紫色文字为“不务正业”对“正业”的借鉴参考。
【本期目录】 | |
一、替换文本的流程 二、正则表达式 三、代码实现 | 这是一个极其简单的功能,几乎完全不需要自己设计任务逻辑。 |
一、替换文本的流程
文本替换的流程是这样的:
①确定原文件的路径、替换文本的规则以及结果保存的路径;
②读取文件内容;
③根据设置的规则替换内容;
④将替换后的内容保存到新的文件中。
新旧文件路径的选择可以使用最原始的输入地址的方法,但是对于文件路径比较复杂的情况来说,直接在窗口中选择文件还是要方便一些。因此这一步需要使用 SketchUp 提供的打开和保存文件的对话框:
filename=UI.openpanel("title")
#>> filename为选择的文件名
# 如果取消filename则为nil
执行以上代码可以打开系统默认的“打开”对话框,窗口的标题可以自己更改:
保存路径的选择也类似于“打开”对话框:
filename=UI.savepanel("title")
#>> filename为选择的文件名
二者的区别在于“保存”对话框可以设置新文件,且选择已有的文件会有覆盖警告;而“打开”对话框只能选择存在的文件。
确定了文件路径后是读取文件,可以使用以下代码:
str1=File.read(input_filename)
#正常运行后str1将保存文件中的所有内容
#类型为String
将字符串保存到新的文件中,则需要使用如下的代码:
f=File.open(filename,"w")
f.write(str2)
f.close()
以上代码中:第1行 .open 方法表示创建文件,其中的第二个参数 "w" 表示允许覆盖创建。第2行的 .write 方法表示将 str2 写入文件中,第3行的 .close 方法保存文件。
最后是文本替换部分,这本是一个很复杂的任务逻辑,但是ruby有自带的方法能够实现这个功能,唯一要做的就是了解字符串类的 .gsub 方法:
str2 = str1.gsub(op,np)
简单来说,这个方法就是以参数规定的规则替换 str1 中的特定内容,然后将结果返回给 str2。如果两个参数都是字符串,那么规则就是将文本中所有 op 替换成 np。如果参数 op 是正则表达式,那么替换的规则将更加复杂且灵活。
二、正则表达式
关于正则表达式,这里只提供一些感性的例子,详细的语法规则和使用方法不是本篇文章能够概括的。可以参考以下两个网址:
https://ruby-doc.org/core-2.7.1/String.html#method-i-gsub
https://www.runoob.com/regexp/regexp-tutorial.html
(1) 简易替换
这种用法和系统自带的记事本替换功能没有区别,但是需要规避某些有特殊含义的符号:
图中的 "\xe7\x94\xa8\xe5\x9c\xb0" 表示“用地”,这么写是因为 ruby 控制台的输入不是 UTF-8 编码,所以会出现编码错误。如果需要使用中文则需要使用 load 方法调用写好的 *.rb 文件
(2) 编号识别替换
对于有明确规则的编号,正则表达式可以找出这些编号并加以利用。
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])
以上代码中的正则表达式 /pid=([0-9,A-F]{6})/ 含义为:以 “pid=” 开始包含 6 位十六进制数位([0-9,A-F])的片段, {6} 表示前一个方括号表示的字符的重复次数为 6。圆括号则用于替换,第一个圆括号所包含的匹配字段用 \1 表示,如果有更多圆括号,则以此类推使用 \2、 \3、 \4……所以最终的效果便是将诸如 “pid=000022” 的片段替换为 “^([000022])”。
另外需要注意,例子中第二个参数使用了单引号,双引号则需要写成 "^([\\1])" ,因为双引号字符串中的反斜杠会被当做转义符号解读。
(3) 断行处理
断行是文本文档与表格文件对接的重要字符,找到符合条件的断点非常关键:
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" 使用了两种引号就是为了在适当的时候使用转义符号,也可以直接使用 "\\1\r\n" 这样的表达。表达式 /(\s[0-9]+h)/ 表示以空格符号(\s)开始以字符 “h” 结尾,中间有一个或多个(+)数字 0-9 的文本片段。
(4) 调换顺序
还可以根据具体要求调换局部文本的顺序:
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
以上代码中正则表达式包含两个圆括号,因此替换字符串可以同时引用 \1 和 \2。表达式 /([0-9\.]+)([NEWS])/ 表示以一个或多个(+)数字 0-9 开始,后跟随小数点(\.),而后以 NEWS 的其中一个字符结尾的文本片段。
(5) 数字识别处理
使用正则表达式还可以很方便的提取出数字文本:
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.500
s5="lingling 40 hours 3424"
s5.gsub(/([0-9]+)(\shours)/,'\1 minutes')
#>> lingling 40 minutes 3424
以上代码第2行通过连续两次 .gsub 方法将小数点后位数追加到三位。表达式 /\.([0-9]{0,2})\s/ 表示的是以小数点(\.)开始以空格符号(\s)结尾,且中间包含不多于2个({,2})的数字 0-9,识别小数部分后在最后追加一个 “0”,以达到补足小数位数的效果。其中的 {,2} 表示前一个方括号代表的字符可以重复2次及以下。第6行代码则识别具体的数量单位并替换。
(6) 单词替换
正则表达式也可以可以根据单词长度或者其他规则自制填空题:
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.
文中的表达式 /::[^\s]+/ 表示以 “::” 开头到空格符号截止的一段文本。其中的 [^\s] 表示除了空格符号(\s)以外的所有符号,符号 “+” 表示一个或更多,相当于 {1,} 的表达。
(7) 序列
甚至可以对长文本序列进行特定数位的截断:
s6="please call 01189998819991197253"
s6.gsub(/([0-9]{3})/,'\1-')
#>> please call 011-899-988-199-911-972-53
根据正则表达式的匹配原则,符合条件的文本中的部分片段不会再重新匹配,因此可以实现每隔固定的位数插入其他字符。
三、代码实现
最后将整个过程封装在 ApiglioToolBox 模块中,由于ruby控制台不支持UTF8输入,所以直接复制进控制台时不能使用带汉字的代码,所以这里直接使用了转义符号:
module ApiglioToolBox
def 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()
end
def 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"
#选择需要替换内容的文档
save_tip="\xe6\x9b\xbf\xe6\x8d\xa2\xe7\xbb\x93\xe6\x9e\x9c\xe4\xbf\x9d\xe5\xad\x98\xe4\xb8\xba"
#替换结果保存为
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"
#替换选项(正则表达式)
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"#正则表达式
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 patterns
return false if patterns[0]==""
case patterns[2]
when mo_reg
patterns[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_ord
puts "ApiglioToolBox.file_rep_func('"+filename+"','"+targetname+"',\""+patterns[0]+"\",\""+patterns[1]+"\")"
end
file_rep_func(filename,targetname,*patterns[0..1])
true
end
end
使用这个替换工具只需要在控制台输入:
ApiglioToolBox.file_rep
而后只需要根据弹出的对话框进行相应的设置即可。当然弹出对话框也未必总是那么方便,因此还可以通过调用 .file_rep_func 方法,通过给定参数来避免在对话框中设置参数:
ApiglioToolBox.file_rep_func(input,output,old_pattern,new_pattern)
#input 输入的文本文档
#output 输出的文本文档
#old_pattern 需要替换的内容(用双引号包括)
# 需要替换内容的正则表达式(用斜杠包括)
#new_pattern 替换后的内容(如不想使用转义符号则使用单引号)
# 替换内容可以增加\1 \2 \3 来引用匹配到的文本
以下是文本替换工具的使用效果:
替换前后文件的内容如下:
正业 这是一个与建模完全无关的小工具,只是利用 SketchUp 中的 ruby 控制台的图灵完备进行一些简易的编程。其存在的意义在于:很多自动化任务只需要非常简单的技术手段,但是为此 beginner 却需要安装整套的语言环境,这是相当繁琐甚至没有必要的。ruby 控制台完全省去了安装开发环境的门槛,不需要额外进行任何准备,再加上 ruby 本身的语法特点,使得这类小功能可以很轻松很愉悦的实现,对于本就安装了 SketchUp 的用户来说十分便利。
另外,这个工具使用了 UI 模块中的几个对话框方法(其实就是系统的对话框)来串联整个工具的逻辑,甚至可以给它设置一个 Toolbar 使之变成一个按键,成为典型意义上的插件功能。但是这种连续跳好几个对话框的处理方式还是太过冗杂,更合适的做法是使用 UI:: HtmlDialog 做类似于ArcToolBox 的窗口,UI 逻辑会明显清晰得多。正如本系列创作的初衷,虽然名为“不务正业”,但怪异旁门的方法也能够给“正统”的使用方式以参考借鉴。
(都看到这了,点个赞呗)
本篇文章是“SketchUp不务正业系列”的其中一篇,编号为SU-2021-S02,更多与 SketchUp Ruby有关的其他文章可以点击公众号菜单中的“SU Ruby”选项获得文章的目录。