サポンテ 勉強ノート

サポンテの勉強ノート・読書メモなどを晒します。

Excel や Numbers からコピーした表を Markdown としてペーストする【CotEditor】

Excel や Numbers、Google Spreadsheet からコピーした表を Markdown としてペーストする【CotEditor】

はじめに

 昨今の Markdown 界はどうなっているだろうかとネットサーフィンをしていると、最近はこのようなエディタが人気らしいではないですか。

 ふむふむ、これは良い WYSIWYG ですね。ていうか、ここまでする?

表のペースト機能がすごい

 「Excel の表をコピーしてペーストすると自動的に Markdown の表になる」という機能に驚かされました。

 これは良い。なんという逆転の発想。コピーするときに Markdown 文字列に変換するのではなく、ペーストする瞬間に処理を行う。これなら Excel だろうが Numbers だろうが Google Spreadsheet だろうが関係ありません。好きなスプレッドシートを使うことができます。もう、あっちは VBA、こっちは AppleScript で、向こうは JavaScript なんて考えなくて済みます。

何を驚いているんだい?そもそも初めから本来こうあるべきだろう?

 そんなスティーブ = ジョブズの名言が聞こえてくるようです。1

CotEditor スクリプトを作ってみる

 クリップボードから TSV 文字列を取り出すのは pbpaste コマンドがやってくれますから、それを加工する処理だけ、好きなスクリプト言語を使って作れます。

 ファイル名を「TSVを表としてペースト.@$v.php」なんてファイル名で CotEditor のスクリプトフォルダに入れれば、「⌘ + Shift + V」で実行できます。

使い方

 表計算ソフトからセル範囲を選択して「コピー」を行います。

 CotEditor に戻って「⌘ + Shift + V」します。以下のようになります。

Id    |Name            |Price       
------|----------------|------------
1     |Apple           |200         
2     |Banana          |150         
3     |Citrus          |300         
九九九|ドラゴンフルーツ|価格未設定。

スクリプトソースコード

#!/usr/bin/php -q
<?php
// %%%{CotEditorXInput=Selection}%%%
// %%%{CotEditorXOutput=ReplaceSelection}%%%

mb_detect_order("UTF-8,SJIS,sjis-win");

// 文字列の長さを、全角:2、半角:1 で数える。
function jp_str_len($str) {
    $len = 0;
    
    foreach (preg_split('//u', $str, null, PREG_SPLIT_NO_EMPTY) as $chr) {
        if (mb_ereg_match("[ -~]", $chr)) {
            $len += 1;
        } else {
            $len += 2;
        }
    }
    
    return $len;
}

// 文字列の長さを、全角:2、半角:1 として PAD する。
function jp_str_pad($input, $pad_length, $pad_string = " ", $pad_style=STR_PAD_RIGHT) {
    $input_len = jp_str_len($input);
    $pad_len = $pad_length - $input_len;
    $pad = str_repeat($pad_string, $pad_len);
    $ret = "";
    if ($pad_style === STR_PAD_RIGHT) {
        $ret = $input . $pad;
    } else {
        $ret = $pad . $input;
    }
    return $ret;
}

// ペーストボード取得
$pb = mb_convert_encoding(`pbpaste`, 'UTF-8', 'SJIS');
//$pb = `pbpaste`;

// 入力チェック
if (strpos($pb, "\n") === false && strpos($pb, "\t") === false) {
    $msg = "改行/タブのいずれも含まれていないようです。";
    $title = "クリップボードの内容は TSV ではありません";
    `osascript -e 'display notification "$msg" with title "$title"'`;
    exit(0);
}

// 行分割
$linesSrc = explode("\n", $pb);

// 列分割
$lines = [];
foreach ($linesSrc as $line) {
    $lines[] = explode("\t", $line);
}

// 列幅取得
$colWidth = [];
for ($i = 0; $i < count($lines[0]); $i++) {
    foreach ($lines as $line) {
        $c = $line[$i];
        $w = jp_str_len($c);
        $colWidth[$i] = max($w, $colWidth[$i]);
    }
}

// 配列全体 PAD 関数定義
$pad_string = ' ';
$array_pad_fnc = function ($text, $cidx) use ($colWidth, &$pad_string) {
    $pad = jp_str_pad($text, $colWidth[$cidx], $pad_string, STR_PAD_RIGHT);
    return $pad;
};

// 出力関数定義
function echo_markdown_line($ary) {
    echo implode('|', $ary) . "\n";
}

// 見出し出力
$titles = array_map($array_pad_fnc, $lines[0], range(0, count($lines[0])-1));
echo_markdown_line($titles);

// 水平線出力
$pad_string = '-';
$tmp = array_fill(0, count($colWidth), '-');
$hr = array_map($array_pad_fnc, $tmp, range(0, count($tmp)-1));
echo_markdown_line($hr);

// データ出力
$pad_string = ' ';
for ($i = 1; $i < count($lines); $i++) {
    $colTxt = $lines[$i];
    $cols = array_map($array_pad_fnc, $lines[$i], range(0, count($lines[$i])-1));
    echo_markdown_line($cols);
}

環境

 以下の環境で開発・動作確認しました。

ProductName:   Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H1323
PHP 7.3.11 (cli) (built: Jun  5 2020 23:50:40) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies
CotEditor version: 2.7.4
Numbers.app version: 10.3.9

 また、Google Spreadsheet でも確認しました。

 CotEditor のスクリプトで動かした時のみ、pbpaste の結果を SJIS から UTF-8 に変換しないといけないのが、何となく納得できませんでした。Numbers.app 以外のアプリケーションから取得すると、また違うのかもしれません。


  1. Disk Burner(Power to Burn)の発表時だと思いますが、出典を見つけることはできませんでした。