「HTML5のtableタグをCSV出力（ダウンロード）してみる」 をリファクタリングしてみた

## 元記事

[HTML5のtableタグをCSV出力（ダウンロード）してみる - Qiita](https://qiita.com/skmk/items/e29bd8046820f1c9cfb0)

HTMLに表示されているtable要素をまるっと簡単にダウンロードできないかなーとググって最初にあたった記事です。
2018年の記事でIE対応とかイマドキ感にかけるので、イマドキ風にTypeScriptでChatGPTにリファクタリングしてもらいました。


## GPT3.5によるリファクタリング

[Poe - TML5のtableタグをCSV出力（ダウンロード）してみるリファクタリング](https://poe.com/s/voLP4LNsjBUlBh9osEyi)

以下は、より効率的なリファクタリング例です。

```tsx
function handleDownload(): void {
  const bom: Uint8Array = new Uint8Array([0xEF, 0xBB, 0xBF]); // 文字コードをBOM付きUTF-8に指定
  const table: HTMLTableElement | null = document.getElementById('table1') as HTMLTableElement; // id=table1という要素を取得
  let data_csv: string = ''; // ここに文字データとして値を格納していく

  if (!table) {
    return;
  }

  for (let i = 0; i < table.rows.length; i++) {
    const cells: HTMLCollectionOf<HTMLTableDataCellElement> = table.rows[i].cells;
    data_csv += [...cells].map(cell => cell.innerText).join(","); // HTML中の表のセル値をdata_csvに格納
    data_csv += '\\n'; // 行終わりに改行コードを追加
  }

  const blob: Blob = new Blob([bom, data_csv], { type: 'text/csv' }); // data_csvのデータをcsvとしてダウンロードするためのBlobオブジェクトを作成

  if (window.navigator.msSaveBlob) {
    // IEの場合の処理
    window.navigator.msSaveBlob(blob, 'test.csv');
    //window.navigator.msSaveOrOpenBlob(blob, "test.csv");// msSaveOrOpenBlobの場合はファイルを保存せずに開ける
  } else {
    const downloadLink: HTMLAnchorElement | null = document.getElementById('download') as HTMLAnchorElement;
    if (downloadLink) {
      downloadLink.href = window.URL.createObjectURL(blob);
    }
  }

  data_csv = ''; // data_csvオブジェクトはもういらないので空文字列で初期化してメモリを開放
}

```

主な変更点は次のとおりです。

1. `table` の存在チェックを行い、存在しない場合は関数を終了させるようにしました。
2. `map` メソッドを使用してセルの値をマッピングし、`join` メソッドを使用してカンマで結合することで、ループと条件分岐を削減しました。
3. 行終わりの改行コードをループの外側で追加するように変更しました。
4. `data_csv` を空文字列で初期化する箇所を移動し、ループの内側から外側に移動しました。
5. `cells` の取得においてスプレッド演算子を使用し、HTMLCollection を配列に変換しました。これにより、`map` メソッドが直接使用できるようになりました。
6. `data_csv` の初期化には空文字列を使用し、`null` ではなくなりました。

## GPT-4によるリファクタリング

さらに自分用に改良を加え、それをGPT-4に直してもらいました。
改良内容は以下です。

* IE対応を削除した
* ボタン(風に見えるaタグ)をあらかじめHTMLに配置し、そのURLを変更する関数に切り出し
* Bootstrap5を取り入れてダウンロードアイコンで表示

```HTML:index.html
<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
</head>
<body>
    <a name="download-button" class="btn btn-outline-secondary" href="#" download disabled>
      <i class="bi bi-cloud-download"></i>
    </a>
    <table name="result-table">
      <!-- テーブル要素は省略 -->
    </table>
</body>
```


```CSS:style.css
a[disabled] {
    pointer-events: none;
}
```



<details><summary>改良を加えたコードをプロンプトとして渡しました。(折りたたみ)</summary>

```tsx
// table要素をCSVとしてダウンロードするリンクを作成する
const handleDownload = (): void => {
const table: HTMLTableElement | null = document.querySelector(
`table[name='result-table']`,
);
if (!table) {
console.error("table not found.");
return;
}

const downloadLink: HTMLAnchorElement | null = document.querySelector(
`a[name='download-button']`,
);
if (!downloadLink) {
console.error("download link not found.");
return;
}

// 検索結果がなければ終了
const len: number = table.rows.length;
if (len < 2) {
// disable要素がなければ付与する
downloadLink.href = "#";
downloadLink.classList.add("btn-outline-secondary");
downloadLink.classList.remove("btn-primary");
downloadLink.setAttribute("disabled", "");
console.error("filter result not found.");
return;
}

let data_csv: string;
// テーブル要素を取得してstringに格納
for (let i = 0; i < len; i++) {
const cells: HTMLCollectionOf<HTMLTableDataCellElement> =
table.rows[i].cells;
// 表のセ��の値をcsv文字列として結合
�    data_csv += [...cells].map((cell) => cell.innerText).join(",");
data_csv += "\n";
}
const bom: Uint8Array = new Uint8Array([0XEF, 0XBB, 0XBF]); // UTF8指定
const blob: Blob = new Blob([bom, data_csv], { type: "text/csv" }); // ダウンロードするBlobオブジェクト

downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.classList.remove("btn-outline-secondary");
downloadLink.classList.add("btn-primary");
downloadLink.removeAttribute("disabled");
};
```

リファクタリングしてください。
</details>

> ChatGPT: 以下は提供されたコードをリファクタリングしたものです。「ボタンを作成する」、「テーブルデータをCSVとして取得する」、
「ダウンロードリンクを設定する」の3つの関数に分割しています。

```tsx
const handleDownload = (): void => {
  const table: HTMLTableElement | null = document.querySelector(`table[name='result-table']`);
  const downloadLink: HTMLAnchorElement | null = document.querySelector(`a[name='download-button']`);

  if (!table || !downloadLink) {
    console.error("table or download link not found.");
    return;
  }

  const data_csv: string = getTableDataAsCsv(table);

  if (!data_csv) {
    initializeButton(downloadLink);
    console.error("filter result not found.");
    return;
  }

  setDownloadLink(downloadLink, data_csv);
};

const initializeButton = (downloadLink: HTMLAnchorElement): void => {
  downloadLink.href = "#";
  downloadLink.classList.add("btn-outline-secondary");
  downloadLink.classList.remove("btn-primary");
  downloadLink.setAttribute("disabled", "");
};

const getTableDataAsCsv = (table: HTMLTableElement): string => {
  const len: number = table.rows.length;

  if (len < 2) return "";

  let data_csv = "";

  for (let i = 0; i < len; i++) {
    const cells: HTMLCollectionOf<HTMLTableDataCellElement> = table.rows[i].cells;
    data_csv += [...cells].map(cell => cell.innerText).join(",") + "\\n";
  }

  return data_csv;
};

const setDownloadLink = (downloadLink: HTMLAnchorElement, data_csv: string): void => {
  const bom: Uint8Array = new Uint8Array([0XEF, 0XBB, 0XBF]);
  const blob: Blob = new Blob([bom, data_csv], { type: "text/csv" });

  downloadLink.href = window.URL.createObjectURL(blob);
  downloadLink.classList.remove("btn-outline-secondary");
  downloadLink.classList.add("btn-primary");
  downloadLink.removeAttribute("disabled");
};

```

> このように処理を分割することで、各関数は一つの具体的なタスクだけを扱います。これによりコードの可読性が向上し、将来的にバグを
修正しやすくなったり、新機能を追加しやすくなるというメリットがあります。



エントリーポイントは`handleDownload()`です。`handleDownload()`を実行するタイミングは、テーブル作成スクリプトの直後においたり、`onclick=handleDownload()`を設定したボタンを設置するなどしてください。

私の力は１割くらいしか使っていません。
元コードの作者様の@skmk(Miki Shunsuke )とChatGPTおよびOpenAI社に敬意を評します。