🔍

Python でテキストファイルを比較して差分を Html に書き出す

2022/05/29に公開約3,700字

環境:

> python --version

Python 3.9.5

完成形


青空文庫の『吾輩は猫である』を使用

変更のない行を非表示にすることもできます。

やっていること

  1. difflibHtmlDiff で Html 形式の表を生成する
  2. テンプレートとして用意しておいた Html に 1. で生成した内容を埋め込む
  3. css で表示を加工する
    • 文字列置換で整形することも考えたものの、比較する文字などの必要な文字は置換されないようにしなくてはならないなど検討事項が多すぎて保留中…

フォルダ構成

> ls -name

diff.py
template.html
wrap.css

使い方

全部の行を表示する
python neko_1.txt neko_2.txt out.html
変更のない行は省略する
python neko_1.txt neko_2.txt out.html --compress

第1引数に元のファイル、第2引数に変更後のファイル、第3引数に出力ファイル名を指定します。 --compress のオプションをつけると変更のない行が省略されるようにしてみました。

コード

コード全体
import argparse
from pathlib import Path
from difflib import HtmlDiff


# 変更されていない行を取り除く
def trim_unchanged(lines:list[str]) -> list[str]:

    # tr の部分とその前後で分ける
    pre = lines[:7]
    trs = lines[7:-2]
    post = lines[-2:]

    # 省略部分を示すフィラー
    filler = '<tr class="filler"><td class="diff_next"></td><td class="diff_header"></td><td nowrap="nowrap"></td><td class="diff_next"></td><td class="diff_header"></td><td nowrap="nowrap"></td></tr>'

    minimal_lines = []
    for i, line in enumerate(trs):

        if ('class="diff_chg"' in line) or ('class="diff_sub"' in line) or ('class="diff_add"' in line):
            # 表示する行とそのインデックスを控えていく
            minimal_lines.append([-1, filler])
            minimal_lines.append([i, line])
            if i == 0:
                # 先頭の行が変更されていた場合は冒頭にフィラー不要
                minimal_lines.pop(-2)
            if len(minimal_lines) > 2 and minimal_lines[-3][0] + 1 == i:
                # 2つ前の行番号と今の行番号が1つしか違わない、すなわち行が連続している場合もフィラー不要
                minimal_lines.pop(-2)

    return pre + [x[1] for x in minimal_lines] + post



def main(from_file:str, to_file:str, out_path:str, template_path:str, css_path:str, skip_unchanged:bool) -> None:
    f_path = Path(from_file)
    t_path = Path(to_file)
    f = f_path.read_text("utf-8").splitlines()
    t = t_path.read_text("utf-8").splitlines()

    # 差分を html table に変換
    df = HtmlDiff()
    markup_lines = df.make_table(f, t, fromdesc=f_path.name, todesc=t_path.name).splitlines()
    if skip_unchanged:
        markup_lines = trim_unchanged(markup_lines)
    markup_table = "\n".join(markup_lines)

    # テンプレートの html 読み込み
    template = Path(template_path).read_text("utf-8")

    # css 読み込み
    style_sheet = Path(css_path).read_text("utf-8")

    # 整形してファイルに書き込み
    html_page = template.replace(
        "<style></style>", "<style>\n{}\n</style>".format(style_sheet)
    ).replace(
        '<div class="main"></div>', '<div class="main">\n{}\n</div>'.format(markup_table)
    )
    Path(out_path).write_text(html_page, "utf-8")

if __name__ == '__main__':

    # コマンドライン引数をパース
    parser = argparse.ArgumentParser()
    parser.add_argument("fromFile", type=str)
    parser.add_argument("toFile", type=str)
    parser.add_argument("outFile", type=str)
    parser.add_argument("--compress", action="store_true")
    args = parser.parse_args()

    # 同階層の css と html を取得
    css_path = Path(__file__).with_name("wrap.css")
    template_path = Path(__file__).with_name("template.html")

    main(args.fromFile, args.toFile, args.outFile, template_path, css_path, args.compress)

挿入先のテンプレートは下記のようにしています。

template.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Diff</title>
    <style></style>
  </head>
  <body>
    <div class="main"></div>
  </body>
</html>

css

https://gist.github.com/AWtnb/2fa67f6c775a24fc4b9b5ab4a6a177a0

日々更新中。


これで比較結果を紙に印刷して配布するようなことになっても安心!

Discussion

ログインするとコメントできます