Docurain Labo

Docurainサービス開発日記

外部サービスと組み合わせて、帳票の内容を動的に変更する

Docurainを使って帳票を出力する場合、そのデータの多くは社内データベースからの出力になるでしょう。しかし、外部サービスと組み合わせることで、データをよりダイナミックに変更できます。プログラムから帳票生成を実行すれば、そういった操作も簡単です。

今回はごく簡単な例として、住所をジオコーディングして位置情報に変換し、さらにその位置情報を使って静的な地図画像を取得します。帳票に限りませんが、PDFの中に外部サービスの情報を埋め込む際のテクニックの1つとして参考にしてください。

データはダミーで生成

今回のデータは個人情報テストデータジェネレーターを使ってダミーで生成しています。

f:id:moongift:20211020213910j:plain

名前、社名、郵便番号と住所を生成し、Googleスプレッドシートに記述しています。

f:id:moongift:20211020213854j:plain

GoogleスプレッドシートからデータをJSONで取得

Googleスプレッドシートに記述しているデータをJSON化するためにSSSAPIを利用しています。SSSAPIはGoogleスプレッドシートを読み込み専用のAPIにしてくれるサービスになります。

f:id:moongift:20211020213931j:plain

先ほどのデータは次のようなURLで取得できます。

https://api.sssapi.app/AAAAAAAA

アクセスすると、以下のようなJSONが取得できます。

[
    {
        "name": "廣瀬 勝久",
        "zipcode": "566-5125",
        "address": "大阪府大阪市淀川区加島1-2-604",
        "company": "有限会社長谷川商店"
    },
  :
    {
        "name": "福山 知香子",
        "zipcode": "804-0049",
        "address": "福岡県福岡市早良区飯倉1-5-21",
        "company": "株式会社佐藤工務店"
    }
]

住所を位置情報に変換する

住所を位置情報に変換することをジオコーディングと言いますが、今回はシンプル ジオコーディング 実験 – 位置参照技術を用いたツールとユーティリティを利用しています。こちらは登録なしで利用できますが、あくまでも実験的なものになりますので、ビジネスで利用される際には他の地図サービスを利用した方が良いでしょう。このサービスでジオコーディングを行う際には、次のようなURLになります。

https://geocode.csis.u-tokyo.ac.jp/cgi-bin/simple_geocode.cgi?addr=(住所)

これで、次のような結果が得られます。

<results>
  <query>目黒区駒場4-6-1</query>
  <geodetic>wgs1984</geodetic>
  <iConf>5</iConf>
  <converted>目黒区駒場4-6-</converted>
  <candidate>
  <address>東京都/目黒区/駒場/四丁目/6番</address>
  <longitude>139.676651</longitude>
  <latitude>35.663120</latitude>
  <iLvl>7</iLvl>
  </candidate>
</results>

今回必要なのは、この結果の中の latitude (緯度)と longitude (経度)になります。

位置情報から静的な地図画像を取得する

最後に位置情報から地図画像を取得する処理ですが、これはMapboxを利用します。MapboxはOpenStreetMapを使った地図サービスになります。静的画像APIのURLはStatic Images API | Playground | Mapboxを使って生成できます。

f:id:moongift:20211020214021j:plain

例えばURLは次のようなものです。

https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/(経度),(緯度),16,0/300x300?access_token=(アクセストークン)

これを実行すると、PNG画像で該当部分の地図が取得できます。

f:id:moongift:20211020214102p:plain

以上で帳票に掲載する素材が揃いました。

帳票の設計

今回はシンプルな帳票となっています。注意点として、画像はシェイプを使って描画する枠を作成しておきます。そして、その内容を #IMAGE($contact.map) としておきます。

f:id:moongift:20211020214652p:plain

A1とA2セルでは次のように記述しておき、データを繰り返し処理します。

#set($e=$ROOT)
#foreach($contact in $e.contacts)

A16あたりで繰り返し処理の終了指定を忘れずに行っておきましょう。

#end

コードを書く

では外部APIの実行を含めたDocurainの帳票生成プログラムを作成していきます。今回はRubyを利用しています。

利用するライブラリのインストール

HTTP用のライブラリを含め、次のライブラリを使っています。

  • faraday
  • faraday_middleware
  • ruby-filemagic

適当なディレクトリでGemfileを作成します。

$ bundle init
Writing new Gemfile to /path/to/dir/Gemfile

このファイルを編集します。今回は次のように変更しています。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"
gem "faraday"
gem "faraday_middleware"
gem "ruby-filemagic"

そしてライブラリをインストールします。

$ bundle install

スクリプトの作成

適当なファイル(今回はupload.rbとしています)を作成し、ライブラリを読み込みます。

# Docurainへのアクセス用
require 'faraday'
require 'faraday_middleware'
require 'ruby-filemagic'
# 他のAPIへのアクセス用
require 'open-uri'
require 'uri'
require 'json'
# 地図画像をBase64エンコードする際に利用
require 'base64'

次にSSSAPIへのアクセス用関数を用意します。実際のURLはあなたが使っているものに書き換えてください。

def get_contact
  JSON.parse open("https://api.sssapi.app/AAAAA").read
end

次に地図画像を得る処理です。まず最初に住所から位置情報に変換し、その後MapboxのAPIを使って静的な地図画像を取得します。Docurainで画像を利用する際にはDataURI形式で投稿する必要があります。今回はPNG画像と決め打ちにしていますが、画像形式に合わせて適宜変更してください。

def get_map(address)
  # 住所から位置情報に変換する
  url = "https://geocode.csis.u-tokyo.ac.jp/cgi-bin/simple_geocode.cgi?addr=#{URI.encode(address)}"
  results = open(url).read
  # XMLですが、今回は正規表現で緯度経度を取得します
  longitude = results.match(/.*<longitude>(.*)<\/longitude>.*/m)[1]
  latitude = results.match(/.*<latitude>(.*)<\/latitude>.*/m)[1]
  # Mapboxにアクセスして、静的地図画像を取得します
  access_token = "YOUR_MAPBOX_ACCESS_TOKEN"
  url = "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/#{longitude},#{latitude},16,0/300x300?access_token=#{access_token}"
  results = open(url).read
  # バイナリデータなのでBase64エンコードし、DataURI形式にします
  "data:image/png;base64,#{Base64.encode64(results)}".gsub(/\n/, '')
end

DocurainのAPIを実行する処理

次にDocurainの実行部分を作成します。ここからは api_call 関数の内容です。

# 帳票レンダリング(インスタント)APIコール
def api_call
  # この中に書きます
end

まず必要な変数を準備します。

token = 'YOUR_API_TOKEN'                  # Docurain API トークン
out_type = 'pdf'                          # 出力形式
template_path = './template.xlsx'         # テンプレートファイルパス
entity_json = open('./entity.json').read  # テンプレート置き換え文字列のJSON
path = "/api/instant/#{out_type}"         # APIのパス

次にテンプレートファイルのコンテンツタイプを取得します。

# テンプレートファイルのContent-Typeを取得
template_content_type = FileMagic.new(FileMagic::MAGIC_MIME).file(template_path, true)

これらの情報を使ってリクエストボディを作ります。

# リクエストボディの作成
params = {
  template: Faraday::FilePart.new(template_path, template_content_type),
  entity:  Faraday::ParamPart.new(entity_json, 'application/json')
}

後はFaraday(HTTPクライアント)を使って、APIを実行します。

# APIを実行する
conn = Faraday.new(:url => 'https://api.docurain.jp') do |builder|
  builder.request :multipart
  builder.adapter :net_http
end
# 認証ヘッダーの設定
conn.headers['Authorization'] = "token #{token}"
# 実行結果(レスポンス)を返す
conn.post(path, params)

これでAPI呼び出し処理の完成です。関数全体の内容は次のようになります。

# 帳票レンダリング(インスタント)APIコール
# 引数のentiry_jsonはテンプレート置き換え文字列のJSON
def api_call(entiry_json)
  token = 'YOUR_API_TOKEN'                  # Docurain API トークン
  out_type = 'pdf'                          # 出力形式
  template_path = './contact.xlsx'         # テンプレートファイルパス
  path = "/api/instant/#{out_type}"         # APIのパス
  # テンプレートファイルのContent-Typeを取得
  template_content_type = FileMagic.new(FileMagic::MAGIC_MIME).file(template_path, true)
  # リクエストボディの作成
  params = {
    template: Faraday::FilePart.new(template_path, template_content_type),
    entity:  Faraday::ParamPart.new(entity_json, 'application/json')
  }
  # APIを実行する
  conn = Faraday.new(:url => 'https://api.docurain.jp') do |builder|
    builder.request :multipart
    builder.adapter :net_http
  end
  # 認証ヘッダーの設定
  conn.headers['Authorization'] = "token #{token}"
  # 実行結果(レスポンス)を返す
  conn.post(path, params)
end

レスポンスを判定する

次にレスポンスの内容を判定して、レポートをファイル出力します。ここからは res_handle 関数の内容です。

# レスポンスハンドリング(適宜必要なハンドリングを行ってください)
def res_handle(res)
  # この中に書きます
end

まずHTTPステータスが200(正常終了)以外の場合はエラーメッセージを出して終了します。

if res.status != 200 # エラー判定
  puts res.body # エラーメッセージを返して終了
  return
end

次にレスポンスのContent-Typeから、出力するファイルの拡張子を指定します。

# 正常時、カレントディレクトリにファイル保存
# content-typeから拡張子に変換
extensions = {
  'application/pdf' => 'pdf',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
  'application/vnd.ms-excel' => 'xls',
  'image/svg+xml' => 'svg',
  'image/png' => 'png',
  'image/jpeg' => 'jpg',
  'image/gif' => 'gif',
}
content_type = res.headers['content-type']
ext = extensions[content_type]

後はファイル名(今回は test に固定)を決めて、ファイル書き出しします。

# ファイル名を決めて書き出し
file_name = "test.#{ext}"
filePath = "#{Dir.pwd}/#{file_name}";
File.binwrite(filePath, res.body)
# メッセージを出力
puts "saved : #{filePath}"

この関数の全体像は次のようになります。

# レスポンスハンドリング(適宜必要なハンドリングを行ってください)
def res_handle(res)
  if res.status != 200 # エラー判定
    puts res.body # エラーメッセージを返して終了
    return
  end
  # 正常時、カレントディレクトリにファイル保存
  # content-typeから拡張子に変換
  extensions = {
    'application/pdf' => 'pdf',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
    'application/vnd.ms-excel' => 'xls',
    'image/svg+xml' => 'svg',
    'image/png' => 'png',
    'image/jpeg' => 'jpg',
    'image/gif' => 'gif',
  }
  content_type = res.headers['content-type']
  ext = extensions[content_type]
  # ファイル名を決めて書き出し
  file_name = "test.#{ext}"
  filePath = "#{Dir.pwd}/#{file_name}";
  File.binwrite(filePath, res.body)
  # メッセージを出力
  puts "saved : #{filePath}"
end

実行する

後は関数を順番に実行していきます。

contacts = get_contact
contacts.each do | contact |
  # mapの中に地図データ(DataURI形式)を入れる
  contact["map"] = get_map(contact["address"])
end

# テンプレート書き換え用パラメータの準備
params = {}
params[:contacts] = contacts
res = api_call(params.to_json)  # APIコール
res_handle(res) # レスポンスハンドリング

出力結果

実行結果です。各コンタクトごとにページが分かれ、入力した情報と地図が埋め込まれています。

f:id:moongift:20211020214330p:plain

まとめ

今回は外部サービスを多用しながら、Docurainの出力を動的に変更する例を解説しました。ここまで使わずとも、外部サービスを使うことで社内データにはない情報を帳票に埋め込めるようになります。地図やWebサイトのサムネイル、アクセス解析結果など帳票やレポートをグレードアップさせるためにも、外部サービスを利用してください。