この記事では、ワード文書比較ツールを紹介します。
このツールは、2つのワードファイルを比較して結果ワードファイルを出力するシンプルなツールです。
この記事の一番最後にあるPythonコードをコピーして使用してください。
本記事にはPythonコードの使用方法の解説はありませんので、既に知識のある方か、独力で使用できる方が対象になります。
このツールで行う比較をワードの標準機能で行いたい方は、別の記事をご参照ください。
コード実行するには、Wordがパソコンにインストールされている必要があります

ツールの使い方
この記事の最後にあるPythonコードを実行すると、以下の画面が現れます。

使い方は、以下の通りです。
① 比較元(変更前)のワードファイルを選択します
② 比較先(変更後)のワードファイルを選択します
③ 比較結果ファイルの出力先フォルダを選択します
④ 比較実行ボタンを押します
比較実行ボタンを押すと、比較処理が始まり、指定した出力先フォルダ内に比較結果ファイルが自動出力されます。
ファイルは、比較元ファイル名_VS_比較先ファイル名_日付6桁_時間6桁.docxの形式で出力されます。
比較結果ファイル
例えば、以下の2つのワードファイルをこのツールで比較したとします。

この場合、比較結果ワードファイルは、以下のようになります。

この結果は、ワードの標準機能を使用して行う比較結果と全く同じになります。
このツールでは、ワードの標準機能の比較実行画面で説明すると、以下の条件(デフォルト)で比較を行っています。

上記の条件に対応するコード箇所は以下になります。
必要に応じてカスタマイズしてください。

パラメーターの説明は以下の通りです。
Destination = 0 (元のドキュメントに比較結果を表示)
Destination = 1 (修正済みドキュメントに比較結果を表示)
Destination = 2 (新しいドキュメントに比較結果を表示)
Granularity = 0 (文字レベルでの比較)
Granularity = 1 (単語レベルでの比較)
書式設定 CompareFormatting
大文字/小文字の変更 CompareCaseChanges
空白文字 CompareWhitespace
表 CompareTables
ヘッダー CompareHeaders
脚注 CompareFootnotes
テキストボックス CompareTextboxes
フィールド CompareFields
コメント CompareComments
移動 CompareMoves
上記の各オプションを無効にしたい場合(比較事項から外したい場合)は、falseを設定してください。
ワードの比較について詳しく知りたい方は、別の記事をご参照ください。
Pythonコード
以下は、このツールのPythonコードです。
コピペして使ってください。
import tkinter as tk
from tkinter import filedialog
from tkinter import ttk
from tkinter import messagebox
import os
import win32com.client
import pythoncom
import time
from datetime import datetime
class WordComparisonApp:
def __init__(self, root):
self.root = root
self.root.title("Word文書比較ツール")
self.root.geometry("800x470")
self.root.resizable(True, True)
# カラーテーマ設定
self.colors = {
"primary": "#2c3e50", # ダークブルー (ヘッダー背景)
"secondary": "#d6eaf8", # 薄い水色 (ボタン背景)
"accent": "#aed6f1", # やや濃い水色 (ボタンホバー)
"text_light": "#ecf0f1", # ライトグレー (ヘッダーテキスト)
"text_dark": "#2c3e50", # ダークブルー (通常テキスト)
"bg_light": "#f5f5f5", # 非常に明るいグレー (背景)
"success": "#27ae60", # グリーン (成功表示)
"warning": "#f39c12", # オレンジ (警告表示)
"error": "#e74c3c" # レッド (エラー表示)
}
# フォント設定
self.fonts = {
"header": ("Helvetica", 14, "bold"),
"title": ("Helvetica", 12, "bold"),
"normal": ("Helvetica", 10),
"small": ("Helvetica", 9)
}
# アプリケーションのスタイル設定
self.style = ttk.Style()
self.style.theme_use('clam') # テーマを設定
# ボタンスタイル
self.style.configure('TButton',
font=self.fonts["normal"],
background="#d6eaf8", # 薄い水色
foreground=self.colors["text_dark"])
self.style.map('TButton',
background=[('active', "#aed6f1")], # ホバー時はさらに濃い水色
foreground=[('active', self.colors["text_dark"])])
# ラベルスタイル
self.style.configure('TLabel',
font=self.fonts["normal"],
background=self.colors["bg_light"],
foreground=self.colors["text_dark"])
# タイトルラベルスタイル
self.style.configure('Title.TLabel',
font=self.fonts["title"],
background=self.colors["bg_light"],
foreground=self.colors["text_dark"])
# ヘッダースタイル
self.style.configure('Header.TLabel',
font=self.fonts["header"],
background=self.colors["primary"],
foreground=self.colors["text_light"])
# プログレスバースタイル
self.style.configure("TProgressbar",
background=self.colors["secondary"],
troughcolor=self.colors["bg_light"])
# ルート背景設定
self.root.configure(background=self.colors["bg_light"])
# 比較元のパス
self.source_path = tk.StringVar()
self.source_path.set("まだ選択されていません")
# 比較先のパス
self.target_path = tk.StringVar()
self.target_path.set("まだ選択されていません")
# 結果出力先のパス
self.output_path = tk.StringVar()
self.output_path.set("まだ選択されていません")
# 処理状況のテキスト
self.status_text = tk.StringVar()
self.status_text.set("")
# UIの構築
self.create_widgets()
def create_widgets(self):
# メインフレーム
main_frame = ttk.Frame(self.root, padding="20 20 20 20", style='TFrame')
main_frame.grid(row=0, column=0, sticky="nsew")
main_frame.configure(style='TFrame')
# ヘッダー
header_frame = ttk.Frame(main_frame, style='TFrame')
header_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 20))
header_frame.configure(style='TFrame')
header_bg = ttk.Label(header_frame, text="", style='Header.TLabel')
header_bg.grid(row=0, column=0, sticky="nsew", pady=0)
header_label = ttk.Label(header_frame, text="Word文書比較ツール", style='Header.TLabel')
header_label.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
# ファイル選択セクション
file_frame = ttk.LabelFrame(main_frame, text="ファイル選択", padding="10 10 10 10")
file_frame.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(0, 15))
# 比較元ファイル
source_label_title = ttk.Label(file_frame, text="比較元:", style='Title.TLabel')
source_label_title.grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
source_label_path = ttk.Label(file_frame, textvariable=self.source_path, wraplength=550)
source_label_path.grid(row=0, column=1, columnspan=2, sticky=tk.W, padx=5, pady=5)
source_btn = ttk.Button(file_frame, text="比較元ファイル選択", command=self.select_source_file)
source_btn.grid(row=0, column=3, padx=5, pady=5, sticky="e")
# 比較先ファイル
target_label_title = ttk.Label(file_frame, text="比較先:", style='Title.TLabel')
target_label_title.grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
target_label_path = ttk.Label(file_frame, textvariable=self.target_path, wraplength=550)
target_label_path.grid(row=1, column=1, columnspan=2, sticky=tk.W, padx=5, pady=5)
target_btn = ttk.Button(file_frame, text="比較先ファイル選択", command=self.select_target_file)
target_btn.grid(row=1, column=3, padx=5, pady=5, sticky="e")
# 結果出力先
output_label_title = ttk.Label(file_frame, text="結果出力先:", style='Title.TLabel')
output_label_title.grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
output_label_path = ttk.Label(file_frame, textvariable=self.output_path, wraplength=550)
output_label_path.grid(row=2, column=1, columnspan=2, sticky=tk.W, padx=5, pady=5)
output_btn = ttk.Button(file_frame, text="出力先フォルダ選択", command=self.select_output_folder)
output_btn.grid(row=2, column=3, padx=5, pady=5, sticky="e")
# 比較ボタンセクション
action_frame = ttk.Frame(main_frame, padding="0 10 0 10")
action_frame.grid(row=2, column=0, columnspan=3, sticky="ew")
# 中央に配置された大きな比較実施ボタン
compare_btn = ttk.Button(action_frame, text="比較実施", command=self.compare_files)
compare_btn.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
action_frame.columnconfigure(0, weight=1) # ボタンを中央に配置
# ステータスセクション
status_frame = ttk.LabelFrame(main_frame, text="処理状況", padding="10 10 10 10")
status_frame.grid(row=3, column=0, columnspan=3, sticky="nsew", pady=(0, 10))
# 処理状況ラベル
self.status_label = ttk.Label(status_frame, textvariable=self.status_text, font=self.fonts["normal"])
self.status_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
# プログレスバー
self.progress_bar = ttk.Progressbar(status_frame, mode="determinate", maximum=100, style="TProgressbar")
self.progress_bar.grid(row=1, column=0, padx=5, pady=5, sticky="ew")
# 初期状態ではステータスが空なので、テキストを設定
self.status_text.set("準備完了。比較するファイルを選択してください。")
# レイアウト調整
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
file_frame.columnconfigure(1, weight=1)
status_frame.columnconfigure(0, weight=1)
def select_source_file(self):
file_path = filedialog.askopenfilename(
title="比較元ワードファイルを選択",
filetypes=[("Word files", "*.docx;*.doc"), ("All files", "*.*")]
)
if file_path:
# 比較先と同じファイルが選択されたかチェック
if file_path == self.target_path.get() and self.target_path.get() != "まだ選択されていません":
messagebox.showwarning("警告", "比較元と比較先に同じファイルが選択されています。\n別のファイルを選択してください。")
return
self.source_path.set(file_path)
def select_target_file(self):
file_path = filedialog.askopenfilename(
title="比較先ワードファイルを選択",
filetypes=[("Word files", "*.docx;*.doc"), ("All files", "*.*")]
)
if file_path:
# 比較元と同じファイルが選択されたかチェック
if file_path == self.source_path.get() and self.source_path.get() != "まだ選択されていません":
messagebox.showwarning("警告", "比較元と比較先に同じファイルが選択されています。\n別のファイルを選択してください。")
return
self.target_path.set(file_path)
def select_output_folder(self):
# フォルダ選択ダイアログを表示
folder_path = filedialog.askdirectory(
title="結果出力先フォルダを選択"
)
if folder_path:
self.output_path.set(folder_path)
def update_progress(self, value, message):
self.progress_bar["value"] = value
self.status_text.set(message)
self.root.update()
time.sleep(0.1) # UIの更新が見えるように少し遅延
def compare_files(self):
# 必要な情報が全て選択されているかチェック
source = self.source_path.get()
target = self.target_path.get()
output = self.output_path.get()
missing_items = []
if source == "まだ選択されていません":
missing_items.append("比較元ワードファイル")
if target == "まだ選択されていません":
missing_items.append("比較先ワードファイル")
if output == "まだ選択されていません":
missing_items.append("結果出力先フォルダ")
# 未選択項目がある場合
if missing_items:
missing_str = "、".join(missing_items)
messagebox.showwarning("警告", f"以下の項目が選択されていません:\n{missing_str}\n\n選択してから比較を実行してください。")
return
# ファイルの存在確認
if not os.path.exists(source):
messagebox.showerror("エラー", f"比較元ファイルが見つかりません:\n{source}")
return
if not os.path.exists(target):
messagebox.showerror("エラー", f"比較先ファイルが見つかりません:\n{target}")
return
if not os.path.exists(output):
messagebox.showerror("エラー", f"出力先フォルダが見つかりません:\n{output}")
return
# 絶対パスに変換
source_abs = os.path.abspath(source)
target_abs = os.path.abspath(target)
output_abs = os.path.abspath(output)
# プログレスバーを表示
self.progress_bar["value"] = 0
self.update_progress(0, "比較処理を開始します...")
# 比較処理を開始
word = None
try:
# Wordアプリケーションを起動
self.update_progress(20, "Wordアプリケーションを起動中...")
pythoncom.CoInitialize() # COM初期化を明示的に行う
word = win32com.client.DispatchEx("Word.Application")
word.Visible = False # バックグラウンドで実行
# 出力ファイル名の作成 (現在の日時を追加)
self.update_progress(30, "出力ファイル名を作成中...")
now = datetime.now().strftime("%Y%m%d_%H%M%S")
source_filename = os.path.basename(source_abs).split('.')[0]
target_filename = os.path.basename(target_abs).split('.')[0]
result_filename = f"{source_filename}_VS_{target_filename}_{now}.docx"
result_path = os.path.join(output_abs, result_filename)
# 比較元ドキュメントを開く
self.update_progress(40, "比較元ドキュメントを開いています...")
source_doc = word.Documents.Open(source_abs)
# 比較先ドキュメントを開く
self.update_progress(50, "比較先ドキュメントを開いています...")
target_doc = word.Documents.Open(target_abs)
# 比較を実行
self.update_progress(60, "ドキュメントを比較中...")
compared_doc = word.CompareDocuments(
OriginalDocument=source_doc,
RevisedDocument=target_doc,
Destination=2, # wdCompareDestinationNew
Granularity=1, # wdGranularityWordLevel
CompareFormatting=True,
CompareCaseChanges=True,
CompareWhitespace=True,
CompareTables=True,
CompareHeaders=True,
CompareFootnotes=True,
CompareTextboxes=True,
CompareFields=True,
CompareComments=True,
CompareMoves=True,
RevisedAuthor="Comparison Tool"
)
# 比較元と比較先のドキュメントを閉じる
self.update_progress(70, "元のドキュメントを閉じています...")
source_doc.Close(SaveChanges=False)
target_doc.Close(SaveChanges=False)
# 現在アクティブなドキュメント(比較結果)を取得
doc = word.ActiveDocument
# ドキュメントが同じかどうかをチェック - 変更履歴(revisions)の数が0なら同じ内容
revisions_count = doc.Revisions.Count
are_documents_identical = (revisions_count == 0)
# 変更履歴の記録を確実にオフにする(既存の変更履歴は保持)
self.update_progress(80, "設定を調整中...")
doc.TrackRevisions = False
# 違いがある場合のみ結果を保存
if not are_documents_identical:
self.update_progress(90, "比較結果を保存中...")
doc.SaveAs(result_path)
# 比較結果を閉じる(保存しない)
doc.Close(SaveChanges=False)
# Wordを終了
self.update_progress(95, "Wordアプリケーションを終了中...")
word.Quit()
word = None
# COMの解放
pythoncom.CoUninitialize()
# 完了
self.update_progress(100, "比較処理が完了しました")
time.sleep(1) # 完了メッセージを少し表示
# ステータスを更新
if are_documents_identical:
self.status_text.set("比較完了: 2つの文書には違いがありません")
else:
self.status_text.set(f"比較完了: 結果ファイルが保存されました")
self.root.update()
# 文書が同一かどうかに応じたメッセージを表示
if are_documents_identical:
messagebox.showinfo("完了", "2つの文書には違いがありません。")
else:
messagebox.showinfo("完了", f"比較が完了しました。\n結果ファイルが保存されました:\n{result_path}")
except Exception as e:
# エラーが発生した場合
self.status_text.set(f"エラー: {str(e)}")
# もしWordが開いたままなら閉じる
if word:
try:
word.Quit()
except:
pass
try:
pythoncom.CoUninitialize()
except:
pass
# エラーメッセージを表示
messagebox.showerror("エラー", f"比較中にエラーが発生しました:\n{str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = WordComparisonApp(root)
root.mainloop()
# V12