我輩はブロガーではない。ネタもまだない

SASとかDelphiあたりの人様の役に立たないネタを提供します

RTFに票出力したものを加工する

突然ですが、こんな票にデータを出力してほしい、と言われたらどうしますか?

パットできちゃう人はさよなら。もうこの記事を読む必要はありませんw

こういう表じゃない票ってあまり好きじゃないのですが、簡単には作成できずに苦戦しました。
(帳票出力スキルがないだけという話も)
でも、たまに見かけるのでもしや需要があるかもしれず、記事にしておこうと思います。


そもそも論として、縦か横できちんと並んだ表にしたいというのはもちろんではあるのですが、作成する報告書の関係上、そのようにはできないというケースがあったりします。(実際あったので)

proc print だろうがproc reportだろうが、こうきれいには出力できません。
普通に出力すると余計な空行が入ってしまいます。

proc printの結果
proc reportの結果

これを制御することは、SASでは無理です。
ですので、出力後に処理をすることを考えます。

幸い、RTFで出力していれば、制御コードで処理されているものですので、加工ができます。

早速見ていきましょう。
(*フルコードを毎回載せているので記事長めです。ご注意下さい)

デフォルトの出力結果

まず、以下のコードを実行すると、このように一覧で出力されます。

ods _all_ close;
ods rtf;
proc report data=sashelp.baseball;
run;
ods rtf close;



1obsずつ表示させる

1obsずつ表示させたいので、byを使います。
そしてヘッダーに、タイトル、日付、番号と邪魔なものがありますね。これらも消しましょう。

ods _all_ close;
ods rtf;
/* タイトルを消す */
title;
options nodate nonumber;
/* byで1obsずつ出力するため、ソートする */
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball;
  by name;
run;
ods rtf close;

項目を選択する

ここからは冒頭の票に合わせるため、表示項目をPlayer's Name, Team, Times, Hits, Home Runs, Runsに制限します。
そしてヘッダーのPlayer’s Name、邪魔ですねぇ…。これも消しちゃいましょう。

ods _all_ close;
ods rtf;
title;
/* nobylineでヘッダーに挿入されるデータを抑制する */
options nodate nonumber nobyline;
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball;
  column Name Team nAtBat nHits nHome nRuns;
  by name;
run;
ods rtf close;

列幅を指定して表示行をコントロールする

ここからはComputeステートメントを使って、各列幅を指定していきます。
列幅を広げることで、無理やり改行させよう、という魂胆です。
なお、サイズはわかりやすくcm表記にしています。

ods _all_ close;
ods rtf;
title;
/* nobylineでヘッダーに挿入されるデータを抑制する */
options nodate nonumber nobyline;
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball;
  column Name Team nAtBat nHits nHome nRuns;
  by name;
  /* computeでセル幅を指定する */
  compute name;
    call define(_col_,'style','style={just=l cellwidth=15cm}');
  endcomp;
  compute team;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nAtBat;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nHits;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nHome;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nRuns;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
run;
ods rtf close;



おお、かなり近くなりましたね。

表示用変数を作成する

Nameの部分を「Player's Name:名前」としたいのですが、Name変数は$18と短いため、「Name:」を先頭に入れてしまうと名前が切れてしまいます。
そのため、表示用に新しい変数を作ってやる必要があります。

ods _all_ close;
ods rtf;
title;
options nodate nonumber nobyline;
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball nowd;
  /* NameLabel変数を追加 */
  column name NameLabel Team nAtBat nHits nHome nRuns;
  by name;
  /* 変数ラベルなしとする */
  define NameLabel / '' computed;
  /* 既存のnameは不要 */
  define name / noprint;
  compute NameLabel / character length=40;
    call define(_col_,'style','style={just=l cellwidth=15cm}');
    /* columnステートメントで前にある変数しか処理できない */
    NameLabel ='Player''s Name: '||name;
  endcomp;
  compute team;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nAtBat;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nHits;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nHome;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nRuns;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
run;
ods rtf close;


不要な枠線を削除する

枠線を消し、お好みでフォントサイズをいじります。

ods _all_ close;
ods rtf;
title;
options nodate nonumber nobyline;
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball nowd;
  column name NameLabel Team nAtBat nHits nHome nRuns;
  by name;
  /* headerの罫線を指定する */
  define NameLabel / ''
                     computed 
                     style(header)=[just=l
                                    backgroundcolor=white
                                    BorderTopColor=White
                                    BorderLeftColor=White
                                    BorderRightColor=White
                                    BorderBottomColor=White];
  define name / noprint;
  /* フォントと罫線を指定する */
  compute NameLabel / character length=40;
    call define(_col_,'style','style={just=l 
                                      fontweight=bold
                                      fontsize=14pt
                                      cellwidth=15cm
                                      BorderTopColor=White
                                      BorderLeftColor=White
                                      BorderRightColor=White
                                      BorderBottomColor=White}');
    NameLabel ='Player''s Name: '||name;
  endcomp;
  compute team;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nAtBat;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nHits;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nHome;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nRuns;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
run;
ods rtf close;


SASでやるのはここまでです。

RTFの制御コードを編集してみる

この後は、rtfコードの編集です。
rtfの仕様を確認すると、新しい段落は「\par」のようです。
Rich Text Format (RTF) Version 1.5 Specification

エディタで確認すると、たしかに何箇所かで段落コードが見つかりますので、これを削除してみます。


きれいに空行がなくなりました。

そして、セクションは「\sect」のようです。
http://www.biblioscape.com/rtf15_spec.htm#Heading27


こちらもセクションを削除することで並んで表示されるようになりました。

最終コード

これらのフルコードは以下です。
冒頭のrtf_outのファイルパスを好きなパスに変更すれば、任意のファイルでこの票出力が実現できます。

ods _all_ close;
ods noresults;
/* 書き換えのため、ファイルパスを固定する */
filename rtf_in "%sysfunc(pathname(work))\rtf_in.rtf";
filename rtf_out "%sysfunc(pathname(work))\rtf_out.rtf";
ods rtf file=rtf_in;
title;
options nodate nonumber nobyline;
proc sort data=sashelp.baseball out=baseball;
  by name;
run;
proc report data=baseball nowd;
  column name NameLabel Team nAtBat nHits nHome nRuns;
  by name;
  define NameLabel / ''
                     computed 
                     style(header)=[just=l
                                    backgroundcolor=white
                                    BorderTopColor=White
                                    BorderLeftColor=White
                                    BorderRightColor=White
                                    BorderBottomColor=White];
  define name / noprint;
  compute NameLabel / character length=40;
    call define(_col_,'style','style={just=l 
                                      fontweight=bold
                                      fontsize=14pt
                                      cellwidth=15cm
                                      BorderTopColor=White
                                      BorderLeftColor=White
                                      BorderRightColor=White
                                      BorderBottomColor=White}');
    NameLabel ='Player''s Name: '||name;
  endcomp;
  compute team;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nAtBat;
    call define(_col_,'style','style={just=l cellwidth=7.5cm}');
  endcomp;
  compute nHits;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nHome;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
  compute nRuns;
    call define(_col_,'style','style={just=l cellwidth=5cm}');
  endcomp;
run;
ods rtf close;

data _null_;
  infile rtf_in;
  file rtf_out;
  input;
  /* 段落コードを削除 */
  if _infile_='\pard\par' then _infile_='\pard';
  /* セクション行を削除 */
  else if index(_infile_,'\sect') then delete;
  put _infile_;
run;