【Python】英語論文の査読回答書を翻訳からwordファイル作成まで自動化した話



# はじめに

英語論文の査読回答書を作るのが大変すぎて、下の写真のようにエクセルの一覧表から翻訳をして、wordファイルに変換するプログラムを作ったので紹介します。




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/fa9bf49e-cffd-4ba3-bd51-e029b551c99b.png)

↓


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/2a4639f5-6785-4018-ba7c-f10a8c7ede39.png)


プログラムはこちらにおいておきます。

https://github.com/yuki-2000/responce_letter_excel2word

## 詳しいバックグラウンド

最近英語のジャーナルに論文を投稿したのですが、査読結果を読んで原稿を修正することが大変なのは当たり前なのですが、それが終わった後に回答書を作成する際に以下の5つが非常に時間がかかり大変でした。
1. 査読者の英語コメントを日本語訳する
1. 自分の日本語回答を英語訳する
1. 原稿の変更前と変更点を色を付けた変更後の文章を載せる
1. 必要に応じて画像も変更前と変更後のものを貼り付ける
1. 全てを同じレイアウトでwordファイルに書き込む

あれ、これってPythonで結構自動化できるのでは？と思い、microsoft copilotに聞きながらエクセルのファイル内で自動でgoogle翻訳し、ワードのファイルに既定の書式で出力してくれるプログラムを作りました。

完成度9割ぐらいの回答書を出力してくれるプログラムが完成し非常に重宝したので、皆様にもぜひ使っていただきたくこの記事を書きました。







# 1. 入力ファイルの用意

## エクセルファイル
まず入力用として必要なエクセルファイル`input.xlsx`を用意します。
A列から順に、
英語質問、日本語質問、日本語回答、英語回答、元文章、修正後文章、ページ等、修正前画像パス、修正後画像パス、
というような列にしています。
画像で説明すると、以下のようになります。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/e7eaf9eb-c02b-483f-811c-f4faa9004eb0.png)



## 画像ファイル
wordファイルに画像も挿入したい場合、その画像ファイルを用意し、エクセルファイルにファイルのパスを記入する必要があります。
今回は、プログラムと同じディレクトリに置き、ファイル名のみをエクセルファイルに書き込んでいます。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/a2599b85-4c6d-41fc-9ca1-2be2843cb663.png)





# 2. 翻訳

日本人が翻訳を使う場面としては、
1. 英語質問から日本語質問へ翻訳して理解する
1. 英語回答から日本語回答へ翻訳して提出する

の2つかと思います。

以下のような各行に質問と回答を入力したファイルを用意してプログラムを実行すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/c2c2f124-77f8-43a8-af51-c679dcea6a61.png)


以下のような翻訳後の文章が記載されたファイルが出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/e66112dc-2834-41e5-9dbb-c47287f2aee4.png)


翻訳元のセルが空白だった場合無視されるので、片方だけの用途の場合でも使用できます。
また、翻訳先のセルが空白だった場合のみ書き込まれるようにしているので、手動で修正した後にもう一度実行しても上書きされません。
逆に言えば、新たに翻訳してほしければ、そこの行だけ翻訳語のセルを空白にすればいいです。


プログラムを以下に載せます。
`openpyxl`というライブラリでエクセルを読み書きし、`deep_translator`というライブラリで`google翻訳`に送って翻訳結果を取得しています。また、deeplにも対応しているようなので、有料apiをお持ちの方はそちらを使うとより良い翻訳になると思います。
また、`deep_translator`は`pip`でインストールする必要があります。


```python
import openpyxl
from deep_translator import GoogleTranslator

def tranlater(excel_file='input.xlsx', output_file_name='input_transrated.xlsx'):

    # エクセルファイルを読み込む
    wb = openpyxl.load_workbook(excel_file)
    ws = wb.active
        
    #英語質問から日本語質問へ翻訳
    # 翻訳器を初期化
    translator = GoogleTranslator(source='en', target='ja')
        
    # 2行目以降のセルを処理
    for row in ws.iter_rows(min_row=2):
        cell_a = row[0].value or ""   #英語質問 
        cell_b = row[1]               #日本語質問
        if cell_b.value == None: #空白の時だけ翻訳後の文章を書き込む
            translated_text = translator.translate(cell_a)
            cell_b.value = translated_text
    

    #英語回答から日本語回答へ翻訳
    # 翻訳器を初期化
    translator = GoogleTranslator(source='ja', target='en')
        
    # 2行目以降のセルを処理
    for row in ws.iter_rows(min_row=2):
        cell_c = row[2].value or ""   #日本語回答
        cell_d = row[3]               #英語回答
        if cell_d.value == None: #空白の時だけ翻訳後の文章を書き込む
            translated_text = translator.translate(cell_c)
            cell_d.value = translated_text
    
    # エクセルファイルを保存    
    wb.save(output_file_name)
    print("翻訳内容を保存しました。")


tranlater(excel_file='input.xlsx', output_file_name='input_transrated.xlsx')
```





# 3. wordファイルへの書き込み

次にエクセルファイルからワードファイルへの変換ですが、以下の画像のように、翻訳語のエクセルファイルからワードファイルに変換することができます。
使用したライブラリは、`openpyxl`でワードファイルを読み込み、`python-docx`でワードファイルに書き込み、`difflib`で文章の差分を計算しました。
なお、`python-docx`は`pip`でインストールする必要があります。

プログラムも載せますが、自分流にアレンジしたい人のために以下で詳しく説明します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/a37e1422-ddec-484f-a8c6-99e8a47fe4bb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/62da1a15-f5d4-4ec7-afb1-c2870d420f68.png)


```python
import openpyxl
from docx import Document
from docx.shared import RGBColor, Inches
from difflib import ndiff
import os
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

def make_responce_docx(excel_file = 'input_transrated.xlsx', output_file_name = 'output.docx'):
    
    # エクセルファイルを読み込む
    wb = openpyxl.load_workbook(excel_file)
    ws = wb.active

    # 新しいWordドキュメントを作成
    doc = Document()



    #タイトルの作成
    p = doc.add_paragraph("Responses to reviewers")

    # 2行目以降のセルを処理
    for row in ws.iter_rows(min_row=2):
    
        #セルが空白の場合に空文字列を使用することで、エラーを回避
        cell_a = row[0].value or "" #英語質問 
        cell_b = row[1].value or "" #日本語質問
        cell_c = row[2].value or "" #日本語回答
        cell_d = row[3].value or "" #英語回答
        cell_e = row[4].value or "" #元文章
        cell_f = row[5].value or "" #修正後文章   
        cell_g = row[6].value or "" #ページ等
        cell_h = row[7].value or "" #修正前画像パス
        cell_i = row[8].value or "" #修正後画像パス

        
        
        # 各行ごとに改ページする
        if not cell_a == "":
            doc.add_page_break()  


        #質問
        ## A列の内容を太文字で書き込む
        #if not cell_a == "":
        #    p = doc.add_paragraph()
        #    #各部分を個別にadd_runで追加し、run.add_break()で改行を追加することで、元のテキストの改行を再現
        #    for part in cell_a.split('\n'):
        #        run = p.add_run(part)
        #        run.bold = True #太文字
        #        run.add_break()

        # A列の内容を見出し2で書き込む（改行あり）
        if not cell_a == "":
            for part in cell_a.split('\n'):
                doc.add_heading(part, level=2)
            p = doc.add_paragraph()

        ##!!!下書き限定!!!!!
        ## 改行して、B列の内容を太文字で書き込む
        #if not cell_b == "":
        #    p = doc.add_paragraph()
        #    for part in cell_b.split('\n'):
        #        run = p.add_run(part)
        #        run.bold = True
        #        run.add_break()




        #回答
        ##!!!下書き限定!!!!!
        ## 1行空けて、C列の内容を普通の文字で書き込む
        #if not cell_c == "":
        #    p = doc.add_paragraph()
        #    for part in cell_c.split('\n'):
        #        run = p.add_run(part)
        #        run.add_break()

        ## 1行空けて、D列の内容を普通の文字で書き込む（改行ver）
        #if not cell_d == "":
        #    p = doc.add_paragraph()
        #    for part in cell_d.split('\n'):
        #        run = p.add_run(part)
        #        run.add_break()

        # 1行空けて、D列の内容を普通の文字で書き込む(段落改行ver)
        if not cell_d == "":
            for part in cell_d.split('\n'):
                p = doc.add_paragraph(part)
                p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
                run = p.add_run()
            p = doc.add_paragraph()




        #元原稿
        # 一行空けて、[Original manuscript]という文字を書く
        p = doc.add_paragraph("[Original manuscript]")

        ## 改行して、E列の内容を書き込む（改行ver）
        #p = doc.add_paragraph()
        #for part in cell_e.split('\n'):
        #    run = p.add_run(part)
        #    run.add_break()

        # 改行して、E列の内容を書き込む(段落改行ver)
        for part in cell_e.split('\n'):
            p = doc.add_paragraph(part)
            p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
            run = p.add_run()
        p = doc.add_paragraph()





        #修正後原稿
        # 一行空けて、[Revised manuscript]という文字を書き、続けてG列の内容（ページ数）を書く
        p = doc.add_paragraph("[Revised manuscript]")
        run = p.add_run(cell_g)

        ## 改行して差分を計算して書き込む（改行なしver）
        #diff = list(ndiff(cell_d.split(), cell_e.split()))
        
        # 改行して差分を計算して書き込む（改行ありver）    
        cell_e_temp = cell_e.replace('\n', ' <br> ') # 改行文字を一時的に別の文字列に置き換える
        cell_f_temp = cell_f.replace('\n', ' <br> ') # 改行文字を一時的に別の文字列に置き換える 
        diff = list(ndiff(cell_e_temp.split(), cell_f_temp.split()))

        p = doc.add_paragraph()   
        p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
        for word in diff:        
            #置き換えた改行文字が来たら改行する    
            if word.endswith('+ <br>') or word.startswith('  <br>'): #改行が追加      
                p = doc.add_paragraph() #改段落
                p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
            elif word.startswith('- <br>'): #改行が削除
                continue
            elif word.startswith('- '): #消えた文章
                run = p.add_run(word[2:] + ' ')
                run.font.color.rgb = RGBColor(255, 0, 0) #赤色
                run.font.strike = True #取り消し線
            elif word.startswith('+ '): #追加された文章
                run = p.add_run(word[2:] + ' ')
                run.font.color.rgb = RGBColor(0, 0, 255) #青色
            elif word.startswith('  '): #変化のない文章
                run = p.add_run(word[2:] + ' ')
                run.font.color.rgb = RGBColor(0, 0, 0) #黒色            
        p = doc.add_paragraph() #改段落
        p = doc.add_paragraph() #改段落




        # 画像の挿入
        if (not cell_h == "") and os.path.isfile(cell_h):
            p = doc.add_paragraph("[Original]")
            p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER #中央ぞろえ
            p.add_run().add_break()  # 改行を追加
            p.add_run().add_picture(cell_h, width=Inches(5)) #画像サイズを指定
            p.add_run().add_break()  # 改行を追加


        if (not cell_i == "") and os.path.isfile(cell_i):        
            p = doc.add_paragraph("[Revised]")
            p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
            p.add_run().add_break()  # 改行を追加
            p.add_run().add_picture(cell_i, width=Inches(5)) #画像サイズを指定



    # docxファイルへの出力
    doc.save(output_file_name)
    print("回答書が", output_file_name, "に保存されました。")





make_responce_docx(excel_file = 'input_transrated.xlsx', output_file_name = 'output.docx')
```




## 査読者コメントの書き込み

人によってレイアウトは違うのですが、今回はワードの機能で自動的に目次を生成するために、`見出し2`スタイルを使用してコメントを書き込みました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/3acbc28f-09a0-41bd-84fc-d35a978dcc59.png)

こうすることで、下の画像のように自動でナビゲーションウィンドウにリンクが作成され(pdfに変換されるので使われませんが...)、また目次機能を使用すれば自動的にコメントのページ数とpdfになってもクリックで該当ページにとべるリンクが生成されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/7f8d0bf8-73ad-4cb5-a4a3-f534da2f0c73.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/d8f3381e-5d2d-424b-903b-4d2c03706fd0.png)


また、見出し機能は使用せずに自分でスタイルを変えたい人用にコメントアウトしているほかの書き方もあり、見出しver、太文字ver、ついでに日本語verを比較してみます。


```python
        #質問
        # A列の内容を見出し2で書き込む（改行あり）
        if not cell_a == "":
            for part in cell_a.split('\n'):
                doc.add_heading(part, level=2)
            p = doc.add_paragraph()

        # A列の内容を太文字で書き込む
        if not cell_a == "":
            p = doc.add_paragraph()
            #各部分を個別にadd_runで追加し、run.add_break()で改行を追加することで、元のテキストの改行を再現
            for part in cell_a.split('\n'):
                run = p.add_run(part)
                run.bold = True #太文字
                run.add_break()

        #!!!下書き限定!!!!!
        # 改行して、B列の内容を太文字で書き込む
        if not cell_b == "":
            p = doc.add_paragraph()
            for part in cell_b.split('\n'):
                run = p.add_run(part)
                run.bold = True
                run.add_break()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/d5fe8219-eb93-4b0b-9448-9e381a6b6be2.png)






## 改行ありの文章の書き込み

本プログラムではエクセルファイル内で改行をしていても、そのままwordファイルに`doc.add_paragraph()`で書き込んでも改行は無視されてしまうことがあったため、明示的に改行文字の`\n`が来るたびに改行して書き込むようにしています。（今回の実行では問題ありませんでした）
また、改行にもただの`改行`と`段落改行`の2種類があり、`改行`だと右端ががたがたになってしまい、両端揃えにすると最後の行が変になってしまうにため、`段落改行`を使用しました。


```改行なしver.py
if not cell_d == "":
    p = doc.add_paragraph(cell_d)
    p = doc.add_paragraph()
```


```段落改行ver.py
if not cell_d == "":
    for part in cell_d.split('\n'):
        p = doc.add_paragraph(part)
        p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
        run = p.add_run()
    p = doc.add_paragraph()
```


```改行ver.py 
# 1行空けて、D列の内容を普通の文字で書き込む（改行ver）
if not cell_d == "":
    p = doc.add_paragraph()
    for part in cell_d.split('\n'):
        run = p.add_run(part)
        run.add_break()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/8bf6b630-c9bd-497c-aa05-40c3fb007184.png)



## 修正前後の差分表示

今回の一番重要な点であり、このプログラムを作ろうとしたきっかけにもなる、差分表示です。
ネットで調べると、wordの比較機能を使うとよいと説明されているのですが、文章単位で比較するのではなく、ファイル単位になってしまい一つ一つの回答ごとにファイルを2つ作って手作業で比較していくというのは時間がかかります。
そこで、`difflib`というライブラリを使用して文章を比較し、色などを付けていくようにしました。


また、ここでも改行が無視されてしまうという問題が起き、また`difflib`でうまく`\n`をそのまま処理できなかったため、一度前処理として`\n`を` <br> `に置き換えてから処理するようにしています。

```python
# 改行して差分を計算して書き込む（改行ありver）    
cell_e_temp = cell_e.replace('\n', ' <br> ') # 改行文字を一時的に別の文字列に置き換える
cell_f_temp = cell_f.replace('\n', ' <br> ') # 改行文字を一時的に別の文字列に置き換える 
diff = list(ndiff(cell_e_temp.split(), cell_f_temp.split()))
```


書き込む際は、`difflib`により先頭に文字が追加され、消えた単語には`- `、追加された単語には` +`、変化のない単語には`  `が追加されるため、すべての単語について3文字目から記入するようにし、それぞれ色や取り消し線を変化させています。
さらに` <br> `に置き換えた部分の処理を追加しています。



```python
p = doc.add_paragraph()   
p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
for word in diff:        
    #置き換えた改行文字が来たら改行する    
    if word.endswith('+ <br>') or word.startswith('  <br>'): #改行が追加      
        p = doc.add_paragraph() #改段落
        p.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY #両端揃えに設定
    elif word.startswith('- <br>'): #改行が削除
        continue
    elif word.startswith('- '): #消えた文章
        run = p.add_run(word[2:] + ' ')
        run.font.color.rgb = RGBColor(255, 0, 0) #赤色
        run.font.strike = True #取り消し線
    elif word.startswith('+ '): #追加された文章
        run = p.add_run(word[2:] + ' ')
        run.font.color.rgb = RGBColor(0, 0, 255) #青色
    elif word.startswith('  '): #変化のない文章
        run = p.add_run(word[2:] + ' ')
        run.font.color.rgb = RGBColor(0, 0, 0) #黒色  
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/1b8f1c48-1291-416b-9d37-79c9a88a8308.png)




## 画像の挿入

最初は手作業でやろうと思っていましたが、同じ画像についていろいろなコメントや異なる査読者で何回も表示する必要があったため、こちらも自動化しました。

プログラムとしては非常に簡単で、エクセルから画像のパスをとってきて、サイズを指定して`add_picture()`で追加しているだけです。
そのため、エクセルのセルには`original_image.png`というように拡張子を含めたパスを記入すればいいです。

```python
if (not cell_h == "") and os.path.isfile(cell_h):
    p = doc.add_paragraph("[Original]")
    p.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER #中央ぞろえ
    p.add_run().add_break()  # 改行を追加
    p.add_run().add_picture(cell_h, width=Inches(5)) #画像サイズを指定
    p.add_run().add_break()  # 改行を追加
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/4c1a9d16-3e7e-4356-aae9-e2a2349a730e.png)






## その他使い方

例えば一つの質問に対し複数の説明と修正前後の文章、画像を載せたいときは、英語質問のセルを書かずに下の行に書いていけば、自然な見た目になります。
しかし、改ページされてしまうので、そこは人力で修正する必要はあります。





# 4. 手作業による修正

ここまでくれば9割完成したワードファイルが出力されています。
後は気になるところを修正し、`python-docx`ではできないことをやっていきます。

私が行ったことは、

* 1ページ目に査読者へのコメントを追加
* 2行以上にわたって書かれている見出しの修正
* 完全新規の画像について`[original]`を削除し`[revised]`を`[additional]`に修正
* フッターにページ数の追加、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/047c2539-48e7-478e-8d19-114101ddf8bb.png)
* 目次の追加、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993303/f6df668c-fc14-4ecb-9116-51d459dc1c5d.png)

でした。












# 最後に

今回は、査読を楽にするための翻訳の自動化とwordファイルへの変換をしてくれるプログラムを紹介しました。
少ない指摘の数なら何とかなりますが、大量にあるととても大変ですし、レイアウトの統一という点でも役に立ってくれればと思います。
以下の記事でも英語論文の出版プロセスを解説しているのでぜひ参考にしてください。
そして皆さんも査読対応頑張ってください。

https://qiita.com/yuki_2020/items/c309b80e75923f4a8839






# おまけ：プロンプト

一番最初にmicrosoft copilotで使用したプロンプトなので、今回のプログラムとは少し違ったものが出力されます。

```
エクセルファイルを読み込み、指定した二つのセルの文章を比較し、文章の変更点について取り消し線を引いたり文字の色を赤くして差分を表示したものを順番に新たなwordファイルに出力するpythonのコードを作ってください
同じ行において、2行目以下において以下をそれぞれの行で順番に実行してください
A列の内容を太文字でwordファイルに書き込む
改行して、B列の内容を太文字でwordファイルに書き込む
1行空けて、C列の内容を普通の文字でwordファイルに書き込む
一行空けて、[original]という文字を書く
改行して、D列の内容を太文字でwordファイルに書き込む
一行空けて、[revised]という文字を書き、続けてF列の内容を書く
一行空けて、Dの列のセルと、同じ行のEの列のセルの文章を比較し、結果をwordファイルに書き込む。ここで、以下のルールに従う
差分において、削除された部分は赤文字に取り消し線を引く
差分において、追加された部分は赤文字にする
差分において、変更のないところは黒文字のままにする

例えば以下の二つの入力1と入力2の文を比較すると、surfacesは赤文字に取り消し線で、出力においてwithが赤文字で、それ以外は黒色のままにしてほしいです。
入力1　Analytical rigid surfaces were used for modeling the support and indenter
入力2　Analytical rigid with were used for modeling the support and indenter
出力　Analytical rigid surfaces with were used for modeling the support and indenter


エクセル内の次の行に行ったら、wordファイルで改ページするようにしてください
```