Docurain Labo

Docurainサービス開発日記

DocurainをDTP的に使いこなす(不動産風チラシを作る)

DocurainはExcelファイルをテンプレートとして、PDFや画像を生成します。主な用途としては納品書や請求書といった帳票が多いですが、実際にはそれだけに限らず利用されています。

今回はその一例として、不動産風チラシを作ってみます。

できあがったチラシ

今回作成したチラシはこのようなものになります。

f:id:moongift:20210813210844j:plain

要素ごとに分解すると、次のようになります。赤い部分が画像やQRコードのマクロ、青い部分がテキストになります。

f:id:moongift:20210813211213j:plain

このチラシは簡単なWebページ上で情報を入力し、送信すると生成されるようになっています(コードは後述します)。今回はPHPで作成していますが、他のプログラミング言語でも同じように実装できるでしょう。

f:id:moongift:20210813211244j:plain

作成したテンプレート

今回作成したテンプレートはこちらです(クリックでダウンロードできます)。ベースは入居募集にすぐ使える エクセルで作った「物件資料テンプレート」を無料で差し上げます! | 愛媛県松山市・収益不動産物件情報センター(洋館家・愛媛 松山道後店)よりいただきました。

利用した画像

チラシに埋め込んだ写真は、下記よりダウンロードしたものになります。

作成手順について

では、ここから作成していく手順を紹介します。

テンプレートファイルの作成

今回のチラシは明細行はありませんので、とてもシンプルなものです。まずセルA1にて #set を使います。

#set($e=$ROOT)

これで ${ROOT} だけではなく ${e} を使ってアクセスできるようになるので、テンプレートのマクロ記述が簡単になります。まずテキスト部分について、すべて記述していきます。ここではDocurainの省略記法を使って %{name} と書くことで、そこに物件名が入るといった具合です。

f:id:moongift:20210813211534p:plain

配列の出力

今回はおすすめポイント1〜3が配列となっています。これは配列の要素を指定することでアクセスできます。

%{goods[0]}

通常のプログラミング言語と同様、配列は1つ目の要素が [0] と指定するので注意してください。

画像の指定

画像はセルに直接書くのではなく、図形を配置します。

そして図形のテキストとして #IMAGE(要素の値) と記述します。例えば #IMAGE($e.appearance) といった具合です。文字列として出力する場合には %{appearance} としますが、マクロに渡す場合には $e.appearance とするので注意してください。

f:id:moongift:20210813211428p:plain

QRコードの指定

QRコードも画像と同じように図形で配置します。QRコードは正方形なので、図形も正方形にしてください。

f:id:moongift:20210813211630p:plain

そして図形のテキストとして #QRCODE(要素の値) と記述します。こちらも画像と同じく #QRCODE(e.url) などとします。このURLは物件情報が掲載されたURLや、電話番号などになるでしょう。

フッターのテキスト

フッターではテンプレート上に書いた文字と、パラメータで渡す文字が混在しています。この場合でも ${} を使って指定できます。

<TEL : %{tel}  HP %{website}>

表示範囲の設定

テンプレートファイルにパラメータを埋め込み終わったら、印刷範囲の設定をしておきましょう。Excelのページレイアウトメニューで、印刷範囲の設定をします。A列を除いて(場合によっては1行目も除いて)帳票として作成したい部分を選択して、印刷範囲の設定を選択します。そして表示メニューで、改ページプレビューにしておくと、実際に帳票が作成される範囲が分かりやすくなります。

f:id:moongift:20210813211707p:plain

プログラミングについて

ではテンプレートファイルができあがったら、PHPによるプログラミングに入っていきます。今回はestimate.phpというファイルで作成しています。

環境の設定とライブラリインストール

PHPがインストールされていない場合にはインストールしてください。

PHP: インストールと設定 - Manual

次に適当なディレクトリを作成し、ライブラリをインストールします。composerを使うので、インストールしていない場合には下記のコマンドを実行します。

curl -sS https://getcomposer.org/installer | php

そしてGuzzleというHTTPクライアントをインストールします。

composer require guzzlehttp/guzzle:^7.0

入力画面の作成

今回は1つのファイルで入力画面と、PDF作成処理を行います。そこで、PHPファイルにGETアクセスした時(入力画面表示)とPOSTアクセスした時(PDF作成)で処理分けします。

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
if (!$_POST) {
?><!doctype html>
  <!-- 入力画面に関する記述 -->
<?php
} else {
  // PDF作成処理に関する記述
}

入力画面はBootstrapを使って作成しています。個々の入力項目の名前と、Excelテンプレートでの指定と合わせておくことで、PDF作成時の処理を簡単なものにしています。今回は入力画面で画像も指定しているので、formタグに enctype="multipart/form-data" を記述しています。

<!-- 入力画面に関する記述 -->
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>不動産チラシ作成</title>
  <meta name="description" content="The HTML5 Herald">
  <meta name="author" content="SitePoint">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    <div class="row">
      <div class="md-12">
        <h2>不動産チラシ作成</h2>
      </div>
      <div class="col-md-12">
        <form class="form" action="/estimate.php" method="POST" enctype="multipart/form-data">
          <div class="row">
            <div class="mb-12">
              <label class="form-label"></label>
              <input type="text" class="form-control" name="name" placeholder="リバーサイド×× 701号室" value="">
            </div>
          </div>
          <!-- 略 -->
          <div class="mb-12">
            <label for="formFile" class="form-label">外観写真</label>
            <input class="form-control" name="appearance" type="file">
          </div>
          <!-- 略 -->
          <div class="row">
            <div class="col-auto">
              <button type="submit" class="btn btn-primary mb-3">チラシ作成</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
  </body>
</html>

入力項目は多いですが、結果として次のような表示になります。

f:id:moongift:20210813211244j:plain

これはPHP内蔵サーバを使って http://localhost:8000/estimate.php にアクセスした時の表示です。サーバの起動は下記コマンドになります。estimate.phpがあるディレクトリにて実行してください。

php -S localhost:8000

PDF作成処理について

チラシの情報を入力して送信すると、PDF作成処理に入ります。まず、Docurainのシークレットキーと、テンプレートファイルのパス、APIエンドポイントを用意します。テンプレートファイルはあらかじめDocurain上にアップロードしておくこともできますが、開発中はトライアンドエラーになると思いますので、ローカルファイルを用いるインスタントAPIの方が効率的でしょう。

APIエンドポイントについては今回はPDFですが、jpgなども指定できます。詳しくはこちらを参照してください(要ログイン)。

// PDF作成処理に関する記述
$secret_key = 'YOUR_DOCURAIN_SECRET_KEY';
$template_path = '/path/to/real-estimate.xls';
$url = 'https://api.docurain.jp/api/instant/pdf';

パラメータの作成

次に送信するパラメータを用意します。ベースになるのはフォームで入力された内容($_POST)です。

$params = $_POST;

そして設定された画像を、それぞれDATA URI形式に変換します。PHPでは $_FILES で受け取れますので、それぞれ変換してパラメータに指定していきます。画像サイズが大きいとエラーになってしまうので注意してください。

foreach ($_FILES as $name => $path) {
  // Mimeタイプを設定
  $uri = 'data:' . mime_content_type($path['tmp_name']) . ';base64,';
  // Base64変換した文字列を設定
  $uri .= base64_encode(file_get_contents($path['tmp_name']));
  // パラメータに追加
  $params[$name] = $uri;
}

作成したパラメータをJSON文字列にします。

$entity_json = json_encode($params);

リクエストパラメータの準備

次にテンプレート情報を含めて、リクエストパラメータを準備します。これは2つのパラメータがあります。

  • entity
    フォーム入力内容。JSONとして送信します。
  • template
    テンプレート用のExcelファイル。Content-Typeは拡張子(xlsまたはxlsx)によって異なります。内容はバイナリです。

内容はそれぞれ次のようになります。

$param = [
  [
      'name' => 'entity',
      'contents' => $entity_json,
      'headers'  => [
        'Content-Type' => 'application/json'
      ]
  ],
  [
      'name' => 'template',
      'contents' => file_get_contents($template_path),
      'headers'  => [
        'Content-Type' => mime_content_type($template_path)
      ],
      'filename' => basename($template_path)
  ],
];

HTTPヘッダーの準備

インスタントAPIの場合、レスポンスはエンドポイントによって異なりますので、Content-Typeを指定する必要はありません。認証ヘッダーのみになります。

$headers['Authorization'] = "token {$secret_key}";

リクエスト情報をまとめる

これで必要なリクエスト情報は集まりましたので、1つの変数にまとめます。

$options = [
  'http_errors' => false,
  'headers'     => $headers,
  'multipart'   => $param
];

リクエスト実行

そしてリクエストを実行します。

$client = new Client();
$res = $client->request('POST', $url, $options);

処理がうまくいったかどうかはレスポンスのステータスコードで判別してください。

if ($res->getStatusCode() === 200) {
} else {
    // APIリクエストエラーの場合
    echo $res->getBody();
}

処理がうまくいった場合には、HTTPヘッダーを付けて結果をファイル estimate.pdf としてダウンロードさせます。

  header("Content-Disposition: attachment; filename=estimate.pdf");
  echo $res->getBody();
  exit;
}

これでPDF出力処理の完成です。

f:id:moongift:20210813211923j:plain

全体のコード

長いですが、全体のコードは次のようになります。

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
if (!$_POST) {
?><!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>不動産チラシ作成</title>
  <meta name="description" content="The HTML5 Herald">
  <meta name="author" content="SitePoint">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    <div class="row">
      <div class="md-12">
        <h2>不動産チラシ作成</h2>
      </div>
      <div class="col-md-12">
        <form class="form" action="/estimate.php" method="POST" enctype="multipart/form-data">
          <div class="row">
            <div class="mb-12">
              <label class="form-label">物件名</label>
              <input type="text" class="form-control" name="name" placeholder="物件名" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">所在地</label>
              <input type="text" class="form-control" name="address" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">最寄り施設</label>
              <input type="text" class="form-control" name="location" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">交通</label>
              <input type="text" class="form-control" name="access" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">間取り(専有面積)</label>
              <input type="text" class="form-control" name="area" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">家賃</label>
              <input type="text" class="form-control" name="cost" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">共益費</label>
              <input type="text" class="form-control" name="fee" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">敷金</label>
              <input type="text" class="form-control" name="deposit" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">礼金</label>
              <input type="text" class="form-control" name="key_money" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">種別</label>
              <input type="text" class="form-control" name="type" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">築年数</label>
              <input type="text" class="form-control" name="build_at" placeholder="" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">設備1</label>
              <input type="text" class="form-control" name="facility1" placeholder="設備1" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">設備2</label>
              <input type="text" class="form-control" name="facility2" placeholder="設備2" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">備考</label>
              <input type="text" class="form-control" name="note" placeholder="特記事項です" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">PRポイント</label>
              <textarea class="form-control" name="pr" placeholder="PRポイントを書きます"></textarea>
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">タイトル(チラシ上段)</label>
              <input type="text" class="form-control" name="title" placeholder="タイトル" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">一押しポイント1</label>
              <input type="text" class="form-control" name="goods[]" placeholder="一押しポイント1" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">一押しポイント2</label>
              <input type="text" class="form-control" name="goods[]" placeholder="一押しポイント2" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">一押しポイント3</label>
              <input type="text" class="form-control" name="goods[]" placeholder="一押しポイント3" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">QRコード用物件URL</label>
              <input type="text" class="form-control" name="url" placeholder="https://docurain.jp/" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">電話番号</label>
              <input type="text" class="form-control" name="tel" placeholder="000-0000-0000" value="">
            </div>
          </div>
          <div class="row">
            <div class="mb-12">
              <label class="form-label">ホームページ</label>
              <input type="text" class="form-control" name="website" placeholder="https://docurain.jp/" value="">
            </div>
          </div>
          <div class="mb-12">
            <label for="formFile" class="form-label">外観写真</label>
            <input class="form-control" name="appearance" type="file">
          </div>
          <div class="mb-12">
            <label for="formFile" class="form-label">内観写真1</label>
            <input class="form-control" name="preview1" type="file">
          </div>
          <div class="mb-12">
            <label for="formFile" class="form-label">内観写真2</label>
            <input class="form-control" name="preview2" type="file">
          </div>
          <div class="mb-12">
            <label for="formFile" class="form-label">見取り図</label>
            <input class="form-control" name="floor" type="file">
          </div>
          <div class="row">
            <div class="col-auto">
              <button type="submit" class="btn btn-primary mb-3">チラシ作成</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
  </body>
</html>
<?php
} else {
  $secret_key = 'YOUR_DOCURAIN_SECRET_KEY';
  $params = $_POST;
  $template_path = '/path/to/real-estimate.xls';

  foreach ($_FILES as $name => $path) {
    $uri = 'data:' . mime_content_type($path['tmp_name']) . ';base64,';
    $uri .= base64_encode(file_get_contents($path['tmp_name']));
    $params[$name] = $uri;
  }
  $entity_json = json_encode($params);

  $url = 'https://api.docurain.jp/api/instant/pdf';
  $param = [
    [
        'name' => 'entity',
        'contents' => $entity_json,
        'headers'  => [
          'Content-Type' => 'application/json'
        ]
    ],
    [
        'name' => 'template',
        'contents' => file_get_contents($template_path),
        'headers'  => [
          'Content-Type' => mime_content_type($template_path)
        ],
        'filename' => basename($template_path)
    ],
  ];    

  $headers['Authorization'] = "token {$secret_key}";
  $options = [
    'http_errors' => false,
    'headers'     => $headers,
    'multipart'   => $param
  ];

  $client = new Client();
  $res = $client->request('POST', $url, $options);
  if ($res->getStatusCode() === 200) {
    header("Content-Disposition: attachment; filename=estimate.pdf");
    echo $res->getBody();
    exit;
  } else {
      // APIリクエストエラーの場合
      echo $res->getBody();
  }
}

Tips

今回の不動産風チラシの作成時における、幾つかのTipsを紹介します。

画像サイズについて

Docurainでは図形のサイズと画像サイズをぴったり合わせる(または等倍にする)と綺麗に出力されます。Excelでは図形サイズの指定がセンチメートル指定になるので、画像編集ソフトウェアを使う際にもセンチメートル指定で作成すると良いでしょう。

f:id:moongift:20210813212216p:plain

フォーマットはxls

Docurainではテンプレートファイルのフォーマットとしてxlsまたはxlsxを指定できます。今回はxlsを使った方が、画像がぴったり表示されました。もし片方のフォーマットでうまくいかない場合には、もう片方のフォーマットで試してみてください。

まとめ

今回はいわゆる帳票ではない、チラシ的なものをDocurainを通して作成しました。画像やQRコードなどを使うことで、表現力が大きく向上するのが分かってもらえたかと思います。これまでは業者に依頼していたようなものも、PDF出力して印刷することで自社内で完結できるようになるでしょう。

なお、通常であればWebページから一つ一つの帳票を作成することはないでしょう。システム連携してDBなどから自動的にDocurainのAPIを呼び出すはずです。とはいえ、こういったちょっとした帳票やチラシなどもDocurainから作れると覚えておくと、利用できる幅が広がるのではないでしょうか。

ぜひDocurainを使って、業務フローを強化、改善してください。