なぜ「TSV 用エスケープ」が必要になるのか
TSV は「Tab Separated Values」、区切り文字がカンマではなくタブ(\t)のテキスト形式です。
CSV よりシンプルに見えますが、「そのまま文字列をタブでつなぐだけ」で済むのは、かなり運がいいケースだけです。
実務でよくあるのは、こういう値です。
- 値の中にタブが紛れ込んでいる
- 値の中に改行が入っている
- 値の中にダブルクォートやカンマも普通に出てくる
TSV では「タブが区切り」「改行が行の区切り」なので、
この 2 つをどう扱うかをきちんと決めておかないと、読み込み側で行・列がズレます。
CSV と違って「公式な厳密仕様」があまり浸透していないぶん、
プロジェクトごとに“ローカルルール”を決めて、それに合わせたエスケープを用意するのが現実的です。
TSV の前提ルールをざっくり決める
よくある実務ルールの一例
まずは、「どういう方針でエスケープするか」を決めます。
ここでは、次のようなシンプルなルールを採用します。
- 区切り文字はタブ
\t - 行の区切りは
\r\n(Windows 互換) - フィールド内にタブや改行があったら、
\tや\nのような「見える形」にエスケープする - ダブルクォートで囲む方式は使わない(TSV では必須ではないため)
つまり、「TSV は“タブ区切りのプレーンテキスト”で、危ない制御文字だけをバックスラッシュ付きで逃がす」という方針です。
この方針だと、読み込み側も「\t をタブに戻す」「\n を改行に戻す」だけで元の値を復元できます。
1 フィールド用の「TSV エスケープ関数」を作る
制御文字を「見える形」に変換する
まずは、1 セル分の文字列を安全な形に変換する関数を作ります。
やりたいことはシンプルです。
- 実際のタブ文字
\t→ 文字列としての\t - 実際の改行
\r/\n→\r/\n - バックスラッシュ
\自体もエスケープしておく(\→\\)
/**
* 1 フィールド分の文字列を TSV 用にエスケープする
*
* 制御文字(タブ・改行・バックスラッシュ)を「見える形」に変換する。
*/
function tsv_escape_field(string $value): string
{
// まずバックスラッシュをエスケープ
$value = str_replace('\\', '\\\\', $value);
// タブを \t に
$value = str_replace("\t", '\\t', $value);
// 改行を \n / \r に
$value = str_replace("\r", '\\r', $value);
$value = str_replace("\n", '\\n', $value);
return $value;
}
PHP使い方の例です。
echo tsv_escape_field("山田\t太郎");
// 出力: 山田\\t太郎
echo tsv_escape_field("1行目\n2行目");
// 出力: 1行目\\n2行目
PHPここでのポイントは、「実際のタブ・改行を、TSV の構造を壊さない“ただの文字列”に変えている」ということです。
TSV の区切りとして使うタブ・改行と、値の中に含まれるタブ・改行を、はっきり分離できます。
行全体を組み立てるユーティリティにする
配列 → 1 行分の TSV 文字列
次に、配列から 1 行分の TSV を作る関数を用意します。
/**
* 配列から 1 行分の TSV 文字列を生成する
*/
function array_to_tsv_line(array $fields, string $delimiter = "\t"): string
{
$escaped = [];
foreach ($fields as $value) {
$escaped[] = tsv_escape_field((string)$value);
}
// タブで結合し、行末に \r\n を付ける
return implode($delimiter, $escaped) . "\r\n";
}
PHP使い方の例です。
$row = [
1,
"山田\t太郎",
"1行目\n2行目",
];
echo array_to_tsv_line($row);
// 例: 1\t山田\\t太郎\t1行目\\n2行目\r\n
PHPこの文字列をファイルに順番に書いていけば、
「タブ区切り・1 行ごとに \r\n・値の中の制御文字はエスケープ済み」の TSV ができます。
読み込み側のことも少しだけ意識する
エスケープを「戻す」処理のイメージ
こちらはユーティリティの範囲を少し超えますが、
エスケープした以上、読み込み側で「元に戻す」処理も必要になります。
イメージとしては、次のような逆変換です。
\\t→ 実際のタブ\t\\n→ 実際の改行\n\\r→ 実際の復帰\r\\\\→ バックスラッシュ\
書くとこうなります。
function tsv_unescape_field(string $value): string
{
$value = str_replace('\\t', "\t", $value);
$value = str_replace('\\n', "\n", $value);
$value = str_replace('\\r', "\r", $value);
$value = str_replace('\\\\', '\\', $value);
return $value;
}
PHP実務では、「出力側と入力側で同じルールを共有する」ことが何より大事です。
TSV は CSV より自由度が高いぶん、「プロジェクト内の約束事」をきちんと決めておくと、後で困りません。
まとめ:今日からの「TSV 用エスケープ」ユーティリティ
TSV 用エスケープの本質は、「タブと改行(構造を決める文字)を、値の中から追い出して“ただの文字列”に変える」ことです。そのためにやるべきことは、次の 2 点に集約されます。
- 値の中のタブ・改行・バックスラッシュを、
\t・\n・\r・\\のような見える形に変換する。 - 行全体は「タブ区切り+\r\n 終端」で組み立てる。
ユーティリティとしてまず持っておくと便利なのは、この 2 本です。
function tsv_escape_field(string $value): string
{
$value = str_replace('\\', '\\\\', $value);
$value = str_replace("\t", '\\t', $value);
$value = str_replace("\r", '\\r', $value);
$value = str_replace("\n", '\\n', $value);
return $value;
}
function array_to_tsv_line(array $fields, string $delimiter = "\t"): string
{
$escaped = [];
foreach ($fields as $value) {
$escaped[] = tsv_escape_field((string)$value);
}
return implode($delimiter, $escaped) . "\r\n";
}
PHPもし、あなたのコードが「implode("\t", $row) だけで TSV を作っている」なら、そこがこのユーティリティを差し込むポイントです。タブと改行を一段“見える形”に逃がしてあげるだけで、「壊れにくい TSV」に一気に近づきます。
