2017年7月31日月曜日

力技_02:FileMakerからExcelワークブックをつくる(その4)

FileMakerからスクリプトを出力する


 Excelの方でスクリプトからワークシートを生成できるようになったので、今度はFileMakerからのスクリプト出力を作っていきます。

カスタム関数を定義する


 力技EMLではひとつのコマンドと複数のパラメータでひとつの命令セットとなります。FileMakerでは複数の値のまとまりはList(値一覧)=改行区切りテキストで扱うようになっていて※1、そのためのList関数も用意されているのでこれを使わない手はありません。
 ただ、これをExcelに持って行くときにはListをscriptワークシート上の1行に水平方向に並べていく必要があるため、改行区切りではなくタブ区切りの方が扱いやすくなります。
 この改行区切り→タブ区切りの変換を行う手順が度々でてくるので、カスタム関数を作ってスッキリさせましょう。

※1 少なくともバージョン15までは。バージョン16からJSONがサポートされたので、今後はこちらが使われてくると思われる。…というか個人的には使っていきたい。

 listToTABという名前でこのような関数を作りました。改行コードをタブに置き換えるためにSubstitute関数の置換条件指定を省略できるようにしただけの単純なものです。
 FileMaker Pro Advancedをお持ちでない方は、カスタム関数の定義ができないので、以下の説明を見て自作されるときはSubstituteを使うか、ダウンロードしたサンプルファイルをベースに作ってみてください。(定義済みのカスタム関数はAdvancedでなくても利用可能です)

 これを使って早速EML作成。一つの計算式でドドっと記述してしまうとこんな感じ。

 基本的には
  listToTAB ( List ( コマンド ; パラメータ1 [ ; パラメータ2 ; … ] ) )
で1命令を構成し、複数の命令はList ()で囲います。
 出来上がった文字列はタブ区切りテキストの形式になります。

 このテキストフィールドをコピーして、前回作成したExcelマクロを組み込んだファイルの「script」ワークシートのA1セルを始点にペーストします。Mac版Excelの場合は「形式を選択してペースト」しないとタブが無視されてしまうようです。
FileMakerで作成したEMLスクリプトをExcelにはり込む。
手順例:消去ボタン→A1セルを右クリックし「形式を選択してペースト」







 実行してみると新規エクセルファイルが作られます。
 セル結合や書式設定がされた表ができています。

 この力技もいよいよ佳境に。次回はFileMakerでデータ処理してExcel出力です。

2017年7月22日土曜日

力技_02:FileMakerからExcelワークブックをつくる(その3)

いよいよ作っていきますよ


 いろいろとくどくどと語ってまいりましたが、(その3)にしてやっと制作に入っていきます。
 末尾にVBAのコードを載せておきますので、参照いただきながら解説していきます。
 Excel VBAの知識がないと、この章を読むのはつまらないかもしれませんね。そんな方は下のファイルダウンロードだけゲットして、ここは読み飛ばしてもOKです。

メイン

 サンプルコード冒頭からご覧下さい。
 タイトルコメントと、モジュール(≒グローバル)変数宣言のあと早速メインルーチンですが、めっちゃシンプルです。
 数行の初期化処理の後、ループがひとつ。それだけ。
 ループ内はコマンドを取り出して、Select Case文でそれぞれのコマンドごとに処理が書いてあります。ちょっと長くなるものはサブルーチンとして後ろの方に追い出してcallしています。
 ここに新しくCase文と処理を書いていけば、お好きなコマンドを追加することができますよ。

pick関数群


 現在処理している行:指定した列のセルの値を返してくる関数群です。
 まったく同じ処理が返値の型別に4種類用意してあります。
 Excelのセル上にスクリプトを記述する仕組みなので、セルの列番号を指定するだけでコマンドや必要なパラメータを取得できます。普通だったら()やら""やら<>なんかで囲まれている文字がどうのこうのと真面目に構文解析するとかなり面倒ですが、このへん非常にらくちん処理になっております。

定数定義


 メインの冒頭で呼ばれている定数初期化のサブルーチンです。
 ここで、いわゆるxl定数を定義しています。
 文字位置や線スタイルなどで指定できるのはこれらの値になります。

たったこれだけ


 以下、メインから追い出されたコマンドのサブルーチン群があるだけで、VBAでのプログラミングはおしまいです。
 あとは、これにかけるスクリプトをFileMakerで生成すればOKですね。次回はこれに挑戦します。

 つづく。 

サンプルダウンロード


今回作成したサンプルファイル "ExcelMaker.xlsm"

サンプルコード(Excel VBA)


'ExcelMaker (example for the blog “BulldozeFileMaker”)
'By kamima 2017 email:kamima@mac.com
'力技FileMakerのための作例 2017.7
'ご自分のソリューションに自由にご利用ください
'書籍・ウェブ上の記事等への利用の場合はkamima@mac.comまでご一報いただけると励みになります
'Free to arrange/use for your solutions.

Dim makerBook   As Workbook         'This WorkBook (contains this program)
Dim scriptSheet As Worksheet        'This WorkBook's sheet(1) "script"
Dim targetBook  As Workbook
Dim targetSheet As Worksheet
Dim line        As Long             'program counter
Dim constant    As New Collection   'xl* constants (lineStyle etc.)

'//
'// main:
'//

Sub makeExcel()
    Application.DisplayAlerts = False
    line = 0
    Set makerBook = ActiveWorkbook
    Set scriptSheet = makerBook.Sheets(1)

    Call init

    Dim cmd As String

    Do
        line = line + 1
        cmd = pickStr(1)
        If cmd = "" Then cmd = "end"

        Select Case cmd
            Case "newBook"
                Set targetBook = newBook(pickInt(2))  '(numOfSheets)
                Set targetSheet = targetBook.Sheets(1)
            Case "selectSheet"
                Set targetSheet = targetBook.Sheets(pickInt(2)) '(sheetNum)
                targetSheet.Select
            Case "nameSheet"
                targetSheet.Name = pickStr(2)
            Case "width"
                Call width
            Case "height"
                Call height
            Case "widthRange"
                targetSheet.Columns(pickInt(2) & ":" & pickInt(3)).columnWidth = pickInt(4)
            Case "heightRange"
                targetSheet.Rows(pickInt(2) & ":" & pickInt(3)).rowHeight = pickInt(4)
            Case "write"
                Call Write_
            Case "merge"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).Merge
            Case "fontSize"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).Font.Size = pickInt(6)
            Case "hAlign"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).HorizontalAlignment = constant(pickStr(6))
            Case "vAlign"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).VerticalAlignment = constant(pickStr(6))
            Case "borders"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).borders.LineStyle = constant(pickStr(6))
            Case "numberFormat"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).NumberFormatLocal = pickStr(6)
            Case "shrinkToFit"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).ShrinkToFit = pickBool(6)
            Case "wrapText"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).WrapText = pickBool(6)
            Case "addIndent"
                targetSheet.Range(Cells(pickInt(2), pickInt(3)), Cells(pickInt(4), pickInt(5))).AddIndent = pickBool(6)
        End Select
        
    Loop Until cmd = "end"
End
End Sub


'//
'// “pick” functions: fetch a command or a parameter
'//
Private Function pickStr(pos As Integer) As String
    pickStr = scriptSheet.Cells(line, pos)
End Function

Private Function pickInt(pos As Integer) As Integer
    pickInt = scriptSheet.Cells(line, pos)
End Function

Private Function pickSin(pos As Integer) As Single
    pickSin = scriptSheet.Cells(line, pos)
End Function

Private Function pickBool(pos As Integer) As Boolean
    pickBool = scriptSheet.Cells(line, pos)
End Function

'//
'// init: defines xl-constants
'//
Private Sub init()
    constant.Add xlGeneral, "xlGeneral"
    constant.Add xlLeft, "xlLeft"
    constant.Add xlCenter, "xlCenter"
    constant.Add xlRight, "xlRight"
    constant.Add xlFill, "xlFill"
    constant.Add xlJustify, "xlJustify"
    constant.Add xlCenterAcrossSelection, "xlCenterAcrossSelection"
    constant.Add xlDistributed, "xlDistributed"
    constant.Add xlTop, "xlTop"
    constant.Add xlContinuous, "xlContinuous"
    constant.Add xlBottom, "xlBottom"
    constant.Add xlDash, "xlDash"
    constant.Add xlDashDotDot, "xlDashDotDot"
    constant.Add xlDot, "xlDot"
    constant.Add xlDouble, "xlDouble"
    constant.Add xlSlantDashDot, "xlSlantDashDot"
    constant.Add xlLineStyleNone, "xlLineStyleNone"
    constant.Add xlHairline, "xlHairline"
    constant.Add xlThin, "xlThin"
    constant.Add xlMedium, "xlMedium"
    constant.Add xlThick, "xlThick"
End Sub

'//
'// commands
'//

Private Function newBook(ByVal n As Long) As Workbook
    Dim a As Long: a = Application.SheetsInNewWorkbook
    Application.SheetsInNewWorkbook = n
    Workbooks.Add
    Application.SheetsInNewWorkbook = a
    Set newBook = ActiveWorkbook
End Function

Private Sub width()
    Dim i As Integer, target As Integer, columnWidth As Single
    
    i = 0
    target = pickInt(2)
    
    Do
        columnWidth = pickSin(3 + i)
        If columnWidth = 0 Then Exit Do
        targetSheet.Columns(target + i).columnWidth = columnWidth
        i = i + 1
    Loop
    
End Sub

Private Sub height()
    Dim i As Integer, target As Integer, rowHeight As Single
    
    i = 0
    target = pickInt(2)
    
    Do
        rowHeight = pickSin(3 + i)
        If rowHeight = 0 Then Exit Do
        targetSheet.Rows(target + i).rowHeight = rowHeight
        i = i + 1
    Loop
    
End Sub

Private Sub Write_()
    Dim x As Integer, y As Integer, i As Integer
    
    i = 0
    For y = pickInt(2) To pickInt(4)
        For x = pickInt(3) To pickInt(5)
            targetSheet.Cells(y, x) = scriptSheet.Cells(line, 6 + i)
            i = i + 1
        Next
    Next
        
End Sub

力技_02:FileMakerからExcelワークブックをつくる(その2)

サンプルの概要


Excelとの連携部分にフォーカスするため、極力シンプルに設計します。

テーブル「売上DB」
 ・売上日(日付)
 ・本支店(テキスト)
 ・売上金額(数字)

以上、フィールド3つのみ。

 小売店を想定し、1レコードがレシート1枚、「本支店」は売った店名、「売上金額」はそのレシートの合計金額です。
 実際は売上票コードをキーにしてぶら下がった「細目」テーブルに個々の商品の情報があったり、「商品マスタ」や「顧客マスタ」と連携しているなど、各自想像してください。

 これに乱数を使ったスクリプトを回し、2017/1/1〜2017/1/31を対象期間として3万レコードぐらいのダミーデータを作成しました。
 今回はここから2種類の月報シートをExcelファイルとして作ることを目標とします。

作戦会議


 まず、FileMakerからどうやってExcelのファイルを出力するのか。そこから考えます。
 FileMakerが直接.xlsxファイルを吐き出すとか、FileMakerがエクセルを直接操作するような仕組みは、ちょっとハードル高そうです。
 Excelの処理を自動化といえば、やっぱマクロ(Excel VBA)ですよね。
 FileMakerでVBA言語のコードをテキスト形式で書いたり、FileMakerからExcelへ出力したいデータを渡して、作表整形する部分をExcel VBAで書くとか、いくつか方法があると思います。それもありっちゃありですが、作るソリューションごとにその都度専用の設計開発をしなければなりません。
 そこで、FileMakerからはもっとシンプルな形でワークシートの作成手順を記述することにして、Excel VBA側でそれを解釈して動作するようなものを作ったら、いろいろ使い回しも効きそうだし、これはいけるんじゃないかと。
 つまりは

Excelマークアップ言語を開発じゃ!

 大風呂敷を広げてしまいましたが、そんな大層なことはありません。いま自分に必要な最低限の機能だけ実現すればいいのです。
 今回作るサンプルでは、FileMakerで作成した命令一覧をExcelのマクロで解析し、ワークシートを作成します。

力技EML 言語仕様

・EML(Excel Markup Language)スクリプトはExcelのワークシート上に展開される
 (FileMakerからはタブ区切りテキストとして生成)
・1行で1命令
・左端(A列)が命令、B列以降右方向にパラメータを並べる
・パラメータは水平位置固定(コマンドによりどの列が何の値か決まっている)
・変数、条件分岐、ループなどもちろん一切ナシ
・エラー処理もナシ

 どうせExcelだし、ワークシート上にスクリプトを書いちゃえという素敵仕様です。このブログのサンプルとしても、開発や処理の過程を見える化できていいですね。さらに、セルで区切る形で情報が並ぶとExcel VBAからの解析もラクに書けそうです。

コマンド一覧
コマンドパラメータ
(スラッシュはセル区切り)
説明
//注釈(NOP)
newBookブック名/シート数新規ワークブック
width開始列番号/幅1/[幅2]/...セル幅指定、開始位置から右方向へ任意の数設定可能
height開始行番号/高さ1/[高さ2]/...セル高さ指定、開始位置から下方向へ任意の数設定可能
selectSheetシート番号シートを選択
nameSheetシート名シート名を設定
mergeSY/SX/EY/EXセルを結合
writeSY/SX/EY/EX/値1/[値2]...セルに値を書き込む※1
fontSizeSY/SX/EY/EX/サイズフォントサイズ設定
hAlignSY/SX/EY/EX/位置セル内水平位置設定※2
vAlignSY/SX/EY/EX/位置セル内垂直位置設定※2
bordersSY/SX/EY/EX/線スタイル枠線を引く(外枠・縦・横すべて)※2
numberFormatSY/SX/EY/EX/スタイルデータの書式設定※3
shrinkToFitSY/SX/EY/EX/TRUE or FALSEセルに合わせて文字を縮小
wrapTextSY/SX/EY/EX/TRUE or FALSEセル内テキスト折り返し
addIndentSY/SX/EY/EX/TRUE or FALSEセル内文字の前後にスペース
※1 指定した範囲のセル数だけデータを並べる。複数行の場合は水平方向優先。
※2 サイズ・位置・スタイルはxl定数名で指定(詳しくは(その3)参照)
※3 (#,###などセル書式で指定する形式)

 言語と呼ぶにはあまりに少ない命令数ですが、ワークシート生成に必要な手順を一つずつコマンドにしていったらこんな具合になりました。これぐらいでもちゃんと表が作れます。

 次回はExcel VBAで実際にこの力技EMLを実行できるようプログラムしていきます。
 つづく。

2017年7月20日木曜日

力技_02:FileMakerからExcelワークブックをつくる(その1)

Excel VS FileMaker


 「その仕事、まだExcelでやってるの?」
 FileMaker使いがよく言うセリフ第1位(嘘)ですが、ちょっと待った!

 ちょっとFileMakerを使いこなしてるからって、Excelのことを下に見ていませんか?
 それは実に失敬な。ここにおわすお方をどなたと心得る、何と言ってもExcelは日本で一番普及している「ドロー・ページレイアウト」アプリケーションですぞ!(結局バカにしてる?)
 こっからExcel方眼紙の功罪とか脇道にそれちゃうと二度と戻ってこられなくなるので冗談はこのぐらいにして、今回はFileMakerからExcelのワークシートを生成しようという話。

 「FileMakerはもともとExcelにエクスポートできるよ」とおっしゃるそこのあなた。確かにおっしゃる通り。FileMaker上のデータを標準機能でExcelにエクスポートすると、こんな感じのファイルが出力される。



 でも、さっきも言ったように「最強の文書作成ソフト」であるExcel様にこのエクスポート機能はあまりにも力不足。1行目をラベル行にできるぐらいしか設定項目もなく、上部中央にタイトル入れてくれるとか、罫線つけてくれるとか、なんにもなし。

 ただ、FileMakerの方にも肩入れしておくと、FileMakerのエクスポート機能はデータ交換のための機能なので、これはこれで「清く正しいデータベース」のあるべき姿なのです。ただただデータだけシンプルに並べるのが大事なことで、余計な体裁づけはインポート先で手間を取らせることにもなりかねません。
 こうしてみるとFileMakerとExcelはそれぞれの根底にあるポリシーは全く違うのだなと改めて考えさせられます。まあ、Excelの方はポリシーというにはだいぶ節操ないですが、その強大無比なシェアのせいで極度の一般化を求められた結果、ノンポリシーというポリシーを背負わされたとも言えそうです。(これはMicrosoftがいい悪いとかじゃなくて、これだけ広く普及したものが世の中からどう捉えられ、どう使われたかという結果の問題だと思う)

なんだかんだExcel様は偉い


 さて、すっかり御託が長くなってしまいましたが、実務場面におけるFileMakerとExcelはというと…
 Excelで一般的にやられている事務作業って大概FileMakerでも実現できるし、印刷レイアウトなんかはむしろExcelより凝ったものが作れたりするのがFileMakerです。FileMaker関係のセミナーなどを受講しても「今までExcelでやってたのをFileMakerに移行して便利で楽にしようよ」なんて言われてその気になったりするわけですが…
「そこそこ体裁が整えられていて、編集もできるファイルが欲しいなあ」
なんて誰かに言われると、途端に困ってしまいます。

 世の中、".fmp12"なんて相手が見たこともないであろう拡張子のファイルを送っても、おそらく「何これ?」としか返ってこない。ならばと渾身のレイアウトできれいなPDFを作成しても「編集できねぇよ」と言われる始末。こういう時はやはり安心と実績の".xlsx"ですよね。

 そんなわけで、「FileMakerからそこそこ体裁の整ったExcelファイル」ってそこそこ需要あるんじゃないかなと思うのです。

 標準機能でエクスポートしてから、手作業で罫線引いたり先頭に行挿入してタイトルつけたり…なんてのもいいですが、ここは一発、力技でなんとかしてしまいましょう。

次回へ続く

2017年7月18日火曜日

力技_01:のび〜る帳票をつくる(その2)




【前回】からのつづき

まずはヨコ線

前回のガタガタレイアウト。

 上図を見ると「空行の削除」を指定したフィールドはデータが入っていないとき枠線も表示されないことがわかります。
 ならば、フィールドの枠線は使用せずに縦線・横線をそれぞれ引いた方がよさそうです。

表組みの罫線を縦横別々に考えると、横線の方はわりと簡単に引けそうです。
1)項目名は上辺・左辺・右辺を設定し、下辺はナシに(上図緑線)
2)ボディパート、フッタパートの最上部に直線ツールで横線を引く(上図赤線)
3)表の各フィールドは枠線ナシ、塗りつぶしナシに

 よしよし。横線だけならわりといい感じ。

問題はタテ線

ここからが力技。
 罫線の画像ファイルを作成してボディパートの塗りつぶしでイメージを選択。用意した罫線の画像を貼り込みます。(ここでは縦線を青色にしてみました)
 サイズ指定は「並べて表示」を選択して、イメージが縦方向に繰り返し表示されるようにします。
 これでボディパートの高さいっぱいに縦線が並びます。
 肝心なのは、その貼り込むイメージファイルです。
 この作例では72ppi・縦1ピクセルの画像の水平方向の必要な位置に点が打ってあるものを作りました。縦のピクセル数は複数にして縦線を引いてももちろんOK、うまく作れば点線などもできるはずです。
 画像の作り方はFileMaker以外の画像処理ソフトが必要になるので、お手持ちのソフトで各自自由研究! PhotoshopでもWindowsペイントでも作れます。
 インスペクタでフィールドの横幅を確認しながら数えて点を打つもよし、レイアウト画面のスクリーンショットを撮って加工するもよし。
 ただ、画像の解像度には注意してください。例えば72ppiで1ドット=1ポイントになります。0.5ポイント幅の縦線を引きたいときは144ppiの画像を用意する必要があります。

 こうして、できあがったのがこのとおり。


 よくできました。

 レイアウトを調整するときには画像の修正が必要になったりするので少々面倒ですが、いまのところはこうでもしないとこの手の帳票は作れないかなと思います。
 あと行けそうな方法としてはExcelに送って印刷かけるとかですかね?


サンプルダウンロード


2017年7月17日月曜日

力技_01:のび〜る帳票をつくる(その1)



可変長テキストを含むデータのリスト表示に悩む

たとえばこんなデータがあったとする。

月日天気参加者記事
06/01(木)晴れ鈴木特になし
06/02(金)くもり鈴木
田中
このぐらい長いテキストを入力するとFileMakerのフィールドの大きさによってはあふれてしまうこともある。
06/03(土)田中
小林
佐藤
FileMakerではテキストの長さに合わせてフィールドサイズを調整することが難しい。
そのため、罫線付の帳票レイアウトを作成しようとすると可変長テキストの処理に頭を痛めることになる。
今回、この問題を力技で解決することができるのか?
06/04(日)休日
06/05(月)晴れ鈴木
佐藤
きょうも特になし

 けっこうこんなデータを扱う機会はあるもので、例えば、旅費計算の経路や業務日報の記録、病院や福祉施設のケース記録などなど。画面上ならばフィールドの高さが固定でも「スクロールしながら見てよ」なんてお茶を濁すこともできなくはないが、印刷しなければならないこともあるはず。
 FileMakerって柔軟なレイアウト機能があるんでしょ?なんとかなるでしょ。となめてかかると結構ハマってしまうのであります。

レイアウト1:あふれる帳票

 まず、このデータを行の高さを1行分にして普通にリスト表示のレイアウトを作成してみると、こんな感じ。
1行におさまらないデータは全く見ることができないので、これを印刷してもまったく使い物にならない。

レイアウト2:もったいない帳票

 レイアウトで各フィールドの高さを大きくしてみた。
 フィールドの高さが固定であるため、記載の少ないところでは余白ができて非常にもったいない。しかも、一番長いテキストはこれでもあふれてしまっている。


テキストの長さに合わせてフィールドサイズを変えたい

フィールドの高さを可変にする方法がないかと探すと「空行の削除」というものがある。余白をカットして位置をスライド(詰める)さらにはレイアウトパートのサイズまで詰めてくれるというかなり期待が持てそうな機能。
 さっそく、フィールドの高さを充分大きめに取り、各フィールドに「空行の削除」を設定すると…

レイアウト3:ガタガタな帳票

なんじゃあこりゃぁ〜!

 長文あふれと余白の問題はクリアできたが、「空行の削除」はフィールドごとに別々に処理されるため、同じ日付の行でもサイズがまちまちに。
 これでは、一覧表に見えない…。
 FileMakerでまともな表を作るにはどうも苦労させられる。この辺のデザインの融通が利かないのは、どうやら枠線で囲まれた表を作る習慣が欧米にはあまりないという事情もあるらしい。

 なんとかそれっぽい表を出力する事はできないものか、果たして思い通りの印刷レイアウトを作ることはできるのか。
 後半へつづく。