效率工具篇-文本处理
Processing 和 Openframeworks 除了可以画画,同样能胜任文本处理的工作。我们可以用它给为自己定制一些提高工作效率的工具。下面以 Openframeworks 为例,分享一段有关文本处理的代码。
这段代码,源于自己的某个需求 - 对文档进行拆分。
最近在都在进行 Processing 和 Openframeworks 的教程写作,这两个部分的内容许多都是相通的。所以一直以来都在同一个文档中合写两个版本,等到后期再进行拆分。
这样做的好处有两点:一目了然,便于比较。而且其中一个版本修改了,另一个也能同时顾及。
如果每次都用手动的方式去复制粘贴,就会非常麻烦。所以设计了一种拆分的语法,让程序来完成这类工作。
词汇标记
这里设计了两类语法。一是对词汇的标记。假如某些词是 Processing 中特有,就会在词汇前加上“ (P5: ” 作为前缀,而 “* ) ” 会作为后缀。若是 Openframeworks 中特有,就会在词汇前加上“ (OF: ” 作为前缀, 并同样以 “* ) ” 作为后缀。
下面截取了一段先前的文章,这是它的原始版本。
下面开始介绍一种专门用于储存色彩的数据类型 - (P5:color*)(OF:ofColor*)。
它接近于前面提到的 (P5:boolean*)(OF:bool*) ,int,float 这类数据类型。
经过程序自动拆分后,便会生成如下两个版本:
(Processing 版)
下面开始介绍一种专门用于储存色彩的数据类型 - color。
它接近于前面提到的 boolean,int,float 这类数据类型。
(Openframeworks 版)
下面开始介绍一种专门用于储存色彩的数据类型 - ofColor。
它接近于前面提到的 bool,int,float 这类数据类型。
段落标记
另一个则是对段落的标记,可用于替换大段的代码示例。例如 Processing 的示例,前面可以以 “ -(P5begin) “ 开头,以” -(P5end) “作为结尾。Openframeworks 的示例则以” -(OFbegin) “开头,以” -(OFend) “作为结尾。
下面是原始版本:
代码示例(9-1):
-(P5begin)
int r,g,b;
void setup(){
size(400,400);
r = 255;
g = 0;
b = 0;
}
void draw(){
background(0);
rectMode(CENTER);
fill(r,g,b);
rect(width/2,height/2,100,100);
}
-(P5end)
-(OFbegin)
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
int r,g,b;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(400,400);
r = 255;
g = 0;
b = 0;
}
void ofApp::update(){
}
void ofApp::draw(){
ofBackground(0);
ofSetRectMode(OF_RECTMODE_CENTER);
ofSetColor(r,g,b);
ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,100,100);
}
-(OFend)
程序拆分后这两段便会独立地归到各自的文章当中。
文本处理源代码
下面是正式代码:
— ofApp.h 中 —
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
string replaceStr(string loadStr,string keyWords,string replaceWords);
string replaceStr(string loadStr,string start,string end,string replaceWords);
void keyPressed(int key);
void keyReleased(int key);
void mouseMoved(int x, int y );
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
string myStr,reStr,P5_Str,OF_Str;
};
— ofApp.cpp 中 —
void ofApp::setup(){
// 读取原始文件
ofBuffer buffer = ofBufferFromFile("原始版.md");
myStr = buffer.getText();
// 文本替换测试
reStr = replaceStr(myStr,"基本知识","今天");
ofBuffer msg(reStr.c_str(), reStr.length());
// 导出为 markdown 格式
ofBufferToFile("字符替换测试.md", msg);
// 导出 P5 版
P5_Str = replaceStr(myStr,"-(OFbegin)","-(OFend)","");
P5_Str = replaceStr(P5_Str,"-(P5begin)","");
P5_Str = replaceStr(P5_Str,"-(P5end)","");
P5_Str = replaceStr(P5_Str,"(OF:","*)","");
P5_Str = replaceStr(P5_Str,"(P5:","");
P5_Str = replaceStr(P5_Str,"*)","");
ofBuffer msg1(P5_Str.c_str(), P5_Str.length());
ofBufferToFile("Processing版.md", msg1);
// 导出 OF 版
OF_Str = replaceStr(myStr,"-(P5begin)","-(P5end)","");
OF_Str = replaceStr(OF_Str,"-(OFbegin)","");
OF_Str = replaceStr(OF_Str,"-(OFend)","");
OF_Str = replaceStr(OF_Str,"(P5:","*)","");
OF_Str = replaceStr(OF_Str,"(OF:","");
OF_Str = replaceStr(OF_Str,"*)","");
ofBuffer msg2(OF_Str.c_str(), OF_Str.length());
ofBufferToFile("Openframeworks版.md", msg2);
}
void ofApp::draw(){
ofBackground(0);
}
string ofApp::replaceStr(string loadStr,string keyWords,string replaceWords){
string newStr;
int num = 0; // 可统计词数
for(int i = 0;i < loadStr.size();i++){
// 激活标志
bool match = true;
if(loadStr[i] == keyWords[0]){
for(int j = 1;j < keyWords.size();j++){
if(loadStr[i + j] == keyWords[j]){
match = true;
}else{
match = false;
j = keyWords.size();
}
}
}else{
match = false;
}
if(match){
newStr.append(replaceWords);
i += keyWords.size() - 1; // 作用是跳过
num++;
}else{
string temp = ofToString(loadStr[i]);
newStr.append(temp);
}
}
cout << num << endl;
return newStr;
}
string ofApp::replaceStr(string loadStr,string start,string end,string replaceWords){
string newStr;
int num = 0; // 可统计词数
for(int i = 0;i < loadStr.size();i++){
// 激活标志
bool matchA = true;
if(loadStr[i] == start[0]){
for(int j = 1;j < start.size();j++){
if(loadStr[i + j] == start[j]){
matchA = true;
}else{
matchA = false;
j = start.size();
}
}
}else{
matchA = false;
}
if(matchA){
i += start.size() - 1; // 作用是跳过
// 寻找下一个标记,计算距离
int index = 0;
bool matchB = false;
while(!matchB){
index++;
if(loadStr[i + index] == end[0]){
for(int j = 1;j < end.size();j++){
if(loadStr[i + index + j] == end[j]){
matchB = true;
}else{
matchB = false;
j = end.size();
}
}
}else{
matchB = false;
}
}
i += index + end.size() - 1;
newStr.append(replaceWords);
num++;
}else{
string temp = ofToString(loadStr[i]);
newStr.append(temp);
}
}
cout << num << endl;
return newStr;
}
导出文件:
代码浅析
标记语法是自己定义的。只要简洁好记,并且不容易和文本内容混淆即可。
由于原始文档是用 markdown 格式去写的,因而使用时需要将原始文件命名为“ 原始版.md ”,并放到 data 文件夹中。Openframeworks 也可以处理 txt 文档,只要在源代码中将后缀修改成 “.txt” 即可
只要执行一次程序,最终便会在data文件夹生成三个文本,一个用作词汇的替换测试,一个是 Processing 版,一个是 Openframeworks 版
ofBuffer 是专门用于存取文件的类型。ofBufferFromFile 用于读取文件,ofBufferToFile 用于保存文件
replaceStr 是自定义函数,适用于中英文本的处理。有两种重载方法,参数数量为 3 时,可替换关键字。第一个参数传入文本 string,第二个参数传入希望被替换的关键字,第三个参数传入替换字符。而当参数数量为 4 时,可替换整个段落。第一个参数传入文本 string,第二个参数传入段落标记的前缀,第三个参数传入段落标记的后缀,第四个参数传入替换后的段落。
另一种应用-简单的代码转换器
利用上面的函数,转换下思路。就可以做一个代码转换器,例如将 openframeworks 的代码转换成 processing 的代码。
代码示例
— ofApp.h 内声明
string myStr,newStr;
— ofApp.cpp 内声明
void ofApp::setup(){
// 读取文件
ofBuffer buffer = ofBufferFromFile("ofApp.cpp");
myStr = buffer.getText();
newStr = replaceStr(myStr,"#include \"ofApp.h\""," ");
newStr = replaceStr(newStr,"ofDrawRectangle","rect");
newStr = replaceStr(newStr,"ofBackground","background");
newStr = replaceStr(newStr,"ofSetWindowShape","size");
newStr = replaceStr(newStr,"void ofApp::keyPressed(int key)","void keyPressed()");
newStr = replaceStr(newStr,"void ofApp::keyReleased(int key)","void keyReleased()");
newStr = replaceStr(newStr,"void ofApp::mouseMoved(int x, int y )","void mouseMoved()");
newStr = replaceStr(newStr,"void ofApp::mouseDragged(int x, int y, int button)",
"void mouseDragged()");
newStr = replaceStr(newStr,"void ofApp::mousePressed(int x, int y, int button)",
"void mousePressed()");
newStr = replaceStr(newStr,"void ofApp::mouseReleased(int x, int y, int button)",
"void mouseReleased()");
newStr = replaceStr(newStr,"void ofApp::mouseEntered(int x, int y)","void mouseEntered()");
newStr = replaceStr(newStr,"void ofApp::mouseExited(int x, int y)","void mouseExited()");
newStr = replaceStr(newStr,"void ofApp::windowResized(int w, int h){"," }"," ");
newStr = replaceStr(newStr,"void ofApp::gotMessage(ofMessage msg){"," }"," ");
newStr = replaceStr(newStr,"void ofApp::dragEvent(ofDragInfo dragInfo){"," }"," ");
newStr = replaceStr(newStr,"void ofApp::","void ");
ofBuffer msg(newStr.c_str(), newStr.length());
// 导出为 pde 格式
ofBufferToFile("p5export.pde", msg);
}
这是原始的 cpp 文件
这是转换后的 pde 文件(Processing 工程文件)
对于一些简单的程序,有了转换器以后就会非常方便。我们可以基于上面的代码进行扩写,添补更多的函数。
END
对写程序而言,学会复用非常重要,磨刀不误砍柴工。当你把一些流程化的步骤打包好,就能一劳永逸。用得越多,节省的时间就越多。像之前制作的 Gif 模块。如果这件事你只打算做一两次,那可以无需用代码,而是对程序直接录屏,再用别的工具将视频生成 gif 即可。
但如果你确定这个事情是之后会持续去做的,前期就可以花些功夫去探索。结合一些库,把它做成模块,以后就变成几行代码的事情了。假设使用这个模块,每个练习生成一次能节省 5 分钟,那 200 个就能节省 1000 分钟。
当然,换个角度。哪怕花时间一样,甚至略多。我认为都是值得去做的。因为你不是做重复性的枯燥劳动。而是在做一些探索性的,有创造力的工作。
这个思路同样适用于创作。自己一直把 “复用一切” 放到最高的位置。练习不是孤立的,不是为了最终生成那几秒动画才去写它。从大量的练习里,你可以看到图形之间的共性,最终就能进行整合,抽象成一个系统。
所以,尝试把一些东西写成函数,写成类,写成库吧,你就可以有千军万马任意差遣。有这些积累,也就能有更多的精力,去展现头脑中更精彩的画面。
(以上实例可通过点击阅读原文进行下载)
()
实例名称:DivideText,OpenframeworksToProcessing