HTMLテンプレートを利用してPDF帳票を作成する

今回は「DioDocs for PDF(ディオドック)」でPDF帳票を作成する方法を紹介します。

PDF帳票を作成する方法

DioDocs for PDFでは、新規にPDFファイルを作成してテキストや図形、直線などのオブジェクトを追加することができますが、この機能を利用して完全に白紙の状態から帳票のレイアウトを設定し一つ一つデータを追加して帳票を作成するのは非常に難易度が高いかと思います。

そこで帳票を作成する際に活用できるのがHTMLからPDFに変換する機能です。あらかじめHTMLで帳票のレイアウト「HTMLテンプレート」を作成しておいて、そこにデータを追加してPDFに変換するという以下の手順でPDF帳票を作成します。

  1. HTMLテンプレートを作成する
  2. HTMLテンプレートを読み込んで必要なデータを追加する
  3. データを追加したHTMLをPDF形式に変換する
PDF帳票を作成する方法

HTMLで作成したテンプレートであれば、HTMLに精通している開発者やデザイナー、情シスなどによるメンテナンスも可能ですし、帳票の更新や追加が発生するたびにいちいちレイアウトを作成しデータを追加する処理をアプリケーションに実装し直す必要もありません。アプリケーションでは作成済みのHTMLテンプレートを読み込んでデータを追加する処理を実装するだけで済みます。

HTMLテンプレート

Mustache記法

HTMLテンプレートにデータを追加する処理にはMustache記法を使用します。HTMLテンプレートに{{ name }}というデータを追加するためのプレースホルダーを記載して、nameという変数に設定したデータを渡すことでHTMLテンプレートにデータを追加することができます。

// テンプレート上のMustache記法(変数name)
Hello {{ name }}

// ハッシュ(変数nameに渡す値)
{
  "name": "Chris",
}

// 出力結果
Hello Chris

.NETでMustache記法を処理するには?

.NETでMustache記法を処理するライブラリとして公式サイトではStubbleが紹介されており、グレープシティのデモでもStubbleが使われています。しかし、Stubbleはテンプレートにデータを追加するためのシンプルな機能に特化しています。そのため、帳票によくある価格や金額など数値の書式を設定したい場合にコーディングで実装する必要が出てきてしまいます。できればHTMLテンプレート側で書式を設定してしまいたいところです。

そこで本記事ではStubbleの代わりにScribanを使用します。Scribanでは以下のようにプレースホルダーにmath.formatを設定して数値の書式を設定することができます。

<table class="summary">
  <tbody>
    <tr>
      <th>合計金額</th>
      <td>{{ invoice.grand_total | math.format "c" "ja-JP" }}</td>
    </tr>
  </tbody>
</table>

参考資料

帳票のレイアウトを設定するにあたっては、非常に参考になる内容がまとめられている記事がQiitaにありますので、こちらをベースにレイアウトを作成するのがいいかと思います。

用意するHTMLテンプレート

以下のようにMustache記法でプレースホルダーを設定したHTMLテンプレートを用意します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="sheet.css" media="all">
    <title>請求書</title>
</head>
<body>
    <section class="sheet">
        <div class="row_1">
            <h1 class="text-center">請求書</h1>
        </div>
        <div class="row_2">
            <ul class="text-right">
                <li>No: {{ invoice.invoice_number }}</li>
                <li>請求日: {{ invoice.issue_date }}</li>
            </ul>
        </div>
        <div class="row_3">
            <div class="col_1">
                <ul>
                    <li><h2 class="customer_name">{{ invoice.customer.name }} 御中</h2></li>
                    <li>件名: {{ invoice.customer.project_name }}</li>
                    <li>支払期限: {{ invoice.due_date }}</li>
                </ul>
            </div>
            <div class="col_2">
                <ul>
                    <li>
                        <h2>葡萄商事株式会社</h2>
                    </li>
                    <li>〒981-9999</li>
                    <li>M県S市紅葉区杜王町2-6-11</li>
                    <li>定禅寺プライムビル3階</li>
                    <li>TEL: 022-7777-8888</li>
                    <li>e-mail: sales@budou-shoji.co.jp</li>
                </ul>
                <!-- <img class="stamp" src="stamp.png"> -->
            </div>
            <div class="clear-element"></div>
        </div>
        <div class="row_4">
            <p>下記のとおりご請求申し上げます。</p>
            <table class="summary">
                <tbody>
                    <tr>
                        <th>合計金額</th>
                        <td>{{ invoice.grand_total | math.format "c" "ja-JP" }}</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <div class="row_5">
            <table class="detail">
                <thead>
                    <tr>
                        <th class="item">品名</th>
                        <th class="unit_price">単価</th>
                        <th class="amount">数量</th>
                        <th class="subtotal">金額</th>
                    </tr>
                </thead>
                <tbody>
                    {{ for item in invoice.items }}
                    <tr class="dataline">
                        <td class="text-left">{{ item.name }}</td>
                        <td>{{ item.price  | math.format "c" "ja-JP" }}</td>
                        <td>{{ item.quantity }}</td>
                        <td>{{ item.total_price | math.format "c" "ja-JP" }}</td>
                    </tr>
                    {{end}}
                    <tr>
                        <td class="space" rowspan="3" colspan="2"> </td>
                        <th>小計</th>
                        <td>{{ invoice.sub_total | math.format "c" "ja-JP" }}</td>
                    </tr>
                    <tr>
                        <th>消費税</th>
                        <td>{{ invoice.tax | math.format "c" "ja-JP" }}</td>
                    </tr>
                    <tr>
                        <th>合計</th>
                        <td>{{ invoice.grand_total | math.format "c" "ja-JP" }}</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <ul>
            <li>振込先</li>
            <li>名義:カ)ブドウショウジ</li>
            <li>杜王町銀行 紫山支店 普通 98765432</li>
        </ul>
        <p>※恐れ入りますがお振込み手数料は御社にてご負担くださいますようお願いいたします。</p>
    </section>
</body>
</html>

なお、Scribanを使う場合には、以下の点(プロパティをスネークケースに変換する)に注意してプレースホルダーを設定します。

NOTICE

By default, Properties and methods of .NET objects are automatically exposed with lowercase and _ names. It means that a property like MyMethodIsNice will be exposed as my_method_is_nice

https://github.com/scriban/scriban

実際にHTMLテンプレートをブラウザで表示すると以下のようになります。Mustache記法によるプレースホルダーの部分が折り返されて表示されていますが、データを追加する際には一行で収まります。

HTMLテンプレート

アプリケーションの作成

Visual Studioでコンソールアプリケーションを作成します。DioDocs for PDFとScribanのパッケージ(GrapeCity.DioDocs.Pdf.jaGrapeCity.DioDocs.Html.jaScriban)を追加します。

DioDocs for PDFとScribanのパッケージを追加

アプリケーションでは以下のような処理を実装します。

  1. 請求書に追加するデータ(JSON)を読み込む
    JsonSerializer.DeserializeメソッドでJSONデータをinvoiceに設定します。
  2. HTMLテンプレートを読み込む
    HTMLテンプレートを読み込んでTemplate.ParseメソッドでMustache記法で記載したプレースホルダーを解析します。
  3. HTMLテンプレートにデータを追加する
    Template.Renderメソッドでデータinvoiceをプレースホルダーに追加します。
  4. データを追加したHTMLを描画してPDFに出力する
    GcHtmlBrowser.NewPageメソッドでHTMLを描画して、HtmlPage.SaveAsPdfメソッドでHTMLをPDFに出力します。
// 請求書に追加するデータ
var invoice = JsonSerializer.Deserialize<Invoice>(File.ReadAllText("InvoiceData.json"));

// HTMLテンプレートを読み込み
var invoiceTemplate = File.ReadAllText("./Template/InvoiceTemplate.html");
var template = Template.Parse(invoiceTemplate);
            
// HTMLテンプレートにデータを追加
var pageContent = template.Render(new { invoice });
File.WriteAllText("./Template/pageContent.html", pageContent, System.Text.Encoding.UTF8);

// HTMLのレンダリングに使用するGcHtmlBrowserのインスタンスを生成
var browserPath = BrowserFetcher.GetSystemChromePath();
using var browser = new GcHtmlBrowser(browserPath);

// HTMLをレンダリング
var uri = new Uri("./Template/pageContent.html", UriKind.Relative);
using var htmlPage = browser.NewPage(uri);

// PDFとして保存
htmlPage.SaveAsPdf("invoice.pdf");

請求書に追加するデータ(JSON)を格納するモデルInvoiceクラスは以下のような構成です。

public class Invoice
    {
        public string InvoiceNumber { get; set; }
        public string IssueDate { get; set; }
        public string DueDate { get; set; }
        public Customer Customer { get; set; }
        public List<Item> Items { get; set; }

        public decimal SubTotal
        {
            get
            {
                return Items.Sum(x => x.TotalPrice);
            }
        }

        public decimal Tax
        {
            get
            {
                return SubTotal * (decimal)0.1;
            }
        }

        public decimal GrandTotal
        {
            get
            {
                return SubTotal + Tax;
            }
        }
    }

    public class Item
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
        public decimal TotalPrice
        {
            get
            {
                return Quantity * Price;
            }
        }
    }

    public class Customer
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string ProjectName { get; set; }
    }

以上です。実行すると以下のようにデータが追加されたPDF帳票を出力できます。単価や金額の数値には、テンプレートで設定した書式が設定されていることが確認できます。

データが追加されたPDF帳票

DioDocs for Excelテンプレート構文のような柔軟性はそれほどありませんが、DioDocs for PDFでもHTMLテンプレートを使用してPDF帳票を作成することができます。

上記コードを実装しているサンプルはこちらです。

補足

なお、本記事ではInvoiceクラスで金額TotalPrice、小計SubTotal、消費税Tax、合計GrandTotalを計算していますが、以下のようにHTMLテンプレートに計算式をすべて記載しておくことも可能です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="sheet.css" media="all">
    <title>請求書</title>
</head>
<body>
    <section class="sheet">
        <div class="row_1">
            <h1 class="text-center">請求書</h1>
        </div>
        <div class="row_2">
            <ul class="text-right">
                <li>No: {{ invoice.invoice_number }}</li>
                <li>請求日: {{ invoice.issue_date }}</li>
            </ul>
        </div>
        <div class="row_3">
            <div class="col_1">
                <ul>
                    <li><h2 class="customer_name">{{ invoice.customer.name }} 御中</h2></li>
                    <li>件名: {{ invoice.customer.project_name }}</li>
                    <li>支払期限: {{ invoice.due_date }}</li>
                </ul>
            </div>
            <div class="col_2">
                <ul>
                    <li>
                        <h2>葡萄商事株式会社</h2>
                    </li>
                    <li>〒981-9999</li>
                    <li>M県S市紅葉区杜王町2-6-11</li>
                    <li>定禅寺プライムビル3階</li>
                    <li>TEL: 022-7777-8888</li>
                    <li>e-mail: sales@budou-shoji.co.jp</li>
                </ul>
                <!-- <img class="stamp" src="stamp.png"> -->
            </div>
            <div class="clear-element"></div>
        </div>
        <div class="row_4">
            <p>下記のとおりご請求申し上げます。</p>
            <table class="summary">
                <tbody>
                    <tr>
                        <th>合計金額</th>
                        {{ subtotal = 0 }}
                        {{ for item in invoice.items }}
                        {{ subtotal += item.total_price }}
                        {{ tax = subtotal * 0.1}}
                        {{ grandtotal = subtotal + tax }}
                        {{ end }}
                        <td>{{ grandtotal | math.format "c" "ja-JP" }}</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <div class="row_5">
            <table class="detail">
                <thead>
                    <tr>
                        <th class="item">品名</th>
                        <th class="unit_price">単価</th>
                        <th class="amount">数量</th>
                        <th class="subtotal">金額</th>
                    </tr>
                </thead>
                <tbody>
                    {{ for item in invoice.items }}
                    <tr class="dataline">
                        <td class="text-left">{{ item.name }}</td>
                        <td>{{ item.price  | math.format "c" "ja-JP" }}</td>
                        <td>{{ item.quantity }}</td>
                        {{ totalprice = item.price * item.quantity }}
                        <td>{{ totalprice | math.format "c" "ja-JP" }}</td>
                    </tr>
                    {{end}}
                    <tr>
                        <td class="space" rowspan="3" colspan="2"> </td>
                        <th>小計</th>
                        <td>{{ subtotal | math.format "c" "ja-JP" }}</td>
                    </tr>
                    <tr>
                        <th>消費税</th>
                        <td>{{ tax | math.format "c" "ja-JP" }}</td>
                    </tr>
                    <tr>
                        <th>合計</th>
                        <td>{{ grandtotal | math.format "c" "ja-JP" }}</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <ul>
            <li>振込先</li>
            <li>名義:カ)ブドウショウジ</li>
            <li>杜王町銀行 紫山支店 普通 98765432</li>
        </ul>
        <p>※恐れ入りますがお振込み手数料は御社にてご負担くださいますようお願いいたします。</p>
    </section>
</body>
</html>

しかし、ブラウザでテンプレートを表示した際に設定した計算式も表示されてしまうので帳票のレイアウトが確認しにくい状態になります。用途にあわせてモデルかテンプレートのどちらに計算処理を実装するか判断する必要があるかと思います。

さいごに

弊社Webサイトでは、製品の機能を気軽に試せるデモアプリケーションやトライアル版も公開していますので、こちらもご確認いただければと思います。

また、ご導入前の製品に関するご相談やご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /