改行を含むTSVファイルを使ってデータ結合を行う方法(UTF-16出力、串刺し印刷対応版)

昨日アップしたPerlスクリプトUTF-16出力での文字化けを回避する方法を「ひらくん」に教えていただきました。
感謝!感謝!です。

UTF-16出力の修正を加えたついでに、串刺し印刷対応のための機能拡張を行いました。データ結合で1ページに複数レコードを割り付ける場合、串刺し印刷になるようレコードを並べ替えたいことがしばしばあります。下記のスクリプトを使えば、-mオプションで面付け数を指定することにより、串刺し印刷のためにレコードを並べ替えてくれます。

【入力ファイル】
Excelで作った下記のようなレコード形式のデータを、Unicodeテキストで保存したもの

表ヘッダ1 表ヘッダ2 表ヘッダ3
行1列1データ 行1列2データ 行1列3データ
行2列1データ 行2列2データ 行2列3データ
行3列1データ 行3列2データ 行3列3データ

注意:1行目は必ずヘッダ行にしてください。

【実行方法】

> perl cr-u.pl -i <入力ファイル名> -m <面付け数>
  • 入力ファイルのファイル名は、XXX.txt
  • 出力ファイルのファイル名は、XXX_OUT.txt

【動作確認】
Windows7 x64
Perl v5.10.1
InDesign CS4

#!perl
#
# ======================================================
#  TSVファイルのフィールドに含まれる改行を@@@に置換する
# ======================================================
#
# [変更履歴]
#
# Ver1.00  2010/07/05 初版
# Ver2.00  2010/12/23 
#   入力ファイルをShift-JIS CSVファイルからUnicode TSVファイルに変更した。
#   Excel上での文字の表示にはUnicodeが使われるが、通常の方法でCSVファイルに保存
#   するとShift-JISになるため、文字化けが発生することがある。
# Ver3.00  2010/12/24 
#   ***_OUT.TXT をUTF-16で出力するように修正した。
#   串刺し印刷のために、面付け数を指定するオプションを追加した。
# Ver3.01  2011/02/04
#   串刺し印刷のバグを修正した。
#
# [スクリプトの解説]
#
# InDesignのデータ結合でCSV/TSVファイルの流し込みをするとき、フィールドの中に
# 改行が含まれていると、正しく流し込みができない。この問題を解決するために、
# データ結合の前に、フィールドの中に含まれる改行を"@@@"に置換する。
# InDesignでデータ結合した後に、"@@@"を改行に置換することにより、フィールド
# の中の改行を再現できる。
#
# [使用方法]
#
# コマンド・プロンプトより、以下の書式に従ってコマンドを入力する。
#
# 書式:
#  cr-u.exe -i <ソースのTSVファイル名称> -m <面付け数>
#
#  ファイル名 => *.txt
#
# 【 重 要 】
#
# ExcelからTSVファイルを保存するときに、Unicodeテキストを指定すること。
#

use warnings;
use strict;

use utf8;
binmode STDOUT, ":encoding(shiftjis)";  # 標準出力をShift-JISにする
binmode STDERR, ":encoding(shiftjis)";  # 標準エラー出力をShift-JISにする
use Encode qw/encode decode from_to/;
use Getopt::Std;
use File::Basename;

print "*** TSVファイルの改行処理スクリプト Ver 3.01 ***\n\n";

# ― グローバル変数宣言 ――――――――――――――――――――――――――┐
our $opt_i = undef;             # -iオプションを取得するための変数
our $opt_m = undef;             # -mオプションを取得するための変数

# ―――――――――――――――――――――――――――――――――――――┘

# ― 引数を取得する ――――――――――――――――――――――――――――┐

$opt_i = undef;
unless( getopts('i:m:')) {
  print "エラー:引数の取得に失敗しました。\n";
  &display_usage();
  die;
}

# -iオプションが指定されていない場合は、使い方を表示してスクリプトを終了する
if (!$opt_i) {
  &display_usage();
  exit;
}

# -iオプションで指定されたファイルが存在するかどうか確認する
my $input_fname = decode('shiftjis', $opt_i);   # ファイル名をShift-JISにデコードする
unless (-e $opt_i) {
  die "エラー:指定されたテキスト・ファイルがありません; ファイル名=$input_fname\n";
}

# -mオプションが指定されていない場合は面付け数を1にする
my $mentsuke = 1;
if ($opt_m) {
  if ($opt_m > 0) {
    $mentsuke = $opt_m;
  } else {
    die "エラー:面付け数には1以上の値を指定してください。\n";
  }
}

#print "MEN = $mentsuke\n";

# ―――――――――――――――――――――――――――――――――――――┘

# ― 入力テキスト・ファイルをパースし、改行の置換を行う ――――――――――┐

my @databuf;  # 改行置換後のデータを保存するための配列

open INPUT_TSV_FILE, '<:encoding(utf16)', $opt_i
  or die "エラー:入力ファイルをオープンできません; ファイル名=$input_fname\n";

# 値に改行を含むTSVファイルを処理する(下記ページのコードを流用)
#   http://www.din.or.jp/~ohzaki/perl.htm#CSVwithCRLF
while (my $line = <INPUT_TSV_FILE>) {
  $line .= <INPUT_TSV_FILE> while ($line =~ tr/"// % 2 and !eof(INPUT_TSV_FILE));
  $line =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/\t/;
  my @values = map {/^"(.*)"$/s ? scalar($_ = $1, s/""/"/g, $_) : $_}
                ($line =~ /("[^"]*(?:""[^"]*)*"|[^\t]*)\t/g);

  # 各フィールドデータの中に改行が含まれていたら、"@@@"に置換する
  my $one_tsv_line = "";
  my $column_cnt = 0;
  foreach my $field (@values) {
    #print "$field\n";
    $field =~ s/\n/@@@/g;
    $one_tsv_line .= $field;
    if ($column_cnt < @values - 1) {
      $one_tsv_line .= "\t";
    }
    $column_cnt++;
  }
  push @databuf, $one_tsv_line;
}

close INPUT_TSV_FILE;

# ―――――――――――――――――――――――――――――――――――――┘

# ― 串刺し印刷のためにデータを並べ替える ―――――――――――――――――┐

my @reorderedbuf;  # 並べ替えたデータを格納する配列
my $n_record = @databuf - 1;               # 最初のレコードはヘッダ行なので-1する
my $n_paper = int($n_record / $mentsuke);  # 紙の通し枚数
my $hasuu = $n_record % $mentsuke;         # 最後の紙の面付け数
$n_paper = ($hasuu == 0 ? $n_paper : $n_paper + 1);  # 端数がある場合は+1する
$hasuu = ($hasuu == 0 ? $mentsuke : $hasuu); # 端数がない場合は、$hasuuを面付け数にする
print "面付け数 = $mentsuke\n";
print "紙通し数 = $n_paper\n\n";
push @reorderedbuf, $databuf[0];           # ヘッダ行をコピー
for (my $paper_cnt = 1; $paper_cnt <= $n_paper; $paper_cnt++) {
  my $index = $paper_cnt;
  for (my $field_cnt = 0; $field_cnt < $mentsuke; $field_cnt++) {
    if (($paper_cnt == $n_paper) && ($field_cnt >= $hasuu)) {
      last;  # 最後の紙は端数の分まで処理したら終わり
    }
    push @reorderedbuf, $databuf[$index];
    #print "index = $index\n";
    if ($field_cnt < $hasuu) {
      $index += $n_paper;       # インデックスに紙の通し枚数を加算
    } else {
      $index += ($n_paper - 1); # 端数の部分
    }
  }
}

# ―――――――――――――――――――――――――――――――――――――┘

# ― 変換後のデータをファイルに出力する ――――――――――――――――――┐

my $output_fname = basename($input_fname, ".txt");
$output_fname = $output_fname . "_OUT.txt";

# UTF-16文字化け対策(詳細は下記のページを参照のこと)
#   http://hirakun.blog57.fc2.com/blog-entry-76.html
#   http://blogs.msdn.com/b/brettsh/archive/2006/06/07/620986.aspx
open OUTPUT_TSV_FILE, ">:raw:encoding(utf16-le):crlf:utf8", encode('shiftjis', $output_fname)
  or die "エラー:出力ファイルをオープンできません; ファイル名=$output_fname\n";
print OUTPUT_TSV_FILE "\x{FEFF}";  # print BOM (Byte Order Mark) for the unicod

foreach my $str (@reorderedbuf) {
  print OUTPUT_TSV_FILE "$str\n";
}

print "\n$output_fname が作成されました\n\n";
close OUTPUT_TSV_FILE;
exit;

# ―――――――――――――――――――――――――――――――――――――┘


################################################################################
# 処理概要: 本スクリプトの使い方を表示する
#
sub display_usage {
  my $msg = <<EOF;

[使用方法]

コマンド・プロンプトより、以下の書式に従ってコマンドを入力する。

書式:
  cr-u.exe -i <ソースのTSVファイル名称> -m <面付け数>

 ファイル名 => *.txt

【 重 要 】

 ExcelからTSVファイルを保存するときに、Unicodeテキストを指定すること。

EOF
  print $msg;
}

【 免 責 】

上記スクリプトの使用により発生する、データの破損などのあらゆる不具合・不利益については、一切の責任を負いかねますのでご了解ください。

【 ダウンロード 】
上記スクリプトを実行するにはActivePerlなどのPerl実行環境が必要です。Perl実行環境がない方は、下記のEXE版を使ってください。これは、上記のPerlスクリプトを実行環境を含めてEXE化したものです。
EXE版を使うときは、スクリプトの中のコメントに書いてあるような書式でコマンドプロンプトから実行します。
cr-u.exe 直

【 補 足 】

十分テストしていないので、不具合に気付かれたら教えてください。特に、串刺しのためのコードは、アルゴリズムを明確にしてから書いたのではなく、カット&トライしながら期待の出力を得るように直していきましたので。

12/28 追記
賢明な方はお気づきだと思いますが、このスクリプトは改行を含まないTSVファイルでも使えます。すなわち、普通にExcelで作成したデータをデータ結合する場合に、串刺し印刷用にデータを並べ替える目的で本スクリプトを活用できるということです。

02/04 追記
Ver3.00→Ver3.01にアップデートしました。
串刺し印刷のための、面付け処理のバグを修正しました。