WallStudio

技術ブログや創作ブログに届かない雑記です

コピペでできる、C#で完全自動でExcelシートからWordの請求書をつくる

f:id:yukawallstudio:20181007175850p:plain

請求書の数字変えるだけ、たかが数分ですが毎月となると面倒ですよね。さらっと自動化しましょう。

C#からMS Officeを叩く方法はいくつかあるのですが、COMを使ったやり方が一番気軽に行えます。(MS Officeがインストールされている限定)今回は365のVer1809を使っています。

COMとは?

COMはWindowsAPIの利用プロトコルみたいなものでこの規約に沿った形で様々なアプリケーションが機能を提供していて、プログラマーはそれらを利用できます。(CeVIO Creative StudioとかもCOM提供してる、ボイロはしてないから裏で苦労してる人が居る)

アプローチ

  1. 【手動、1回のみ】テンプレートとなるWord文章を作成

  2. 【手動、1回のみ】テンプレート内の毎回変わる部分をメタ文書(「%HOGEHOGE%」など)に変更

  3. Excelの指定セルの数値を取得

  4. テンプレートのメタ文字を置換

ライブラリなど

  • MS OfficeのCOMを使うために次の参照を追加します

  • 次の2クラスを作成

    適当なラッパです。コピペでOKですが、usingの関係でWordとExcelの名前がぶつかるので別のファイルにした方がいいです。using

using System;
using System.Collections.Generic;
using Microsoft.Office.Interop.Excel;
class Excel : IDisposable {
    public Dictionary<string, Worksheet> Sheets = new Dictionary<string, Worksheet>();
    private Application excel;
    private Workbook workbook;
    public Excel(string filePath) {
        excel = new Application { Visible = false };
        workbook = excel.Workbooks.Open(filePath);

        for(int i = 0; i < workbook.Sheets.Count; i++) {
            Worksheet sheet = workbook.Sheets.Item[i+1];
            Sheets.Add(sheet.Name, sheet);
        }
    }
    public void Dispose() {
        workbook.Close();
        workbook = null;
        excel.Quit();
        excel = null;
    }
    public int ReadCellInt(string sheetKey, string address) {
        var value = ReadCell(sheetKey, address);
        var integer = (int)(Math.Round(float.Parse(value)));
        return integer;
    }
    public string ReadCell(string sheetKey, string address) {
        var range = Sheets[sheetKey].get_Range(address);
        var cell = range.Cells[1,1];
        return cell.Value.ToString();
    }
}
using System;
using Microsoft.Office.Interop.Word;
class Word : IDisposable {
    private Application word;
    private Document document;
    public Word(string filePath) {
        word = new Application {  Visible = false };
        document = word.Documents.Open(filePath, ReadOnly:true);
    }
    public void Dispose() {
        document.Close();
        document = null;
        word.Quit();
        word = null;
    }
    public void Replace(string search, string replace) {
        Find find = word.Selection.Find;
        find.ClearFormatting();
        find.Text = search;
        find.Replacement.ClearFormatting();
        find.Replacement.Text = replace;
        find.Execute(Replace: WdReplace.wdReplaceAll);
    }
    public void SaveAs(string savePath) {
        document.SaveAs2(savePath);
    }
    public void SavePdf(string savePath) {
        document.SaveAs2(savePath, FileFormat: WdSaveFormat.wdFormatPDF);
    }
}

置換してみる

Excel.ReadCellIntWord.Replaceを毎回変わる部分の数だけ繰り返せばOKです。

using(var excel = new Excel("読み込むエクセルシートのパス.xlsx")){
    var cellAddress = "P15とかA7みたいなエクセルのセルアドレス";
    var price = excel.ReadCellInt("シートの名前", cellAddress);

    using(var word = new Word("テンプレートのWord文書のパス.docx")){
        word.Replace("%値段%", prive.ToString());
        
        word.SaveAs("保存するパス.docx");
        word.SavePdf("PDFでも保存できるよ.pdf");
    }
}

地雷

  • Sheet.get_Range(string address) はVisualStudioのサジェストに表示されないが使える。(dynamicで実装されてるからっぽい)

  • (int)R は切り捨てだけど、Excelの「通貨セル」は四捨五入。

  • word.Selection.FindMicrosoft.Office.Interop.Word のスタティックメンバみたいな解説がされているサイトがあるが、インスタンスのメンバ。