CSVの整形ならExcelよりもおすすめ ~「EmEditor」の自動化機能で作業を効率化!
具体的なサンプルでスクリプトマクロに入門
2017年4月28日 06:45
定期的に上がってくるCSVデータを整形して見やすくする――といった業務を行う時にマスターしていると便利なのが、“マクロ”機能だ。
“マクロ”とは、操作の手順をあらかじめ登録しておき、必要な時に実行させる機能のこと。大きく分けて、マウスやキーボードの操作を記録しておき、あとで再生するタイプと、スクリプトを記述して実行するタイプの2種類が存在する。
もしあなたが“毎回同じ編集作業をしている”ならば、それはマクロで代替できる可能性が高い。“ちょっとしたことだから、マクロを使うまでもない”と考えている人もいるかもしれないが、たった数分の時間短縮でも、それを週1回行うとして年間で数時間以上の節約になる可能性がある。
「Excel」は高機能すぎてデータの意味が変わってしまうことも
さて、CSVデータを扱うソフトでマクロ機能を備えたソフトと言えば、真っ先に思い浮かぶのは「Microsoft Excel」だろう。しかし、「Excel」には高機能であるがゆえの落とし穴がいくつかある。
たとえば、先頭の0が勝手に削除されてしまうのは、“製品ナンバー”や“電話番号”などのデータを扱う場合に困る仕様だ。また、日付の書式が変ったり、分数が日付とみなされてしまったり、分数が勝手に約分されてしまうのも厄介と言える。たとえば、“(販売実績)/(在庫数)”という意味で“2/10”と記録しておいたものが、勝手に“1/5”と約分されてしまっては、データの意味が変ってしまう。
高機能なCSV機能を備える「EmEditor」のマクロを使おう
こうしたお節介な仕様は運用である程度回避できるが、そもそもデータの分析・計算が不要な、単なる整形処理であれば、わざわざ「Excel」を持ち出す必要もない。CSVデータに対応したテキストエディターがあれば十分だし、そちらの方が向いている。
そこで本稿では、高機能なCSV機能を備える「EmEditor Professional」を用いて、スクリプトマクロによる初歩的なCSVデータの編集について解説する。これまでマクロ機能を使ったことがないユーザーも、ぜひこれを機会にマクロに挑戦してみてほしい。意外に簡単で、その割に効果が高いことを実感していただけるだろう。
はじめに:「EmEditor」のマクロ機能
「EmEditor」のスクリプトマクロは、“Windows Script Host(WSH)”と呼ばれるOS搭載の機能を利用している。WSHはOSで利用されていることもあって安定性に定評があり、“COM”と呼ばれる仕組みでさまざまなアプリケーションと連携させることができる。やろうと思えば、「Internet Explorer」や「Microsoft Office」といったCOM対応アプリケーションを遠隔操作することも可能だ。
また、さまざまなプログラミング言語が利用できるのも特徴。本稿ではとりあえずJavaScriptを利用するが、他にもVBScript、Perl、Python、PHP、Rubyなどに対応しているので、自分の得意な言語があればそれを利用するとよいだろう。
「EmEditor」のスクリプトマクロを利用するには、スクリプトの記述、読み込み、実行という3つのステップを踏む必要がある。例として、現在利用中の「EmEditor」のバージョンを表示するマクロを作成してみよう。
まず、以下のコードを記述した“get-version.jsee”というファイルを“ドキュメント”の“My Macros”フォルダーへ作成しよう。
get-version.jsee
// 「EmEdior」のバージョンを取得
// (行が // で始まる場合はコメントと扱われ、実行されない)
var emeditor_version = editor.Version;
// バージョンを表示する
alert('現在利用中のバージョンは ' + emeditor_version + ' です。')
次に、このファイルを[マクロ]-[選択]メニューから読み込む。すると、[マクロ]-[実行]メニューに“get-version.jsee”と表示されるので、このコマンドを実行しよう。バージョンを表示するダイアログが表示されれば、マクロの実行は成功だ。
今後は基本的に出力を[アウトプット]パネルで行うことにするので、ダイアログを表示する“alert()”を使わないバージョンも例示しておこう。必要に応じて出力をクリップボードへコピーする機能も付けておくと便利だろう。
var emeditor_version = editor.Version;
// [アウトプット]パネルが表示されていない場合は表示
if (!OutputBar.Visible) OutputBar.Visible = true;
// [アウトプット]パネルの内容をクリア
// (使う場合だけ、コメントを削除してください)
// OutputBar.Clear();
// バージョンを表示する(write は行末改行なし、writeln は行末改行あり)
var message = '現在利用中のバージョンは ' + emeditor_version + ' です。';
OutputBar.writeln(message)
// バージョンをクリップボードへテキスト形式でコピーする
clipboardData.setData("Text", message);
ケース1:ログデータから報告用の書式のデータを作成する
それではまず、ログデータから必要な列だけを抽出したり、列の順番を入れ替えるなどして、報告用のデータを作成するケースを考えてみよう。
サンプルデータは以下の通り(一部抜粋)。
case1_sample.txt(2.62MB)※データはツールを使って作成したダミーです
名前 ふりがな アドレス 性別 年齢 誕生日 婚姻 血液型 都道府県 電話番号 携帯 キャリア
宮田 聡 みやた さとし miyata_satoshi@example.com 男 61 1955/4/30 既婚 A型 高知県 088-291-xxxx 080-1223-xxxx ドコモ
進藤 宏 しんどう ひろし shinndou_hiroshi@example.com 男 68 1948/6/13 既婚 A型 北海道 077-278-xxxx 080-3702-xxxx au
水島 慶太 みずしま けいた mizushima_keita@example.com 男 51 1966/1/20 未婚 A型 静岡県 063-393-xxxx 090-4777-xxxx au
これを加工して、名前(1番目の列)と電話番号(10番目の列)、キャリア(12番目の列)を抽出してみよう。マクロのコードは、以下のようになる。
// [アウトプット]パネルへの出力を準備する
if (!OutputBar.Visible) OutputBar.Visible = true;
OutputBar.Clear();
// ドキュメントの行数を取得
var line_count = document.GetLines();
// それぞれの行に対して加工処理を行いる
for (var i = 1; i <= line_count; i++)
{
// i 番目の行を取得する
var line = document.GetLine(i);
// タブ(\t)で分割してセルの配列を取得する
var cells = line.split('\t');
// 出力を組み立てる(配列の添え字は 0 から始まる)
var output = i + '\t' // 何番目か
+ cells[0] + '\t' // “0”=1番目の列“名前”
+ cells[10] + '\t' // “10”=11番目の列“携帯”
+ cells[11]; // “11”=12番目の列“キャリア”
// 出力する
OutputBar.Writeln(output);
}
サンプルデータを開いてアクティブにした状態でこのコードを実行すれば、加工されたデータが[アウトプット]パネルへ出力される。データの数が多いと途中でフリーズしたように見えることがあるが、そのまま少し放置すると処理が完了するはずだ。
「EmEditor」で編集中のドキュメントに関する情報は、“document”オブジェクト“document”オブジェクトで取得可能。“document.GetLines()”メソッドで行数を、“document.GetLine(n)”メソッドでn行目の列を取得することができるので、1行ずつ読み込んで目的のセルを抜き出し、出力を組み立てて、[アウトプット]パネルに表示している。
電話番号の代わりに、性別(4番目の列)を出力したい場合は、“cells[10]”を“cells[3]”に書き換えればOK。JavaScriptでは、配列の添え字が0から始まることに注意しよう(4番目の列であれば、添え字に“[3]”を指定する)。
なお、前掲のコードでは簡便のためセルにカンマが含まれている場合などを考慮していない。万全を期すならば、“var cells = line.split('¥t');”の部分を“document.GetCell()”メソッドを使って書き換えるとよいだろう。2重引用符や区切り文字(デリミッター)を考慮した処理が行える。
if (!OutputBar.Visible) OutputBar.Visible = true;
OutputBar.Clear();
var line_count = document.GetLines();
for (var i = 1; i <= line_count; i++)
{
var line = document.GetLine(i);
// タブ(\t)で分割してセルの配列を取得する(改善版
var cells = [];
for (var j = 1; j < column_count; j++ )
{
cells.push(document.GetCell(i, j, eeCellIncludeNone));
}
var output = i + '\t' // 何番目か
+ cells[0] + '\t' // “0”=1番目の列“名前”
+ cells[10] + '\t' // “10”=11番目の列“携帯”
+ cells[11]; // “11”=12番目の列“キャリア”
OutputBar.Writeln(output);
}
ケース2:複数のファイルに分かれているデータを1つにまとめて重複行を削除
次は、2つのファイルを合体させるケースだ。サンプルデータは以下のようになっている(一部抜粋)。
case2a_sample.txt(0.01MB)※データはツールを使って作成したダミーです
氏名,氏名(カタカナ),性別,電話番号,メールアドレス,郵便番号,住所,,,,,住所(カタカナ),,,,,住所(ローマ字),,,,,生年月日
和田正三郎,ワダショウザブロウ,男,045773xxxx,shouzaburouwada@example.com,231-0861,神奈川県,横浜市●区,●町,x-xx,,カナガワケン,ヨコハマシ●●ク,●●マチ,x-xx,,kanagawaken,yokohamashi●●ku,●●machi,x-xx,,1973/03/05
伊藤陽花,イトウハルカ,女,046530xxxx,jhsqkiiofwgtharuka56417@example.com,298-0123,千葉県,●●●市,●谷,x-x-xx,●谷ランド408,チバケン,●●●シ,●●ヤ,x-x-xx,●●ヤランド408,chibaken,●●●shi,●●ya,x-x-xx,●●yarando408,1962/02/22
岡野乙葉,オカノオトハ,女,052549xxxx,otoha_okano@example.com,464-0817,愛知県,名古屋市●●区,●●町,x-x,ロイヤル●●町410,アイチケン,ナゴヤシ●●ク,●●●チョウ,x-x,ロイヤル●●●チョウ410,aichiken,nagoyashi●●●ku,●●●chou,x-x,roiyaru●●●chou410,1961/05/14
case2b_sample.txt(0.01MB)※データはツールを使って作成したダミーです
氏名,氏名(カタカナ),性別,電話番号,メールアドレス,郵便番号,住所,,,,,住所(カタカナ),,,,,住所(ローマ字),,,,,生年月日
小島祥子,コジマショウコ,女,097713xxxx,shouko00978@example.com,870-0953,大分県,大分市,●●東,x-x-xx,●●東コート114,オオイタケン,オオイタシ,●●●●●ヒガシ,x-x-xx,●●●●●ヒガシコート114,ooitaken,ooitashi,●●●●●higashi,x-x-xx,●●●●●higashiko-to114,1984/10/11
小菅菜摘,コスゲナツミ,女,087313xxxx,natsumi8152@example.com,763-0027,香川県,●●市,●番丁,x-x-xx,,カガワケン,●●●●シ,●●バンチョウ,x-x-xx,,kagawaken,●●●●shi,●●banchou,x-x-xx,,1985/05/12
砂川美怜,サガワミレイ,女,028944xxxx,mirei_sagawa@example.com,321-0918,栃木県,●●●市,●●町,x-x,●●町キャッスル119,トチギケン,●●●●●シ,●●●●マチ,x-x,●●●●マチキャッスル119,tochigiken,utsunomiyashi,●●●●machi,x-x,●●●●machikyassuru119,1981/11/06
この2つを結合させて、“どちらにも含まれている人の名前と電話番号”だけを抽出してみよう。コードはたったこれだけだ。
editor.Join(
0, // 一意キー、大文字・小文字の区別、結合方法を指定するフラグ
"case2a_sample.txt", // 第1ドキュメント(フルパスなども利用できる)
"氏名", // キーになるデータ
"case2b_sample.txt", // 第2ドキュメント
"氏名", // キーになるデータ
// どのデータをどのデータと突き合わせるか(データマッピングを指定)
"case2a_sample.txt>氏名,case2a_sample.txt>電話番号"
);
サンプルデータを2つともCSVモードで開いた状態でこのマクロを実行すれば、新規タブに結合済みのデータが表示されるだろう。
利用しているメソッドは“editor.Join()”だけだが、このメソッドは一番最初の引数に指定するフラグが少しわかりにくいので、このマクロはGUIから生成するのがお勧め。
まず、[編集]-[CSV]-[結合]メニューを選択しよう。すると[CSV の結合]ダイアログが現れる。あとはパラメーターを設定して右下の[エクスポート]ボタンを押すと、マクロが生成される。マクロとして保存しておけば、次回からはわざわざダイアログを呼び出さなくても簡単に結合処理が行える。
今回の例では、“どちらにも含まれている人”を抽出する必要があるが、これは“どちらにも氏名が記載されている人”と読み替えられる。つまり、“氏名”をキーに一致を判断すればいいので、[キー列]には“氏名”を指定すればよい。ただし、これは同姓同名のケースを考慮していない。場合によっては“電話番号”や“メールアドレス”を[キー列]にすることも検討した方がよいだろう。
[一意キー]オプションは、“キー列”に指定したデータに重複するものがないと保証される場合(IDなど)、有効化しておくと結合処理が速くなる。通常はそのまま(無効化)のままで問題ないだろう。
両文書の[一致しない行を含める]は、結合の方法をコントロールするためのオプションだ。初期状態では両方ともOFFになっており、“両方に含まれる”場合だけが抽出される。両方ともONにすれば、一致データの重複を許さずに2つを結合することが可能だ(SQLに習熟している場合は、それぞれINNER JOIN、OUTER JOINに相当すると考えればわかりやすいだろう。LEFT JOINやRIGHT JOINも可能だ)。
ケース3:箇条書きになっているテキストをCSVに変換
さいごに、箇条書きになっているテキストをCSVに変換する例を取り上げよう。たとえば、以下のような質問フォームを――
case3.txt(0.01MB)
Q01:あなたのパソコン歴を教えてください。
01:1年未満
02:1年~3年未満
03:3年~6年未満
04:6年~10年未満
05:10年~15年未満
06:15年以上
07:その他
Q02:あなたのインターネット歴を教えてください。
01:1年未満
02:1年~3年未満
03:3年~6年未満
04:6年~10年未満
05:10年以上
06:その他
以下のようなCSV形式に変換するケースを考える。
Q01,1年未満,1年~3年未満,3年~6年未満,6年~10年未満,10年~15年未満,15年以上,その他
Q02,1年未満,1年~3年未満,3年~6年未満,6年~10年未満,10年以上,その他
サンプルコードは以下のようになる。サンプルデータを開いてタブをアクティブにした状態で、マクロを実行してみよう。
// [アウトプット]パネルへの出力を準備
if (!OutputBar.Visible) OutputBar.Visible = true;
OutputBar.Clear();
// ドキュメントの行数を取得
var line_count = document.GetLines();
// それぞれの行に対して加工処理を行う
for (var i = 1; i <= line_count; i++)
{
// i 番目の行を取得する
var line = document.GetLine(i);
// 空行の場合
if (line === '') {
// 質問の区切りとみなす → 改行して以降の処理をスキップ
OutputBar.Write('\r\n');
continue;
}
// 「Q**:~」形式(行が'Q'で始まる)の場合
if (line.indexOf('Q') === 0) {
// 「@**」部分(“:”で分割した前半)だけを抽出する
line = line.split(':')[0];
// それ以外の場合(「選択肢の番号:選択肢の内容」形式)
} else {
// 選択肢の内容だけを抽出する
line = line.split(':')[1];
}
// 改行せずに出力
OutputBar.Write(line);
// 区切り文字を出力
OutputBar.Write(',');
}
基本的な流れは、ケース1と同様、ドキュメントの内容を取得し、一行ずつ処理するだけだ。区切り文字をタブにしたい(TSV形式にしたい)場合は、“OutputBar.Write(',');”を“OutputBar.Write('¥t');”にすればよい。
結果を[アウトプット]パネルではなく、クリップボードへコピーしたり、新規タブへ表示したい場合は、“OutputBar”へ直接出力せず、出力を変数へ保存するとよいだろう。前掲のコードでは、簡便のため二重引用符やカンマが含まれている場合を考慮していなかったが、以下のコードでは対策を施してある。
// 出力を保存する変数 output を準備
var output = '';
// データの加工を行って結果を output へ格納
var line_count = document.GetLines();
for (var i = 1; i <= line_count; i++)
{
var line = document.GetLine(i);
if (line === '') {
output += '\r\n'; // output = output + '\r\n' と同じ意味
continue;
}
if (line.indexOf('Q') === 0) {
line = line.split(':')[0];
} else {
line = line.split(':')[1];
}
// カンマ、2重引用符が含まれている場合の処理
if ((line.indexOf(',') != -1 || line.indexOf('"') != -1)) {
line = '"' + line.replace(/"/g, '""') + '"';
}
output += line;
output += ',';
}
// 新規タブを用意して output を出力
editor.EnableTab = true;
editor.NewFile();
document.write(output);
// もしくはクリップボードへコピー
// clipboardData.setData("Text", output);
これでおおむね目的を達成したが、それぞれの行末に余計な“,”が追加されるのが気になるなら、最後に正規表現で削除してしまうとよい。[置換]ダイアログを利用してもよいが、マクロで書くならば以下のようになる。
// グローバル(g)、マルチライン(m)オプションを利用して
// 正規表現で行末の「,」(正規表現では“,$”)を削除(から文字列に置換)する
output = output.replace(/,$/gm, "");
この行を新規タブへ出力する直前あたりに挿入しておこう。このように正規表現を使った検索・置換も覚えておくと役に立つ。
まとめ
コードが含まれているため記事が少し長くなってしまったが、やっていること自体はそれほど難しくない。変数などを自分の利用ケースに応じて書き換えるだけで、サンプルマクロをそのまま利用できるケースも少なくないと思われるので、ぜひ活用して、日々の業務を楽にしていていただきたい。また、最新版の「EmEditor」v16.7では、“SetCell()”“SetColumn()”“InsertColumn()”などCSV関連のマクロ拡張も行われている。今後のさらなる機能拡充にも期待したい。
[制作協力:Emurasoft, Inc.]