📄

SheetJSで空の先頭行がデータ範囲に含まれる

2024/11/01に公開

要約

  • SheetJSでExcelを読み取ったとき、空の先頭行がデータ範囲に含まれることがある
  • 原因は、空行に見えても内部的なデータが残っているため
  • 解決方法(2種類)
    1. Excel自体から先頭の空行を削除する
    2. データ範囲を検証し、正しくなければ先頭行を削除する

問題点:空の先頭行がデータ範囲に含まれる

Excelの読み込みを行うシステムでは、あらかじめ決まっているテンプレートに沿ってデータが記載されていることがよくあると思います。
そしてテンプレートによっては、シートの1行目に何も入力せず、余白として使用する場合があります。
このケースでは、常に2行目以降をデータ範囲として扱う必要が出てきます。

SheetJSは、基本的にデータの存在しない行列をデータ範囲外として扱います。特別なオプションや処理を追加する必要はありません。
しかし、データが存在しないように見えても内部的に残っている場合、対象行はデータ範囲に含まれてしまいます。

空の先頭行がデータ範囲に含まれているシートを取得し、JSONに変換しました。

const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(sheet);

出力内容は下記のようになり、空白セルがヘッダーとして扱われています。(__EMPTYは空白ヘッダーの代替文字列)

[
  {
    "__EMPTY_1": "項目1",
    "__EMPTY_2": "項目2",
    "__EMPTY_3": "項目3"
  },
  {
    "__EMPTY_1": "データ1_項目1",
    "__EMPTY_2": "データ1_項目2",
    "__EMPTY_3": "データ1_項目3"
  },
  {
    "__EMPTY_1": "データ2_項目1",
    // ...
  }
]

内部的なデータは、Excelセルを編集したときに残ることがあります。
このため、Excelを手動編集してからシステムで読み込む場合は、空の先頭行がデータ範囲に含まれるケースと含まれないケースの考慮が必要となります。

解決方法

1. Excel自体から先頭の空行を削除する

Excel側を変更できるのであれば、先頭行から空行を削除することが最も簡単な解決方法です。
ただ、システムの都合上変更が困難な場合もありますので、その場合は次に示すとおりデータ整形で対応していきます。

2. データ範囲を検証し、正しくなければ先頭行を削除する

ファイル読み込み後、対象シートのデータ範囲を検証することができます。
下記のコードでは、Excelの1枚目のシートを取得しています。

const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];

sheetの中身は次のようになっています。

{
  !ref: "A1:K4",
  B2: {
    h: "項目1",
    // ...
  },
  B3: // ...
}

!refがシートのデータ範囲です。ここではA1が開始セルとなっています、しかし実際にはA1の値は取得されておらず、B2のデータから始まっており、空の先頭行がデータ範囲に含まれていることが分かります。

次のコードは、!refの値を検証してシートから先頭行を削除しています。

const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
if (sheet["!ref"] && sheet["!ref"].startsWith("A1")) {
  const range = XLSX.utils.decode_range(sheet["!ref"]);
  range.s.r += 1;
  sheet["!ref"] = XLSX.utils.encode_range(range);
}

decode_rangeencode_rangeはデータ範囲の変換ユーティリティです。
range.s.rはデータ範囲開始セルの行番号を示しており、1を加算することで先頭行をデータ範囲から除外しています。

また、変換時に範囲を指定して先頭行を削除することもできます。

const jsonData = XLSX.utils.sheet_to_json<FileJson>(sheet, {
  range:
    sheet["!ref"] && sheet["!ref"].startsWith("A1") ? 1 : undefined,
});

ファイル読み取り時の除外はできなかった

試した限りでは、ファイルの読み取り時に先頭の空白行をデータ範囲から除外することはできませんでした。
.xlsx.xlsは自身のデータ範囲を保持しており、SheetJSはデフォルトでその値に従って読み込みを行います。
nodimオプションを付与することで、SheetJS側でデータ範囲を再検証できるそうですが、今回のパターンでは指定してもデータ範囲の変化はありませんでした。

const workbook = XLSX.read(binaryStr, { nodim: true });

参考

NCDCエンジニアブログ

Discussion