🐻‍❄️

テーブルをクリップボードにコピーしてExcelに「正しく」ペーストする

に公開

概要

ReactにはさまざまなData Grid Component[1]があります。

一方、それらの大多数はExcelにうまくコピーできないという問題があります。
それを解決するために、いろいろ試してみたのでその備忘録を記します。

検証環境

  • macOS
  • Excel for Mac
    • Microsoft 365
    • ver. 16.96.1

結論

TSV形式だとうまくいかない(少なくとも、Mac環境では)。
HTML(Blob)形式でコピーするべし。

検証

最小環境を作る

特に工夫も何もないデータ構造です。

TSV形式でコピーする

walkframe/gridsheetの実装を参考にしました。

https://github.com/walkframe/gridsheet/blob/master/packages/react-core/lib/clipboard.ts

上記のような感じで、テーブルをTSVに変換して、クリップボードに書き込んでいます。
これを、先ほどの最小環境に付け足してみます。

実際に動かして、Consoleに吐き出させると以下のようになります。
TSVに変換できていますね。

これを、Excelに貼り付けてみるとどうなるでしょうか。。?

あ、、、\tが失われてしまいました。。。

一部情報では、UNIX系の\nではうまくいかない。という情報があったので、\r\nでも試しましたが、やはり同じようです🧐🧐🧐

HTML形式でコピーする

実はこっちが本命です。
HTMLをBlob形式にして、クリップボードに書き込むというものです。

参考:copy html table into clipboard with format

コアはこれです。

const tableCopy = (table: string[][]) => {
    let html = "";

    html += "<table>";
    table.forEach((row) => {
        html += "<tr>";
        row.forEach((value) => {
            html += `<td>${value}</td>`;
        });
        html += "</tr>";
    });
    html += "</table>";

    console.log(html);

    const htmlBlob = new Blob([html], { type: "text/html" });

    navigator.clipboard.write([
        new ClipboardItem({
            "text/html": htmlBlob,
        }),
    ]);
};

これを、Excelに貼り付けてみるとどうなるでしょうか。。?

うまくいきました!

ただ、TSVという扱い易い形式ではなくBlobに変換してしまっているので、用途等は見極めてご使用ください。

Spreadsheetではどうやってる?

ということで、うまく行ってるSpreadsheetの実装をのぞいてみましょう。

ということで、Clipboardの中を覗くツールを作ってみます。

import "./App.css";

function App() {
    return (
        <>
            <button
                onClick={async () => {
                    const res = await navigator.clipboard.read();
                    res.forEach((val) => {
                        val.types.forEach((type) => {
                            val.getType(type)
                                .then((contents) => {
                                    return contents.text();
                                })
                                .then((text) => {
                                    console.log(type, text);
                                });
                        });
                    });
                }}
            >
                クリップボードを覗く
            </button>
        </>
    );
}

export default App;

Spreadsheetからデータをコピーしてみましょう。

すると以下のようなデータが格納されていることがわかりました。

text/html形式
<google-sheets-html-origin>
<style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style>
<table xmlns="http://www.w3.org/1999/xhtml" cellspacing="0" cellpadding="0" dir="ltr" border="1" style="table-layout:fixed;font-size:10pt;font-family:Arial;width:0px;border-collapse:collapse;border:none" data-sheets-root="1" data-sheets-baot="1">
    <colgroup>
        <col width="100"/>
        <col width="100"/>
        <col width="100"/>
    </colgroup>
    <tbody>
        <tr style="height:21px;">
            <td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;">id</td>
            <td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;">name</td>
            <td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;">email</td>
        </tr>
        <tr style="height:21px;">
            <td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;">1</td>
            <td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;">Bob</td>
            <td style="border-right:1px solid transparent;overflow:visible;padding:2px 0px 2px 0px;vertical-align:bottom;">
                <div style="white-space:nowrap;overflow:hidden;position:relative;width:196px;left:3px;">
                    <div style="float:left;">
                        bob@example.com
                    </div>
                </div>
            </td>
        </tr>
        <!-- 略 -->
    </tbody>
</table>
text/plain形式
id	name	email
1	Bob	bob@example.com
2	Alex	alex@example.com
3	Ana	ana@example.com

TSVとHTMLどっちも格納してるんですね

脚注
  1. 本記事ではData Grid Componentという用語を、Google Spreadsheetの表部分のような、Excelのように選択, コピー, ペースト, 編集できるもの。として使っています。 ↩︎

Discussion