###はじめに
Excelでセルごとの差分をみたいときないですか?
ただの true or false のセル比較ではなく、vimdiff みたいにどこに違いがあるかをみたいとき.そしてその差分をいつでも見られるように記録しておきたいとき.そんなときの為にpythonとマクロを使って差分を視覚化する機能を作りました.
###概要
ここでいう差分の視覚化とは以下の画像のように異列同行の2つの文字列を比較したときに片方にのみ存在する文字列を赤太字で強調表示することを指しています。
###備考
###開発環境
windows10
Python-3.9.4
使用ライブラリ: difflib, pywin32
###Python コード
python
# 起動しているExcelの列同士でvimdiffのようにdiffをとるためのコード
# pywin32でセルの中の文字列の一部のみに対して処理を施す方法がわからなかったため
# 妥協でExcelマクロにも頼ることとなった.
import difflib
import win32com.client
# カラー指定用変数
xlColorIndexAutomatic = -4105
# 開いているExcelbookを取得. 2つ以上開いている場合は1番最初に開いたものが指定されるはず.
# 一応bookは1つしか開いていない前提でやってます.
xl = win32com.client.GetObject(Class="Excel.Application")
# ActiveSheetを取得.
ws = xl.ActiveSheet
# Excelは起動したまま.
xl.Visible = True
# 比較したい列.
column1 = "B"
column2 = "C"
# 比較結果を書き込む列 比較したい列と同じにしておけば上書き可能.
column1_result ="E"
column2_result ="F"
# 比較したい行範囲を書き込む.
row_s = 3
row_e = 200
# 行ループ
for row in range(row_s,row_e+1):
# word1 : column2の文字列との差分を記録
# word2 : column1の文字列との差分を記録
word1 = ""
word2 = ""
# cell1, cell2 : 比較対象となるセル
row = str(row)
cell1 = ws.Range(column1+row)
cell2 = ws.Range(column2+row)
# cell1, cell2それぞれ空欄ではない且つ cell1.value == cell2.value ではないときのみ差分比較する。
if cell1.value is None or cell2.value is None:
pass
elif cell1.value != cell2.value :
ndiff = difflib.ndiff(cell1.value,cell2.value)
# 差分として取得してきた文字列(ndiff)をExcel上で処理しやすいように変換
# difflib.ndiffでは
# ・差分がない場合は先頭に空白" "を付加
# ・cell1.valueにのみ存在するものには "- "を付加
# ・cell2.valueにのみ存在するものには "+ "を付加
# 空白部分が邪魔となるため除去する.
# また "-"や"+"も一般的に扱われる文字列であるためあまり扱われることのない"$"を差分として出てきた文字の前に付加する
# 後々この"$"の位置を探し、その後ろの文字を赤字&太字化することで差分を視覚化する.
for i in ndiff:
# 1文字目が空白のとき(つまり差分がないとき)は先頭2文字(空白部分)を削除
if i.find(" ") == 0:
i = i[2:]
word1 = word1 + i
word2 = word2 + i
# 改行は差分として扱わないこととするため改行のみを残す
elif i.find("- \n") == 0:
i = i.replace("- \n","\n")
word1 = word1 + i
elif i.find("- ") == 0:
i = i.removeprefix("- ")
i = "$" + i
word1 = word1 + i
# 改行は差分として扱わないこととするため改行のみを残す
elif i.find("+ \n") == 0:
i = i.replace("+ \n","\n")
word2 = word2 + i
elif i.find("+ ") == 0:
i = i.removeprefix("+ ")
i = "$" + i
word2 = word2 + i
# 念のためelseも入れておく. 恐らく要らない.
else:
word1 = word1 + i
word2 = word2 + i
# Excelで扱えるように変換した差分文字列(word1, word2)を指定の列に書き込む
# Fontを指定しているのは書き込むセルのFont情報が元々赤字や太字であったときに
# 最初から赤字や太字で書き込まれてしまい差分がわからなくなることがあり、そうした事象を避けるため.
# ※xlColorIndexAutomatic = -4105 (上部で指定済)
ws.Range(column1_result+row).value = word1
ws.Range(column1_result+row).Font.ColorIndex = xlColorIndexAutomatic
ws.Range(column1_result+row).Font.Bold = False
ws.Range(column2_result+row).value = word2
ws.Range(column2_result+row).Font.ColorIndex = xlColorIndexAutomatic
ws.Range(column2_result+row).Font.Bold = False
# ここまでで差分文字の前に"$"が挿入されている状態を実現.
# セルの中の"$"の後ろの文字にだけ赤字化&太字化の処理を施し"$"を削除することで差分を可視化する.
# このセルの中の一部の文字列にだけ処理を施すという操作がpywin32でどのように実現できるか分からずマクロに頼ることとなった...
for row in range(row_s,row_e+1):
row = str(row)
if ws.Range(column1_result+row).value is None:
pass
elif ws.Range(column1+row).value == ws.Range(column2+row).value:
pass
else:
#差分の書き込まれているセルのみを選択し、選択したセルに対してマクロ"DiffWithPython"を実行していく
#マクロの中身は下記参照
ws.Range(column1_result+row).Select()
xl.Application.Run("PERSONAL.XLSB!DiffWithPython")
ws.Range(column2_result+row).Select()
xl.Application.Run("PERSONAL.XLSB!DiffWithPython")
###マクロ(vb)コード
このマクロの実行自体はpythonがやってくれます.
マクロ(vb)
Sub DiffWithPython()
Dim k, l() As Integer
'処理高速化のため処理の可視化をオフにします.
Application.ScreenUpdating = False
'セルの中の"$"の数を数えます.
'元の文字数と"$"を消し去った時の文字数の引き算で炙り出します.(もっと簡単な方法あると思う)
k = Len(Selection.Value) - Len(Replace(Selection.Value, "$", ""))
If k > 0 Then
'k個の要素を持つリストを作ります. 0スタートなのでk-1です.
ReDim l(k - 1) As Integer
'"$"が何文字目にあるか文字列の先頭から探してリストl()に記録します.
'みつけた"$"は削除して次の$を探します.
'こうすることで"$"を削除したあとのリストl()にある値は差分文字の位置になってます.
For n = 1 To k
l(n - 1) = InStr(Selection.Value, "$")
Selection.Value = WorksheetFunction.Replace(Selection.Value, l(n - 1), 1, "")
Next n
'l()文字目にある差分文字を赤字化&太字化します.
' -16776961 が赤らしい.(もっと良い書き方あると思う) 変数に入れてやろうとしたらオーバーフローしました...
For n = 1 To k
Selection.Characters(l(n - 1), 1).Font.Color = -16776961
Selection.Characters(l(n - 1), 1).Font.Bold = True
Next n
End If
'オフにしていた処理の可視化をオンにして終わりです.
Application.ScreenUpdating = True
End Sub
私と同じようなことを思っていてこの機能が役に立ったって人が1人でもいたら凄く嬉しいので教えてください!以上です.