ColdFusion-開発効率を求められる今だから知りたい高性能Webアプリケーションサーバー

第6回ColdFusionでPDFを扱う

第5回目の記事ではHTMLフォームからのデータの扱いについて学びました。フォームからPOSTで送信されたデータはFormスコープに、GETで送られたデータはUrlスコープに代入されることが分かりました。

それと併せて、あるスコープに変数が定義されているかどうかをStructKeyExists()関数で調べる方法を学びました。IsDefined()関数でも同様のことが可能ですが、IsDefined()関数は参照可能なスコープを全て辿っていってしまうため、予期しない値を返すことを学びました。

今回はAdobe製品群の一員となったことで強化されたPDF機能を学びます。

HTMLをPDFに変換する

ColdFusion MX 7からの機能ですが、cfdocumentタグを使用することで、cfdocumentタグで囲った中身のHTMLをPDFに変換することができます。簡単な例では以下のようなものになります。{cf_root}フォルダ({cf_root}はColdFusionをインストールしたディレクトリです。Windowsでは『C:\ColdFusion8』になります)内にある『wwwroot』の中に『gihyo』という名前のフォルダを作り、その中に『6』というフォルダを作ってください。⁠6』のフォルダの中に以下のような中身のファイルをcfdocument.cfmとして保存してください(ファイルのパスは{cf_root}/wwwroot/gihyo/6/cfdocument.cfmになります⁠⁠。なお保存時の文字コードはUTF-8としてください。その後、http://127.0.0.1:8500/gihyo/6/cfdocument.cfmでアクセスしてみましょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfdocument format="PDF">
<style>
h1{
  border-left: 5px solid #067499;
  border-bottom: 1px solid #067499;
  padding-left: 0.5em;
  font-size: 1.5em;
}
</style>
  <h1>ColdFusion-開発効率を求められる今だから知りたい高性能Webアプリケーションサーバー</h1>
  <p>2007年9月21日に最新版ColdFusion 8日本語版がアドビシステムズ株式会社よりリリースされました。この連載では、なぜColdFusionが長きに渡ってWeb開発者から支持を集めてきたのか、その魅力に迫ります。</p>
</cfdocument>
図1 cfdocument.cfmの実行結果
図1 cfdocument.cfmの実行結果

Adobe Readerなどのインストールが必要ですが、ご覧の通りcfdocumentタグで囲った中身がPDF化されて表示されていることが分かります。

また、cfdocumentタグの中身にはCFMLを含ませることができます。例えば、第4回の例で使ったcfchartタグでグラフを描画し、それをPDF化してみましょう。なお、cfchartタグをcfdocumentタグの中に入れることができるのはColdFusion 8からの機能になります。

以下の例をchartpdf.cfmとして保存し、アクセスすると、PDF化されたグラフがブラウザ上で表示またはダウンロードされるでしょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfquery datasource="cfartgallery" name="qry">
  SELECT
    COUNT(art.artID) AS num, art.mediaID
  FROM
    art
  WHERE
    isSold=0
  GROUP BY
    mediaID
</cfquery>
<cfdocument format="PDF" name="aPdf">
  <cfchart format="png" chartheight="200" chartwidth="440" font="arialunicodeMS" xAxisTitle="メディアID " yAxisTitle="個">
    <cfchartseries type="bar" query="qry" itemColumn="mediaID" valueColumn="num" seriesLabel="在庫" ></cfchartseries>
  </cfchart>
</cfdocument>
<cfcontent type="application/pdf" variable="#aPdf#">
図2 chartpdf.cfmの実行結果
図2 chartpdf.cfmの実行結果

この例ではPDFのバイナリデータを「aPdf」と言う名前の変数に格納し、そのデータをcfcontentタグのvariable属性を使い直接ブラウザに送出しています。MIME media typeとしては「application/pdf」を指定しているので、お使いのブラウザ環境によって、ブラウザ内で開かれたり、ダウンロードダイアログが出るなどの動作になると思います。

このようにCFMLを組み合わせてクエリーを元にグラフなどを描画し、それをPDF化することも可能なので、決算短信や営業成績レポートなどの生成に使うこともできるでしょう。

cfpdfタグ

ColdFusion 8から、PDF操作専用のCFMLタグcfpdfタグが追加されました。cfpdfタグでは以下のようなことができます。

  • 透かしの追加・除去
  • PDFの情報の取得
  • 2つ以上のPDFファイルのマージ
  • PDFファイルのパスワードでの保護及び暗号化
  • PDFの各ページのサムネイルの作成
  • DDX(Document Description XML)を用いたLiveCycle Assemblerによる高度なPDF処理

PDFを暗号化する

cfpdfタグを使って、送信されてきたPDFファイルを暗号化してみることにしましょう。以下の例のpdfenc.cfmとして保存し、適当なPDFColdFusion 8パフォーマンス評価ガイドなど)をダウンロードした後、pdfenc.cfmにアクセスし、ダウンロードしたPDFとパスワードを入力し、⁠暗号化してダウンロード」ボタンを押してみましょう。ファイルダウンロードダイアログが表示されると思います。そして、ダウンロードしたPDFを開くには、先ほどフォームに入力したパスワードの入力が求められると思います。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfif StructKeyExists(Form, 'submit')>
  <cffile action="upload" filefield="pdf" destination="#GetTempDirectory()#" nameconflict="MakeUnique">
  <cfpdf action="protect" newUserPassword="#Form.password#" source="#cffile.serverDirectory#/#cffile.serverFile#" name="aPdf">
  <cfset aDownloadName = ReplaceNoCase(cffile.serverFile, '.pdf', '_enc.pdf')>
  <cfheader name="Content-Disposition" value="attachment; filename=#aDownloadName#">
  <cfcontent type="application/octet-stream" variable="#ToBinary(aPdf)#" reset="false">
</cfif>
<cfcontent type="text/html; charset=UTF-8">
<form action="pdfenc.cfm" method="post" enctype="multipart/form-data">
  <p>PDFファイルを指定してください:<input type="file" name="pdf"><br>
     パスワードを入力してください:<input type="password" name="password" value=""><br>
     <input type="submit" name="submit" value="暗号化してダウンロード" ></p>
</form>

いくつか見慣れないタグやコードが出てきているので解説します。

cffileタグではHTMLフォームからアップロードされたファイルを実際にサーバー上にアップロードする作業をします。action属性に「upload」を指定し、フォームからのデータをアップロードする処理であることを明示します。次にfilefield属性にフォームのtype="file"に付けた名前(今回は「pdf⁠⁠)を指定し、どのフィールド名のファイルを扱うかを指定します。最後にdestination属性に、サーバーのどのディレクトリにファイルを置くかを指定します。今回はGetTempDirectory()関数でColdFusionの一時ファイルを置くディレクトリを指定しました。nameconflict="MakeUnique"となっているのは、destination属性で指定したディレクトリに同名のファイルがアップロードされた場合、サーバー上で上書きされないようにするための処置です。

cffileタグでファイルをアップロードすると、cffileスコープというスコープが生成されます。cffileスコープにはserverDirectory(アップロードしたファイルがあるディレクトリ。つまり先ほどdestination属性属性で設定したディレクトリ)やserverFile(アップロードしたファイルのファイル名)などが格納されています。それ以外の値についてはcffileスコープをcfdumpしてみてください(またはcffile action = "upload"のリファレンスページを参照してください⁠⁠。

次に、cfpdf action="protect"とし、source属性で指定したPDFファイルにnewUserPassword属性で指定したパスワードをかける処理をしています。パスワード処理したPDFファイルはname属性で指定した変数に格納されます。

ReplaceNoCase()関数は、その名前の通り大文字小文字を区別せず文字列を置換する関数で、ブラウザにダウンロードさせるファイル名の末尾が「_enc.pdf」になるように、元のアップロードファイル名からダウンロードファイル名を作り出しています。

cfheaderタグはHTTPレスポンスヘッダを指定するためのタグです。今回は先ほどReplaceNoCase()関数で作り出したファイル名でファイルを保存させたいため、HTTPレスポンスヘッダにContent-Dispositionを追加しています。

最後にcfcontentタグで、MIME media typeが「application/octet-stream」なデータをこれから送ることを宣言し、データの中身をvariable属性に渡しています。

オーダーメイドPDFを作る

cfpdfタグではPDF中の任意のページを抽出して、それらを結合し1枚のPDFにすることができます。この機能を利用してオーダーメイドPDFを作成することにしましょう。オーダーメイドPDFとは、あるPDFからユーザーが欲しいと思ったページのみを抽出し、1ファイルにまとめたPDFの事です。

まず、適当なPDFファイルColdFusion 8パフォーマンス評価ガイドなど)を{cf_root}/wwwroot/gihyo/6/source.pdfとして保存します。その後、以下のCFMLをordermade.cfmとして保存し、アクセスします。

<cfprocessingdirective pageEncoding="UTF-8" />
<!--- PDFのサムネイルを置くディレクトリなど --->
<cfset aCurrentDir = GetDirectoryFromPath(GetTemplatePath())>
<cfset aThumbsUrl = 'thumbs'>
<cfset aThumbsDir = aCurrentDir & aThumbsUrl>
<cfset aSourcePdf = aCurrentDir & 'source.pdf'>

<cfif NOT DirectoryExists(aThumbsDir)>
  <!--- PDFのサムネイルが作成されていないので作成する --->
  <cfdirectory
    action="create"
    directory="#aThumbsDir#">
  <cfpdf
    action="thumbnail"
    source="#aSourcePdf#"
    destination="#aThumbsDir#"
    format="png"
    scale="50"
    imagePrefix="thumbs"
    overwrite="yes">
</cfif>

<!--- オーダーメイドPDFを作る部分 --->
<cfif StructKeyExists(Form, 'submit')>
  <cfpdf action="merge" source="#aSourcePdf#" pages="#Form.page#" name="aPdf">
  <cfheader name="Content-Disposition" value="attachment; filename=catalog.pdf">
  <cfcontent type="application/octet-stream" variable="#ToBinary(aPdf)#" reset="false">
</cfif>

<!--- オーダーメイド用の情報を指定してもらう部分 --->
<cfcontent type="text/html; charset=UTF-8">
<cfpdf action="getInfo" source="#aSourcePdf#" name="aPDFInfo">
<form action="ordermade.cfm" method="post">
  <p>
    <cfoutput>
      <ul>
        <cfloop from="1" to="#aPDFInfo.TotalPages#" index="i">
          <li><input type="checkbox" name="page" value="#i#">ページ#i#
            <img src="#aThumbsUrl#/thumbs_page_#i#.png" height="100" style="vertical-align:middle;"></li>
        </cfloop>
      </ul>
    </cfoutput>
    <input type="submit" name="submit" value="オーダーメイドする" >
  </p>
</form>

すると、source.pdfとして保存したPDFの各ページのサムネイルが表示され、左横にチェックボックスが現れたと思います。欲しいページにチェックボックスを入れ、⁠オーダーメイドする」ボタンを押すと、チェックを入れたページのみが書き込まれたPDFが生成され、ダウンロードダイアログが表示されるかと思います。

図3 ordermade.cfmの実行結果
図3 ordermade.cfmの実行結果

このように顧客一人一人に合わせたPDFをサーバー上で作成することも可能なので、オーダーメイドのカタログを作成することも可能でしょう。

また、今回は紹介しませんでしたが、cfprintタグと組み合わせると、ColdFusionが動いているサーバーに接続されたプリンターからPDFを印刷することも可能です。顧客が選んだページのみをプリンタで出力し、顧客ごとにカスタマイズされたダイレクトメールを郵送するなどの使い方も考えられるでしょう。

まとめと次回予告

今回はAdobe製品の一員となったことで強化されたPDF機能をご紹介しました。

特にColdFusion 8で追加されたcfpdfタグでは、複雑なPDF操作がカプセル化されており、オーダーメイドPDFのような複雑な処理も40行程度で記述できてしまうことが分かりました。

今回はご紹介していませんが、DDXというLiveCycleで用いるPDF処理言語を用いると、PDFにヘッダを追加するなど、LiveCycleで行うような複雑なPDF操作をColdFusion上で実現することもできます。

帳票文化が色濃く残る日本において、PDFがタグ1つで簡便に扱えるColdFusionのメリットは計り知れないでしょう。

次回はラインデバッギングをご紹介します。

おすすめ記事

記事・ニュース一覧