コラム
2026/01/21
プログラミングについて 第124回目
『作っておくと便利なちょっとしたプログラム その8』
今回は前回の続きです。プログラム全体のリストは最後に掲載します。
さっそく前回の続きの関数 get_next_item です。これはちょっと厄介です。ダブルクォーテーションで囲まれている文字列がないという前提であれば簡単なのですが、この文字列があるおかげでグッと面倒になります。
文字列内のダブルクォーテーションがバックスラッシュでエスケープされる場合を次の文字列で考えてみましょう。
"a兎bc\"\\123\狸45",
分かりやすくするためにこのテキストを縦に並べて説明します。
" ダブルクォーテーションなのでここから文字列が開始。
a ダブルクォーテーションでもバックスラッシュでもない。
兎 ダブルクォーテーションでもバックスラッシュでもない。
b ダブルクォーテーションでもバックスラッシュでもない。
c ダブルクォーテーションでもバックスラッシュでもない。
\ バックスラッシュなので、次の1文字はチェックしない。
"
\ バックスラッシュなので、次の1文字はチェックしない。
\
1 ダブルクォーテーションでもバックスラッシュでもない。
2 ダブルクォーテーションでもバックスラッシュでもない。
3 ダブルクォーテーションでもバックスラッシュでもない。
\ バックスラッシュなので、次の1文字はチェックしない。
狸
4 ダブルクォーテーションでもバックスラッシュでもない。
5 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションなのでここで文字列が終わり。
, コンマなので項目の区切り。
基本的にこのように動作すれば良い訳です。2バイト文字が含まれる可能性もあるので1バイト文字であるか否かも判断しなければなりません。バックスラッシュのときにも次の1文字をチェックしないとは言っても、次の文字が1バイト文字か否かはチェックしなければなりません。ここで1つ落とし穴があります。バックスラッシュの次の文字が1バイト文字だったらなんでもいいのかというとそうではないのです。ヌルである可能性もあるのです。本来はあってはなりませんが、テキストエディタなどで作成するとどうしてもミスが発生する可能性があるのです。もし、このチェックをしないと、それを越えていってしまいますので、とんでもない文字列になってしまう可能性がありますので注意が必要です。
"a兎bc""123"狸45",
次に連続するダブルクォーテーションで表現される場合です。
" ダブルクォーテーションなのでここから文字列が開始。
a ダブルクォーテーションでもバックスラッシュでもない。
兎 ダブルクォーテーションでもバックスラッシュでもない。
b ダブルクォーテーションでもバックスラッシュでもない。
c ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションが連続しているので次の文字はチェックしない。
"
1 ダブルクォーテーションでもバックスラッシュでもない。
2 ダブルクォーテーションでもバックスラッシュでもない。
3 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションなのでここから文字列が開始。
狸 ダブルクォーテーションでもバックスラッシュでもない。
4 ダブルクォーテーションでもバックスラッシュでもない。
5 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションが連続していないのでここで文字列が終わり。
, コンマなので項目の区切り。
ということになるのですが、本来から言えばこれは変な文字列です。しかしながらこんなものでも処理できた方が使う方は安心です。
これを踏まえて次を見て下さい。
char *get_next_item(data,item)
char *data,*item;
{
char *s,*ss,c;
int is_text;
is_text=0; /* 文字列のときには 1、そうでないときには 0 */
item[0]='\0';
s=data;
while(1){
if(_ismbblead(*s)){ /* 2バイト文字。この関数は Visual C の文字
分類関数で、2バイト文字の第1バイト目のときに
真 */
s+=2;
}
/* 文字が文字列を囲むものである */
else if(*s==text_enclose_char){
s++; /* 次の文字にポインタを移動 */
/* 文字列を囲む文字と文字列内のエスケープ文字が同じ */
if(text_enclose_char==text_escape_char){
/* 現在、文字列でないときには文字列の設定をする */
if(!is_text )is_text=1;
/* 現在の文字がエスケープ文字のときには、次の文字にポ
インタを移動 */
else if(*s==text_escape_char)s++;
/* エスケープ文字と同じでないので文字列の終了を設定 */
else is_text=0;
}
/* 文字列を囲む文字と文字列内のエスケープ文字が違うと
きには文字列か否かの値を反転させる */
else{
is_text^=1;
}
}
/* 文字列内で、文字がヌルでないとき */
else if(is_text && *s){
/* 文字がエスケープ文字のとき。ここは文字列を囲む文字
とエスケープ文字が違うときにだけ真になる可能性があ
る */
if(*s==text_escape_char){
if(*++s){ /* 次の文字がヌルでないときにだけ次の文字が1バイト文
字か否かを調べ、次の文字にポインタを移動 */
if(_ismbblead(*s))s+=2;
else s+=1;
}
}
else{ /* ここは1バイト文字しかありませんので、次の文字にポ
インタを移動 */
s++;
}
}
/* 文字がコンマかヌルのとき。ヌルになるのは文字列の表
現に誤りがあるときに可能性がある */
else if(*s==',' || *s=='\0' ){
/* 現在の文字を覚えておいて、その位置の文字をヌルにし
項目の文字列を取り出す */
c = *s;
*s='\0';
item=new_strcat(item,data);
/* 元の文字がコンマのときには、それ以降の文字列を先頭
からコピーし、そうでないときには先頭をヌルにする */
if(c==',')strcpy(data,s+1);
else data[0]='\0';
break;
}
else{ /* 文字列外のとき。ここは1バイト文字しかありません */
s++;
}
}
/* 項目の文字列へのポインタを返す */
return item;
}
というようになります。じっくりと読んで理解して下さい。この関数にはもう少し付け加えることがあります。コンマの前後のスペースの削除の指定があるときには、項目の先頭と末尾のスペースを削除する機能をこの関数に加えます。return item; の前に次のコードを追加します。
/* スペースの削除の指定がないときにはリターンする */
if(!ignore_terminal_space)return item;
/* 先頭の空白を削除します */
/* 文字を先頭から調べていき、空白以外の文字になったら
ループを終了して、その位置からの文字列を先頭に移動
する */
s=item;
while(*s){
if(isSJISspace(s))s+=2;
else if(isspace(*s) )s+=1;
else break;
}
if(s!=item)strcpy(item,s);
/* 末尾の空白を削除します */
/* 文字列を先頭から最後まで調べていき、それ以降が空白
文字である位置を取得します。ループ終了後にその位置
を取得されていれば、その位置の文字をヌルにする */
ss=NULL;
s =item;
while(*s){
if(_ismbblead(*s)){
if(isSJISspace(s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=2;
}
else{
if(isspace(*s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=1;
}
}
if(ss)*ss='\0';
これもじっくりと読んで理解して下さい。これでソースデータのロードが終わりになります。次に表示です。表示の前にもう1つやっておくことがあります。各項目の最大文字列長を調べておくことです。関数 isspace 標準関数の1つで半角の空白文字か否かを調べるものです。関数 isSJISspace は、シフトJISの全角の空白文字か否かを判定するもので、
int isSJISspace(char *s)
{
unsigned char *us;
us=(unsigned char *)s;
if(*us==0x81 && *(us+1)==0x40)return 1;
else return 0;
}
という内容です。色々とやってみたのですが、安直にシフトJISの空白の文字コードであるか否かを調べることにしました。
メイン関数の close_source の次に
make_form_table();
を追加します。この関数で各項目の最大文字列長を調べ、各項目に対する表示形式のテーブルを作成します。表示形式のテーブルは、文字列の配列で各文字列は printf 関数で使用するパターンを入れます。
テーブルは、
char **form_table=NULL;
と関数外で定義しておきます。
関数 make_form_table は、最初に項目数の最大値を調べ、次にその個数分の int 型の配列を作成して各項目の最大文字列長をその配列に入れていきます。最後に form_table用のメモリーを確保し、そこに printf 関数で使うパターンを入れていきます。
make_form_table()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int total,i,l;
char form[21];
/* 項目の最大数を調べます */
total=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/* 各項目の最大文字列長を代入するテーブルを
作成し、初期化する */
length_table=NEW(int,total);
for(i=0;i<total;i++)length_table[i]=0;
/* 各項目の最大文字列長をテーブルに設定する */
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
l=strlen(dtc->text);
if(l>length_table[i])length_table[i]=l;
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/* フォームテーブルを作成します。1つ多めに
メモリーを確保するのは、最後の要素にヌル
を入れておき、解放するときの門番に使用
するためです */
form_table=NEW(char *,total+1);
for(i=0;i<total;i++){
if(proc_mode==PROC_MODE_EXPAND){
if(display_mode==LEFT_ADJUST)sprintf(form,"%%-%ds",length_table[i]);
else sprintf(form,"%%%ds" ,length_table[i]);
}
else{
strcpy(form,"%s");
}
form_table[i]=new_strcat(NULL,form);
}
form_table[i]=NULL; /* 最後の要素に門番としてヌルを入れます */
DELETE(length_table);
return 1;
}
ここまでできれば後はもう少しです。
メイン関数の make_form_table(); の後に、
display();
を追加し、表示部分を作ります。
display()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int i;
char form[21];
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
/* dtc が dhc->data_text と違うときには2つめ
以降の項目ですのでコンマを表示します。この
ときコンマの前後にスペースを挿入する指定が
あるときにはコンマの前にスペースを表示しま
す */
if(dtc!=dhc->data_text){
if(display_add_space)putchar(' ');
putchar(',');
}
/* dtc->next がヌルでないときは最後の項目では
ありません。このとき、コンマの前後にスペー
スを入れる指定があるときには項目の文字列の
前にスペースを表示します。項目はフォームテ
ーブルのものをそのまま使用します */
if(dtc->next){
if(display_add_space)putchar(' ');
printf(form_table[i],dtc->text);
}
/* 最後の項目の処理です。最後の項目に文字があ
るときしか表示は行ないません */
else if(dtc->text[0]){
if(display_add_space)putchar(' ');
/* 左詰めのときには文字列のみを表示し、
そうでないときにはフォームテーブルのものを
使用します */
if(display_mode==LEFT_ADJUST)printf("%s",dtc->text);
else printf(form_table[i],dtc->text);
}
i++;
dtc=dtc->next;
}
/* 1行の終わりに改行をします */
putchar('\n');
dhc=dhc->next;
}
return 1;
}
そして、表示の終了後に各メモリーの解放をするだけです。ここまでで殆ど完成です。
最後にヘルプの表示機能、とメモリーの解放を付け加えた全リストです。今回も私のリストそのままとしありますので簡単なコメントも残っています。タブは4タブのスペースに変換してあります。関数の順番はバラバラです。
#define VERSION_NUMBER "v1.0"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <mbctype.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define READ_BUF_SIZE 8192
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
char *fname=NULL;
FILE *fp=stdin;
struct data_text_strct{
char *text;
struct data_text_strct *next;
};
struct data_head_strct{
struct data_text_strct *data_text;
struct data_head_strct *next;
};
struct data_head_strct *data_head_top=NULL;
struct data_head_strct *data_head_end=NULL;
struct data_text_strct *data_text_end=NULL;
char **form_table=NULL;
char *new_strcat(char *string1,char *string2);
char *get_next_item(char *data,char *item);
int isSJISspace(char *s);
/*==================================== main ===================================
* Main routine.
*/
void main(argc,argv)
int argc;
char *argv[];
{
/*
* " Analize argment "
*/
if(analize_argment(argc,argv)== -1)goto end;
/*
* " Process "
*/
if(proc_mode==PROC_MODE_HELP){
display_help();
}
else{
if(proc_mode==PROC_MODE_COMPRESS){
ignore_terminal_space=1;
display_add_space=0;
display_mode=LEFT_ADJUST;
}
if(open_source()== -1)goto end;
if(load_source()== -1)goto end;
close_source();
make_form_table();
display();
purge_data();
purge_form_table();
}
/*
* " End of program "
*/
end:
exit(0);
}
/*================================= new_strcat ================================
* strcat.
*/
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
/*================================ open_source ================================
* Open source.
*/
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
/*================================ close_source ===============================
* Close source.
*/
close_source()
{
if(fname)fclose(fp);
return 1;
}
/*================================ load_source ================================
* Load source data.
*/
load_source()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *text;
int is,ie;
text=NULL;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
text=new_strcat(text,data+is);
is=ie+1;
if(ie==ldata)break;
text=new_strcat(text,",");
add_source(text);
text[0]='\0';
}
}
DELETE(text);
return 1;
}
/*================================ isSJISspace ================================
* Is space of SJIS.
*/
int isSJISspace(char *s)
{
unsigned char *us;
us=(unsigned char *)s;
if(*us==0x81 && *(us+1)==0x40)return 1;
else return 0;
}
/*================================= add_source ================================
* Add source.
*/
add_source(data)
char *data;
{
char *item;
add_new_data_head();
item=new_strcat(NULL,"");
while(data[0]){
item=get_next_item(data,item);
add_new_data_text(item);
}
DELETE(item);
return 1;
}
/*============================= add_new_data_head =============================
* Add new data head.
*/
add_new_data_head()
{
struct data_head_strct *dhc;
dhc=NEW(struct data_head_strct,1);
dhc->data_text=NULL;
dhc->next=NULL;
if(data_head_end==NULL)data_head_top =dhc;
else data_head_end->next=dhc;
data_head_end=dhc;
data_text_end=NULL;
return 1;
}
/*============================= add_new_data_text =============================
* Add new data item.
*/
add_new_data_text(text)
char *text;
{
struct data_text_strct *dtc;
dtc=NEW(struct data_text_strct,1);
dtc->text=new_strcat(NULL,text);
dtc->next=NULL;
if(data_text_end==NULL)data_head_end->data_text=dtc;
else data_text_end->next =dtc;
data_text_end=dtc;
return 1;
}
/*============================== make_form_table ==============================
* Make form table.
*/
make_form_table()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int total,i,l;
char form[21];
/*
* " Get maximum item number "
*/
total=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/*
* " Make length table "
*/
length_table=NEW(int,total);
for(i=0;i<total;i++)length_table[i]=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
l=strlen(dtc->text);
if(l>length_table[i])length_table[i]=l;
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/*
* " Make table array "
*/
form_table=NEW(char *,total);
for(i=0;i<total;i++){
if(proc_mode==PROC_MODE_EXPAND){
if(display_mode==LEFT_ADJUST)sprintf(form,"%%-%ds",length_table[i]);
else sprintf(form,"%%%ds" ,length_table[i]);
}
else{
strcpy(form,"%s");
}
form_table[i]=new_strcat(NULL,form);
}
form_table[i]=NULL;
DELETE(length_table);
return 1;
}
/*================================== display ==================================
* Display.
*/
display()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int i;
char form[21];
/*
* " Get maximum item number "
*/
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
if(dtc!=dhc->data_text){
if(display_add_space)putchar(' ');
putchar(',');
}
if(dtc->next){
if(display_add_space)putchar(' ');
printf(form_table[i],dtc->text);
}
else if(dtc->text[0]){
if(display_add_space)putchar(' ');
if(display_mode==LEFT_ADJUST)printf("%s",dtc->text);
else printf(form_table[i],dtc->text);
}
i++;
dtc=dtc->next;
}
putchar('\n');
dhc=dhc->next;
}
return 1;
}
/*============================== analize_argment ==============================
* Analize argment.
*/
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-c")==0
|| stricmp(argv[i],"/c")==0)proc_mode=PROC_MODE_COMPRESS;
else if(stricmp(argv[i],"-e")==0
|| stricmp(argv[i],"/e")==0)proc_mode=PROC_MODE_EXPAND;
else if(stricmp(argv[i],"-?")==0
|| stricmp(argv[i],"/?")==0){
proc_mode=PROC_MODE_HELP;
return 1;
}
else if(stricmp(argv[i],"-i")==0
|| stricmp(argv[i],"/i")==0)ignore_terminal_space=1;
else if(stricmp(argv[i],"-l")==0
|| stricmp(argv[i],"/l")==0)display_mode=LEFT_ADJUST;
else if(stricmp(argv[i],"-r")==0
|| stricmp(argv[i],"/r")==0)display_mode=RIGHT_ADJUST;
else if(stricmp(argv[i],"-t1")==0
|| stricmp(argv[i],"/t1")==0){
text_enclose_char='"' ;
text_escape_char='\\';
}
else if(stricmp(argv[i],"-t2")==0
|| stricmp(argv[i],"/t2")==0){
text_enclose_char='"';
text_escape_char='"';
}
else if(stricmp(argv[i],"-s")==0
|| stricmp(argv[i],"/s")==0)display_add_space=1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
/*=============================== get_next_item ===============================
* Get (Put) next item.
*/
char *get_next_item(data,item)
char *data,*item;
{
char *s,*ss,c;
int is_text;
is_text=0;
item[0]='\0';
s=data;
while(1){
if(_ismbblead(*s)){
s+=2;
}
else if(*s==text_enclose_char){
s++;
if(text_enclose_char==text_escape_char){
if(!is_text )is_text=1;
else if(*s==text_escape_char)s++;
else is_text=0;
}
else{
is_text^=1;
}
}
else if(is_text && *s){
if(*s==text_escape_char){
if(*++s){
if(_ismbblead(*s))s+=2;
else s+=1;
}
}
else{
s++;
}
}
else if(*s==',' || *s=='\0' ){
c= *s;
*s='\0';
item=new_strcat(item,data);
if(c==',')strcpy(data,s+1);
else data[0]='\0';
break;
}
else{
s++;
}
}
if(!ignore_terminal_space)return item;
/*
* " Reject leading space "
*/
s=item;
while(*s){
if(isSJISspace(s))s+=2;
else if(isspace(*s) )s+=1;
else break;
}
if(s!=item)strcpy(item,s);
/*
* " Reject trailing space "
*/
ss=NULL;
s =item;
while(*s){
if(_ismbblead(*s)){
if(isSJISspace(s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=2;
}
else{
if(isspace(*s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=1;
}
}
if(ss)*ss='\0';
return item;
}
/*================================ display_help ===============================
* Display help.
*/
display_help()
{
#define p printf
p(" refcom - コンマ区切りテキスト整形プログラム %s\n",VERSION_NUMBER);
p("\n");
p("使用方法:\n");
p("\n");
p(" refcom [-e|c|?] [-t1|2] [-l|r] [-i] [-s] [ソースファイル名]\n");
p("\n");
p(" -e 見やすいように拡張して表示します。\n");
p("\n");
p(" -c コンマ間のスペースを削除(圧縮)して表示します。\n");
p("\n");
p(" -? 本ヘルプを表示します。\n");
p("\n");
p(" ∴ -e、-c、-? のいずれかのオプションも指定されないときには -e として\n");
p(" 表示します。\n");
p("\n");
p(" -t1 文字列の内のダブルクォーテーションは \\\" と表現されているテキスト\n");
p(" を処理します。\n");
p("\n");
p(" -t2 文字列の内のダブルクォーテーションは \"\" と表現されているテキスト\n");
p(" を処理します。\n");
p("\n");
p(" -l -e のときには左詰めで表示します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -r -e のときには右詰めで表示します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -i -e のときにはコンマの前後のスペースを無視します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -s -e のときにはコンマの前後に1つスペースを挿入します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" ソーステキストファイル名\n");
p(" 処理するソーステキストファイルの名称。本パラメータを省略すると標準入力か\n");
p(" らの読み込みになります。\n");
return 1;
#undef p
}
/*================================= purge_data ================================
* Purge datas.
*/
purge_data()
{
struct data_head_strct *dhc,*dhn;
struct data_text_strct *dtc,*dtn;
dhc=data_head_top;
while(dhc){
dhn=dhc->next;
dtc=dhc->data_text;
while(dtc){
dtn=dtc->next;
DELETE(dtc->text);
DELETE(dtc );
dtc=dtn;
}
DELETE(dhc);
dhc=dhn;
}
data_head_top=NULL;
data_head_end=NULL;
data_text_end=NULL;
return 1;
}
/*============================== purge_form_table =============================
* Purge form table.
*/
purge_form_table()
{
int i;
for(i=0;form_table[i];i++)DELETE(form_table[i]);
DELETE(form_table);
return 1;
}
短そうでも書いてみると意外とプログラムは長くなるものですね。
ではまた次回。
第124回 プログラミングについて『作っておくと便利なちょっとしたプログラム その8』
プログラミングについて 第124回目
『作っておくと便利なちょっとしたプログラム その8』
今回は前回の続きです。プログラム全体のリストは最後に掲載します。
さっそく前回の続きの関数 get_next_item です。これはちょっと厄介です。ダブルクォーテーションで囲まれている文字列がないという前提であれば簡単なのですが、この文字列があるおかげでグッと面倒になります。
文字列内のダブルクォーテーションがバックスラッシュでエスケープされる場合を次の文字列で考えてみましょう。
"a兎bc\"\\123\狸45",
分かりやすくするためにこのテキストを縦に並べて説明します。
" ダブルクォーテーションなのでここから文字列が開始。
a ダブルクォーテーションでもバックスラッシュでもない。
兎 ダブルクォーテーションでもバックスラッシュでもない。
b ダブルクォーテーションでもバックスラッシュでもない。
c ダブルクォーテーションでもバックスラッシュでもない。
\ バックスラッシュなので、次の1文字はチェックしない。
"
\ バックスラッシュなので、次の1文字はチェックしない。
\
1 ダブルクォーテーションでもバックスラッシュでもない。
2 ダブルクォーテーションでもバックスラッシュでもない。
3 ダブルクォーテーションでもバックスラッシュでもない。
\ バックスラッシュなので、次の1文字はチェックしない。
狸
4 ダブルクォーテーションでもバックスラッシュでもない。
5 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションなのでここで文字列が終わり。
, コンマなので項目の区切り。
基本的にこのように動作すれば良い訳です。2バイト文字が含まれる可能性もあるので1バイト文字であるか否かも判断しなければなりません。バックスラッシュのときにも次の1文字をチェックしないとは言っても、次の文字が1バイト文字か否かはチェックしなければなりません。ここで1つ落とし穴があります。バックスラッシュの次の文字が1バイト文字だったらなんでもいいのかというとそうではないのです。ヌルである可能性もあるのです。本来はあってはなりませんが、テキストエディタなどで作成するとどうしてもミスが発生する可能性があるのです。もし、このチェックをしないと、それを越えていってしまいますので、とんでもない文字列になってしまう可能性がありますので注意が必要です。
"a兎bc""123"狸45",
次に連続するダブルクォーテーションで表現される場合です。
" ダブルクォーテーションなのでここから文字列が開始。
a ダブルクォーテーションでもバックスラッシュでもない。
兎 ダブルクォーテーションでもバックスラッシュでもない。
b ダブルクォーテーションでもバックスラッシュでもない。
c ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションが連続しているので次の文字はチェックしない。
"
1 ダブルクォーテーションでもバックスラッシュでもない。
2 ダブルクォーテーションでもバックスラッシュでもない。
3 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションなのでここから文字列が開始。
狸 ダブルクォーテーションでもバックスラッシュでもない。
4 ダブルクォーテーションでもバックスラッシュでもない。
5 ダブルクォーテーションでもバックスラッシュでもない。
" ダブルクォーテーションが連続していないのでここで文字列が終わり。
, コンマなので項目の区切り。
ということになるのですが、本来から言えばこれは変な文字列です。しかしながらこんなものでも処理できた方が使う方は安心です。
これを踏まえて次を見て下さい。
char *get_next_item(data,item)
char *data,*item;
{
char *s,*ss,c;
int is_text;
is_text=0; /* 文字列のときには 1、そうでないときには 0 */
item[0]='\0';
s=data;
while(1){
if(_ismbblead(*s)){ /* 2バイト文字。この関数は Visual C の文字
分類関数で、2バイト文字の第1バイト目のときに
真 */
s+=2;
}
/* 文字が文字列を囲むものである */
else if(*s==text_enclose_char){
s++; /* 次の文字にポインタを移動 */
/* 文字列を囲む文字と文字列内のエスケープ文字が同じ */
if(text_enclose_char==text_escape_char){
/* 現在、文字列でないときには文字列の設定をする */
if(!is_text )is_text=1;
/* 現在の文字がエスケープ文字のときには、次の文字にポ
インタを移動 */
else if(*s==text_escape_char)s++;
/* エスケープ文字と同じでないので文字列の終了を設定 */
else is_text=0;
}
/* 文字列を囲む文字と文字列内のエスケープ文字が違うと
きには文字列か否かの値を反転させる */
else{
is_text^=1;
}
}
/* 文字列内で、文字がヌルでないとき */
else if(is_text && *s){
/* 文字がエスケープ文字のとき。ここは文字列を囲む文字
とエスケープ文字が違うときにだけ真になる可能性があ
る */
if(*s==text_escape_char){
if(*++s){ /* 次の文字がヌルでないときにだけ次の文字が1バイト文
字か否かを調べ、次の文字にポインタを移動 */
if(_ismbblead(*s))s+=2;
else s+=1;
}
}
else{ /* ここは1バイト文字しかありませんので、次の文字にポ
インタを移動 */
s++;
}
}
/* 文字がコンマかヌルのとき。ヌルになるのは文字列の表
現に誤りがあるときに可能性がある */
else if(*s==',' || *s=='\0' ){
/* 現在の文字を覚えておいて、その位置の文字をヌルにし
項目の文字列を取り出す */
c = *s;
*s='\0';
item=new_strcat(item,data);
/* 元の文字がコンマのときには、それ以降の文字列を先頭
からコピーし、そうでないときには先頭をヌルにする */
if(c==',')strcpy(data,s+1);
else data[0]='\0';
break;
}
else{ /* 文字列外のとき。ここは1バイト文字しかありません */
s++;
}
}
/* 項目の文字列へのポインタを返す */
return item;
}
というようになります。じっくりと読んで理解して下さい。この関数にはもう少し付け加えることがあります。コンマの前後のスペースの削除の指定があるときには、項目の先頭と末尾のスペースを削除する機能をこの関数に加えます。return item; の前に次のコードを追加します。
/* スペースの削除の指定がないときにはリターンする */
if(!ignore_terminal_space)return item;
/* 先頭の空白を削除します */
/* 文字を先頭から調べていき、空白以外の文字になったら
ループを終了して、その位置からの文字列を先頭に移動
する */
s=item;
while(*s){
if(isSJISspace(s))s+=2;
else if(isspace(*s) )s+=1;
else break;
}
if(s!=item)strcpy(item,s);
/* 末尾の空白を削除します */
/* 文字列を先頭から最後まで調べていき、それ以降が空白
文字である位置を取得します。ループ終了後にその位置
を取得されていれば、その位置の文字をヌルにする */
ss=NULL;
s =item;
while(*s){
if(_ismbblead(*s)){
if(isSJISspace(s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=2;
}
else{
if(isspace(*s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=1;
}
}
if(ss)*ss='\0';
これもじっくりと読んで理解して下さい。これでソースデータのロードが終わりになります。次に表示です。表示の前にもう1つやっておくことがあります。各項目の最大文字列長を調べておくことです。関数 isspace 標準関数の1つで半角の空白文字か否かを調べるものです。関数 isSJISspace は、シフトJISの全角の空白文字か否かを判定するもので、
int isSJISspace(char *s)
{
unsigned char *us;
us=(unsigned char *)s;
if(*us==0x81 && *(us+1)==0x40)return 1;
else return 0;
}
という内容です。色々とやってみたのですが、安直にシフトJISの空白の文字コードであるか否かを調べることにしました。
メイン関数の close_source の次に
make_form_table();
を追加します。この関数で各項目の最大文字列長を調べ、各項目に対する表示形式のテーブルを作成します。表示形式のテーブルは、文字列の配列で各文字列は printf 関数で使用するパターンを入れます。
テーブルは、
char **form_table=NULL;
と関数外で定義しておきます。
関数 make_form_table は、最初に項目数の最大値を調べ、次にその個数分の int 型の配列を作成して各項目の最大文字列長をその配列に入れていきます。最後に form_table用のメモリーを確保し、そこに printf 関数で使うパターンを入れていきます。
make_form_table()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int total,i,l;
char form[21];
/* 項目の最大数を調べます */
total=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/* 各項目の最大文字列長を代入するテーブルを
作成し、初期化する */
length_table=NEW(int,total);
for(i=0;i<total;i++)length_table[i]=0;
/* 各項目の最大文字列長をテーブルに設定する */
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
l=strlen(dtc->text);
if(l>length_table[i])length_table[i]=l;
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/* フォームテーブルを作成します。1つ多めに
メモリーを確保するのは、最後の要素にヌル
を入れておき、解放するときの門番に使用
するためです */
form_table=NEW(char *,total+1);
for(i=0;i<total;i++){
if(proc_mode==PROC_MODE_EXPAND){
if(display_mode==LEFT_ADJUST)sprintf(form,"%%-%ds",length_table[i]);
else sprintf(form,"%%%ds" ,length_table[i]);
}
else{
strcpy(form,"%s");
}
form_table[i]=new_strcat(NULL,form);
}
form_table[i]=NULL; /* 最後の要素に門番としてヌルを入れます */
DELETE(length_table);
return 1;
}
ここまでできれば後はもう少しです。
メイン関数の make_form_table(); の後に、
display();
を追加し、表示部分を作ります。
display()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int i;
char form[21];
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
/* dtc が dhc->data_text と違うときには2つめ
以降の項目ですのでコンマを表示します。この
ときコンマの前後にスペースを挿入する指定が
あるときにはコンマの前にスペースを表示しま
す */
if(dtc!=dhc->data_text){
if(display_add_space)putchar(' ');
putchar(',');
}
/* dtc->next がヌルでないときは最後の項目では
ありません。このとき、コンマの前後にスペー
スを入れる指定があるときには項目の文字列の
前にスペースを表示します。項目はフォームテ
ーブルのものをそのまま使用します */
if(dtc->next){
if(display_add_space)putchar(' ');
printf(form_table[i],dtc->text);
}
/* 最後の項目の処理です。最後の項目に文字があ
るときしか表示は行ないません */
else if(dtc->text[0]){
if(display_add_space)putchar(' ');
/* 左詰めのときには文字列のみを表示し、
そうでないときにはフォームテーブルのものを
使用します */
if(display_mode==LEFT_ADJUST)printf("%s",dtc->text);
else printf(form_table[i],dtc->text);
}
i++;
dtc=dtc->next;
}
/* 1行の終わりに改行をします */
putchar('\n');
dhc=dhc->next;
}
return 1;
}
そして、表示の終了後に各メモリーの解放をするだけです。ここまでで殆ど完成です。
最後にヘルプの表示機能、とメモリーの解放を付け加えた全リストです。今回も私のリストそのままとしありますので簡単なコメントも残っています。タブは4タブのスペースに変換してあります。関数の順番はバラバラです。
#define VERSION_NUMBER "v1.0"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <mbctype.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define READ_BUF_SIZE 8192
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
char *fname=NULL;
FILE *fp=stdin;
struct data_text_strct{
char *text;
struct data_text_strct *next;
};
struct data_head_strct{
struct data_text_strct *data_text;
struct data_head_strct *next;
};
struct data_head_strct *data_head_top=NULL;
struct data_head_strct *data_head_end=NULL;
struct data_text_strct *data_text_end=NULL;
char **form_table=NULL;
char *new_strcat(char *string1,char *string2);
char *get_next_item(char *data,char *item);
int isSJISspace(char *s);
/*==================================== main ===================================
* Main routine.
*/
void main(argc,argv)
int argc;
char *argv[];
{
/*
* " Analize argment "
*/
if(analize_argment(argc,argv)== -1)goto end;
/*
* " Process "
*/
if(proc_mode==PROC_MODE_HELP){
display_help();
}
else{
if(proc_mode==PROC_MODE_COMPRESS){
ignore_terminal_space=1;
display_add_space=0;
display_mode=LEFT_ADJUST;
}
if(open_source()== -1)goto end;
if(load_source()== -1)goto end;
close_source();
make_form_table();
display();
purge_data();
purge_form_table();
}
/*
* " End of program "
*/
end:
exit(0);
}
/*================================= new_strcat ================================
* strcat.
*/
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
/*================================ open_source ================================
* Open source.
*/
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
/*================================ close_source ===============================
* Close source.
*/
close_source()
{
if(fname)fclose(fp);
return 1;
}
/*================================ load_source ================================
* Load source data.
*/
load_source()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *text;
int is,ie;
text=NULL;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
text=new_strcat(text,data+is);
is=ie+1;
if(ie==ldata)break;
text=new_strcat(text,",");
add_source(text);
text[0]='\0';
}
}
DELETE(text);
return 1;
}
/*================================ isSJISspace ================================
* Is space of SJIS.
*/
int isSJISspace(char *s)
{
unsigned char *us;
us=(unsigned char *)s;
if(*us==0x81 && *(us+1)==0x40)return 1;
else return 0;
}
/*================================= add_source ================================
* Add source.
*/
add_source(data)
char *data;
{
char *item;
add_new_data_head();
item=new_strcat(NULL,"");
while(data[0]){
item=get_next_item(data,item);
add_new_data_text(item);
}
DELETE(item);
return 1;
}
/*============================= add_new_data_head =============================
* Add new data head.
*/
add_new_data_head()
{
struct data_head_strct *dhc;
dhc=NEW(struct data_head_strct,1);
dhc->data_text=NULL;
dhc->next=NULL;
if(data_head_end==NULL)data_head_top =dhc;
else data_head_end->next=dhc;
data_head_end=dhc;
data_text_end=NULL;
return 1;
}
/*============================= add_new_data_text =============================
* Add new data item.
*/
add_new_data_text(text)
char *text;
{
struct data_text_strct *dtc;
dtc=NEW(struct data_text_strct,1);
dtc->text=new_strcat(NULL,text);
dtc->next=NULL;
if(data_text_end==NULL)data_head_end->data_text=dtc;
else data_text_end->next =dtc;
data_text_end=dtc;
return 1;
}
/*============================== make_form_table ==============================
* Make form table.
*/
make_form_table()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int total,i,l;
char form[21];
/*
* " Get maximum item number "
*/
total=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/*
* " Make length table "
*/
length_table=NEW(int,total);
for(i=0;i<total;i++)length_table[i]=0;
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
l=strlen(dtc->text);
if(l>length_table[i])length_table[i]=l;
i++;
dtc=dtc->next;
}
if(i>total)total=i;
dhc=dhc->next;
}
/*
* " Make table array "
*/
form_table=NEW(char *,total);
for(i=0;i<total;i++){
if(proc_mode==PROC_MODE_EXPAND){
if(display_mode==LEFT_ADJUST)sprintf(form,"%%-%ds",length_table[i]);
else sprintf(form,"%%%ds" ,length_table[i]);
}
else{
strcpy(form,"%s");
}
form_table[i]=new_strcat(NULL,form);
}
form_table[i]=NULL;
DELETE(length_table);
return 1;
}
/*================================== display ==================================
* Display.
*/
display()
{
struct data_head_strct *dhc;
struct data_text_strct *dtc;
int *length_table;
int i;
char form[21];
/*
* " Get maximum item number "
*/
dhc=data_head_top;
while(dhc){
i=0;
dtc=dhc->data_text;
while(dtc){
if(dtc!=dhc->data_text){
if(display_add_space)putchar(' ');
putchar(',');
}
if(dtc->next){
if(display_add_space)putchar(' ');
printf(form_table[i],dtc->text);
}
else if(dtc->text[0]){
if(display_add_space)putchar(' ');
if(display_mode==LEFT_ADJUST)printf("%s",dtc->text);
else printf(form_table[i],dtc->text);
}
i++;
dtc=dtc->next;
}
putchar('\n');
dhc=dhc->next;
}
return 1;
}
/*============================== analize_argment ==============================
* Analize argment.
*/
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-c")==0
|| stricmp(argv[i],"/c")==0)proc_mode=PROC_MODE_COMPRESS;
else if(stricmp(argv[i],"-e")==0
|| stricmp(argv[i],"/e")==0)proc_mode=PROC_MODE_EXPAND;
else if(stricmp(argv[i],"-?")==0
|| stricmp(argv[i],"/?")==0){
proc_mode=PROC_MODE_HELP;
return 1;
}
else if(stricmp(argv[i],"-i")==0
|| stricmp(argv[i],"/i")==0)ignore_terminal_space=1;
else if(stricmp(argv[i],"-l")==0
|| stricmp(argv[i],"/l")==0)display_mode=LEFT_ADJUST;
else if(stricmp(argv[i],"-r")==0
|| stricmp(argv[i],"/r")==0)display_mode=RIGHT_ADJUST;
else if(stricmp(argv[i],"-t1")==0
|| stricmp(argv[i],"/t1")==0){
text_enclose_char='"' ;
text_escape_char='\\';
}
else if(stricmp(argv[i],"-t2")==0
|| stricmp(argv[i],"/t2")==0){
text_enclose_char='"';
text_escape_char='"';
}
else if(stricmp(argv[i],"-s")==0
|| stricmp(argv[i],"/s")==0)display_add_space=1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
/*=============================== get_next_item ===============================
* Get (Put) next item.
*/
char *get_next_item(data,item)
char *data,*item;
{
char *s,*ss,c;
int is_text;
is_text=0;
item[0]='\0';
s=data;
while(1){
if(_ismbblead(*s)){
s+=2;
}
else if(*s==text_enclose_char){
s++;
if(text_enclose_char==text_escape_char){
if(!is_text )is_text=1;
else if(*s==text_escape_char)s++;
else is_text=0;
}
else{
is_text^=1;
}
}
else if(is_text && *s){
if(*s==text_escape_char){
if(*++s){
if(_ismbblead(*s))s+=2;
else s+=1;
}
}
else{
s++;
}
}
else if(*s==',' || *s=='\0' ){
c= *s;
*s='\0';
item=new_strcat(item,data);
if(c==',')strcpy(data,s+1);
else data[0]='\0';
break;
}
else{
s++;
}
}
if(!ignore_terminal_space)return item;
/*
* " Reject leading space "
*/
s=item;
while(*s){
if(isSJISspace(s))s+=2;
else if(isspace(*s) )s+=1;
else break;
}
if(s!=item)strcpy(item,s);
/*
* " Reject trailing space "
*/
ss=NULL;
s =item;
while(*s){
if(_ismbblead(*s)){
if(isSJISspace(s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=2;
}
else{
if(isspace(*s)){
if(!ss)ss=s;
}
else{
ss=NULL;
}
s+=1;
}
}
if(ss)*ss='\0';
return item;
}
/*================================ display_help ===============================
* Display help.
*/
display_help()
{
#define p printf
p(" refcom - コンマ区切りテキスト整形プログラム %s\n",VERSION_NUMBER);
p("\n");
p("使用方法:\n");
p("\n");
p(" refcom [-e|c|?] [-t1|2] [-l|r] [-i] [-s] [ソースファイル名]\n");
p("\n");
p(" -e 見やすいように拡張して表示します。\n");
p("\n");
p(" -c コンマ間のスペースを削除(圧縮)して表示します。\n");
p("\n");
p(" -? 本ヘルプを表示します。\n");
p("\n");
p(" ∴ -e、-c、-? のいずれかのオプションも指定されないときには -e として\n");
p(" 表示します。\n");
p("\n");
p(" -t1 文字列の内のダブルクォーテーションは \\\" と表現されているテキスト\n");
p(" を処理します。\n");
p("\n");
p(" -t2 文字列の内のダブルクォーテーションは \"\" と表現されているテキスト\n");
p(" を処理します。\n");
p("\n");
p(" -l -e のときには左詰めで表示します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -r -e のときには右詰めで表示します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -i -e のときにはコンマの前後のスペースを無視します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" -s -e のときにはコンマの前後に1つスペースを挿入します。\n");
p(" -c のときには本オプションは無視します。\n");
p("\n");
p(" ソーステキストファイル名\n");
p(" 処理するソーステキストファイルの名称。本パラメータを省略すると標準入力か\n");
p(" らの読み込みになります。\n");
return 1;
#undef p
}
/*================================= purge_data ================================
* Purge datas.
*/
purge_data()
{
struct data_head_strct *dhc,*dhn;
struct data_text_strct *dtc,*dtn;
dhc=data_head_top;
while(dhc){
dhn=dhc->next;
dtc=dhc->data_text;
while(dtc){
dtn=dtc->next;
DELETE(dtc->text);
DELETE(dtc );
dtc=dtn;
}
DELETE(dhc);
dhc=dhn;
}
data_head_top=NULL;
data_head_end=NULL;
data_text_end=NULL;
return 1;
}
/*============================== purge_form_table =============================
* Purge form table.
*/
purge_form_table()
{
int i;
for(i=0;form_table[i];i++)DELETE(form_table[i]);
DELETE(form_table);
return 1;
}
短そうでも書いてみると意外とプログラムは長くなるものですね。
ではまた次回。






