[Excel差分比較] セルの値以外の要素の差分を比較したい


## はじめに
こんにちは！株式会社GxPに今年新卒として入社した香取です。
この記事は[グロースエクスパートナーズAdvent Calender 2024](https://qiita.com/advent-calendar/2024/gxp)
の18日目の記事です
配属一か月目に業務システムで使用しているExcelファイルをうっかりデグレさせ、障害を発生させてしまいました。今回はその後の再発防止策として開発した「Excel差分比較ツール」について書いていこうと思います。

## 背景
開発中のシステムにはPDFを発行する機能があり、Excelで作成したテンプレートにJavaで値を入力し、PDFとして出力する仕組みを採用しています。値の入力は、テンプレート内のセルを[名前付き範囲](https://atmarkit.itmedia.co.jp/ait/articles/1709/06/news021.html)として設定し、Java側で名前を指定することで実現しています。

今回の障害は、私がテンプレートを修正する際に、誤って古いバージョンのExcelファイルを編集してしまったことが原因です。その結果、セルの値や名前付き範囲が変更され、PDFが正しく出力されなくなりました。

このような問題を防ぐため、Excelファイルの差分を確認できる仕組みを作る必要があると考え、「Excel差分比較ツール」を開発しました。

## 問題
+ Excelのようなバイナリファイルはプルリクエストや`git diff`などで差分を確認することができないため、誰も私がテンプレートを破壊していることに気付けなかった
+ 修正箇所が1セルだけだったことも油断につながってしまった


## やりたいこと(ツールの目的)
+ Excelファイルの差分を確認したい
+ 値の差分だけでなく、値以外の要素(今回は名前付き範囲)の差分も見たい
+ コンソールで結果を表示し、CIに組み込めるようにしたい
+ Windows,Mac両方で差分を確認できるようにしたい


## 検討したツール

[方眼Diff](https://qiita.com/nmby/items/ca7227a1048528b1bf31)
変更点が視覚的にわかりやすかったが、値の差分しか見られなかったのとコンソール出力できなかったので使わなかった

[Excelアドイン](https://support.microsoft.com/ja-jp/topic/-info-excel-%E3%81%AE-com-%E3%82%A2%E3%83%89%E3%82%A4%E3%83%B3%E3%81%8A%E3%82%88%E3%81%B3%E3%82%AA%E3%83%BC%E3%83%88%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-%E3%82%A2%E3%83%89%E3%82%A4%E3%83%B3-91f5ff06-0c9c-b98e-06e9-3657964eec72)
Excelにアドインを追加することでExcelの差分を見ることができる。
値の差分だけでなく、名前付き範囲やフォントなどの値以外のものを比較することもできる。
コンソール出力ができないのでCI出力できないのと、Windowsでしか使えないという理由で落選(チームにmacユーザーがいた)

結局、既存のツールだけではやりたいことが実現できるものを見つけることができませんでした…

## 実際に作ったもの
Excel差分比較をするために[Apach Tika](https://tika.apache.org)と[Apach POI](https://poi.apache.org)を使用して、`git diff`で差分を出力できるようにしました。

Apache TikaはPDFやWordファイルからテキストを取得することができるjarファイルで、Excelファイル(.xlsx)のセルの値を取得することにも対応しています。
しかし、セルの値しか取得できないので値以外を取得するためにApache POIというjavaのライブラリを使用してjarファイルを作っていきます。(全部POIで実装してもよかったが手間だったので値はTikaで取得するようにしました)


| 比較するもの | 使うもの |
|:-:|:-:|
|  セルの値 | Apache Tika|
|  値以外 (名前付き範囲,セルの色,フォントなど) | Apache POI |



### Apache Tikaの準備

tika-app.X.X.X.jar (X.X.Xはバージョン)を[公式サイト](https://tika.apache.org/download.html "Download Apache Tika")からダウンロードしたら準備OK！


### Apache POIの準備
POIはjavaのライブラリなのでpom.xmlやbuild.gradleに追記していくと使えるようになります。

今回はmavenでexceldiffという名前の新しいプロジェクトを作成し、pom.xmlのdependenciesに以下の記述を追加しました。
``` pom.xml
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.3</version>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>5.2.3</version>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml-schemas</artifactId>
  <version>4.1.2</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.4</version>
</dependency>
```

### jarファイルの作成
以下のコードをメインクラスとして実装し、JARファイルを作成します。

``` exceldiff.java
package com.example;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class exceldiff {

  public static void main(String[] args) throws IOException {
    if (args.length != 1) {
      System.out.println("Usage: java CustomExcelDiff <file-path>");
      return;
    }

    FileInputStream file = new FileInputStream(new File(args[0]));
    Workbook workbook = new XSSFWorkbook(file);

    // ワークブック内のすべてのシートをループ
    for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
      Sheet sheet = workbook.getSheetAt(i);
      System.out.println("Sheet: " + sheet.getSheetName());

      // 名前付き範囲をすべて取得
      List<? extends Name> name = workbook.getAllNames();
      name.forEach(
          (Name n) -> {
            String reference = n.getRefersToFormula(); //名前付き範囲の範囲を取得
            System.out.println("(" + reference + ") " + n.getNameName()); //名前付き範囲を出力

          }
      );

      // ここの内容を書き換えるとフォントや色などの他要素も取得することができる
      
    }
    workbook.close();
    file.close();
  }
}
```

次に以下のリンクを参考にjarファイル出力する(exceldiff.jarを作成)

https://qiita.com/pike3/items/ff2f0ce3b00d3d886163

### git diff で差分を出力できるようにする
git diffをした際に比較対象がExcelファイル(.xlsx)であれば、tikaと今回作ったexceldiffを起動するようなshファイルを呼び出すように設定します。


まずgit管理しているリポジトリ内に.gitattributesファイルを作成し、次のように記述します。
``` .gitattribute
*.xlsx diff=openxlsx
*.XLSX diff=openxlsx
```
これでファイルの拡張子が`.xlsx`のものを`git diff`で比較するときに`git config`内のopenxlsxという設定を元に`git diff`してくれるようになります（設定は後述)

次にshファイルを作成します(`exceldiff.sh`)
```exceldiff.sh
#!/bin/bash
cd `dirname $0`
java -Dfile.encoding=UTF-8 -cp './exceldiff.jar' com.example.exceldiff "$1"
java -Dfile.encoding=UTF-8 -jar './tika-app-2.9.2.jar' -t "$1"
```
`'./exceldiff-1.0.jar'`と`'./tika-app-2.9.2.jar'`に関してはjarファイルのある場所によって変えてください。

次に差分出力したいリポジトリで`git config`の設定をします。
```terminal
git config --local diff.openxlsx exceldiff.shのパス
```

ここまででgit diffでexcelファイルの差分比較する準備が整いました

### 実際に差分出力してみる
今回はコミットしているExcelファイルと現在のExcelファイルをgit diffで比較してみます

**更新前**(コミットしているファイル)は画像のようになっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877870/4d4e19fe-594b-5536-71b4-72c0a3737b1a.png)

また、`C1`セルには`テスト1`という名前付き範囲が設定されており、カーソルを合わせるとこんな感じにセル番号(`C1`)ではなく`テスト1`という表示になっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877870/33c2535d-7975-980b-4864-bfca3f18384c.png)

**更新後**の方は次の画像の通りで`A1`と`B1`の値が書き換得ている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877870/11c77aec-b994-4554-6cf3-ae999ad32cfb.png)

また`C1`についていた名前付き範囲も更新されており、`テスト2`としている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877870/a4ef967b-1d2c-a76b-e1e2-122111e390f6.png)

これをgit diffしてみる
```diff
diff --git a/demo.xlsx b/demo.xlsx
index f80dd05..f2d9db5 100644
--- a/demo.xlsx
+++ b/demo.xlsx
@@ -1,6 +1,6 @@
 Sheet: Sheet1
-(Sheet1!$C$1) テスト1 
+(Sheet1!$C$1) テスト2 
 Sheet1
-       更新前Excel コウシンマエ セルの値AAAA アタイ 
+       更新後Excel コウシン ゴ セルの値BBBB アタイ

```
このように出力される
中段辺りにある↓はPOIで作ったexceldiffによる出力です
```diff
 Sheet: Sheet1
-(Sheet1!$C$1) テスト1 
+(Sheet1!$C$1) テスト2 
```
変更した名前付き範囲も差分確認できています。


下の方にある↓はTikaによる出力になります。(漢字にフリガナを付けてくれている)
```diff
 Sheet1
-       更新前Excel コウシンマエ セルの値AAAA アタイ 
+       更新後Excel コウシン ゴ セルの値BBBB アタイ
```
値の差分もちゃんと比較してくれてます


## おわりに
最後まで読んでいただきありがとうございました！
Excelの名前付き範囲の差分を見たい…!というかなりニッチな内容の記事でしたがいかがでしたでしょうか?
私たちのチームでは実際に今回の差分比較ツールをCIに組み込んで、自動で差分確認を行うようにしています。


Excelの値の差分比較している記事は何個か見かけるのですが、名前付き範囲の差分比較をしている記事を見つけることができず😢
結局自分で作ることになってしまいましたが、良い方法を知っているという方がいれば教えていただきたいです...!



## 参考
https://qiita.com/shuhei/items/6a18d968051378d7ac1a

https://ohbarye.hatenablog.jp/entry/2014/05/01/220500

https://nulltemp.hatenablog.com/entry/2018/12/23/232456

https://blog-smatsuzaki.hatenablog.com/entry/2023/06/24/195707


今回の記事を書くにあたって調べなおしたら出てきた記事

https://qiita.com/RyoWakabayashi/items/bde84624bffc1f68071d

CIに組み込んでプルリクにコメントしてくれるという機能を作っている記事

https://tokukichi.com/posts/1298