2024/04/25

DXL Step-by-Step:#32)表の作成と行・列の間隔

DXL 活用の調査・検証で、実現できたことや発見したことご紹介する『DXL Step-by-Step』シリーズの第 32 回です。前回から ”表” の構造について整理しています。

今回は、実際に LotusScript で表を作成してみます。そして、LotusScript の NotesRichText*** クラスでは操作できない行の間隔と列の間隔を設定します。


メインルーチン

#27)イメージリソースの表示』で作成したエージェントをコピペして修正する前提で記載します。記載していない部分のプログラムについては変更はありません。

まずは、DXL を作成するメインルーチン xSetDXL です。表を作成する関数をコールしています。また、表の前後には段落が必要となりますので、同じ処理となりますので、関数化しています(関数については後述)。

Function xSetDXL(vdprs As NotesDOMParser)
         ・・・
   Dim denTbl As NotesDOMElementNode
         ・・・
   'リッチテキスト作成
   Set den = ddn.CreateElementNode("richtext")
   Set denRT = denItem.AppendChild(den)

   '段落定義
   Call xSetDXL_pardef(ddn, denRT)


   '表前の段落追加
    Call xSetDXL_par(ddn, denRT)

   '表の追加
    Set denTbl = xSetDXL_table(ddn, denRT, 3, 2)

   '表後の段落追加
    Call xSetDXL_par(ddn, denRT)
 End Function


表の作成

xSetDXL_table 関数では、新しい表を作成しリッチテキストに追加します。引数で作成する表の行数と列数を指定できるようにしています。前回確認した通り、表のノードは複数の階層で構造化されていました。以下のリストでは階層がわかるよう変数の文字色を変えています。

Function xSetDXL_table(_
            vddn As NotesDOMDocumentNode, _
            vdenRT As NotesDOMElementNode, _
            ByVal viRows As Integer, ByVal viCols As Integer _
            ) As NotesDOMElementNode
   Dim denTbl As NotesDOMElementNode
   Dim denRow As NotesDOMElementNode
   Dim den As NotesDOMElementNode
   Dim iCol As Integer
   Dim iRow As Integer

   '表の作成
   Set den = vddn.CreateElementNode("table")
   Set denTbl = vdenRT.AppendChild(den)

   '列定義の作成
   For iCol = 1 To viCols
      Set den = vddn.CreateElementNode("tablecolumn")
      Call denTbl.AppendChild(den)
   Next

   '行の作成
   For iRow = 1 To viRows
      Set den = vddn.CreateElementNode("tablerow")
      Set denRow = denTbl.AppendChild(den)

      'セルの作成
      For iCol = 1 To viCols
         Set den = vddn.CreateElementNode("tablecell")
         Call denRow.AppendChild(den)
      Next
   Next

   Set xSetDXL_table = denTbl
End Function


段落の作成

xSetDXL_par 関数は表の前後に配置する空の段落を作成します。par ノードを作成し、リッチテキストに追加するだけのシンプルなプログラムとなっています。

Function xSetDXL_par(_
            vddn As NotesDOMDocumentNode, _
            vdenRT As NotesDOMElementNode _
            ) As NotesDOMElementNode
   Dim den As NotesDOMElementNode

   '段落の作成
   Set den = vddn.CreateElementNode("par")
   Call den.SetAttribute("def", "1")
   Set xSetDXL_par = vdenRT.AppendChild(den)
End Function


ここまでのエージェントを実行すると、3 行 2 列の素気のない表が作成されます。


行・列の間隔

それではこの表に行の間隔と列の間隔をセットしてみましょう。プロパティの以下の設定です。


この設定は table ノードの次の属性で設定します。

属性 設定値 補足
columnspacing '0.0591in' など 列の間隔(セル内側の左右余白)
rowspacing '0.0591in' など 行の間隔(セル内側の上下余白)


間隔を設定する関数を新規に作成します。

引数で渡された table ノードに対して SetAttribute メソッドで属性をセットしています。

Function xSetDXL_table_spcing( _
            vdenTbl As NotesDOMElementNode, _
            ByVal vdRowSpan As Double, ByVal vdColSpan As Double)
   Dim s As String

   '行の間隔
   s = DXL_CMToInch(vdRowSpan)
   Call vdenTbl.SetAttribute("rowspacing", s)

   '列の間隔
   s = DXL_CMToInch(vdColSpan)
   Call vdenTbl.SetAttribute("columnspacing", s)
End Function

ポイントは、設定の単位がインチであることです。引数で指定されたセンチメートルの値を #17)段落の定義② 余白の設定 で作成した関数 DXL_CMToInch でインチの文字列に変換しています。


メインルーチンには以下の行を追加し、行の間隔を 0.2 cm、列の間隔を 0.5 cm に設定します。

         ・・・

   '表の追加
   Set denTbl = xSetDXL_table(ddn, denRT, 3, 2)

   '間隔の設定
   Call xSetDXL_table_spcing(denTbl, 0.2, 0.5)

   '表後の段落追加
   Call xSetDXL_par(ddn, denRT)
End Function


実行結果

できあがったプログラムを実行すると行列の間隔を設定した表が作成されます。

ちなみに、この処理で作成される DXL は次の通りとなります。


前回 DXL Step-by-Step


2024/04/22

DXL Step-by-Step:#31)表の基本構造

DXL 活用の調査・検証で、実現できたことや発見したことご紹介する『DXL Step-by-Step』シリーズの第 31 回です。今回からは ”表” の構造について調査した結果をまとめます。

LotusSctipt の NotesRichText*** クラスでは、表は作成できるのですが、細かなプロパティの操作はできないものが多いです。DXL を使えばほとんどの操作(たぶん全部?)が可能となりますので、できることが一気に広がりますよ。


表の作成位置

第 15 回 でリッチテキストフィールドを表す richtext ノード直下には、次の 4 種のノードが配置できると紹介しました。

pardef 段落の定義
par 段落
table
section セクション

前回まで "段落" やその中に配置できるオブジェクトに関してでしたが、今回からまとめる "表" は段落と並列に配置される table ノードとなります。


表の基本構造

まずは table ノードの周辺の仕様を確認します。

以下の DXL はリッチテキストに 3 行 2 列の単純な表を作成し、出力した結果です(一部省略して表示しています)。

まず richtext ノードに段落定義を表す pardef ノードがあり、それに並列に table ノードが配置されています。上表の通りですね。

また、table ノードの前後には段落を表す par ノードが存在します。いろいろと検証した結果、このノードがないと表を正しく表示できない”仕様”のようです。特に上側の par ノードがないと DXL と保存(Import)できませんでした。

リッチテキストに表を作成すると前後の段落が作成されるので、この動作を踏襲しているのだと思います。


表の中身

続いて table ノードの中の構造を確認します。

まず、表の列の定義があります。tablecolumn ノードが 1 列分を表します。今回の表は 2 列だったので、tablecolumn ノードも 2 つ存在します。

列定義に続いて、行を表す tablerow ノードが存在します。今回は 3 行存在するということですね。


行の中身

行である tablerow ノードの配下には tablecell ノードが列数分だけ配置されています。

tablecell ノード内には、段落を表す par ノードが存在します。表のセルは、richtext ノードと同様の機能を持つ仕様になっています。これを利用して表内にさまざまなコンテンツを配置できます(詳細は別の機会にまとめます)。


まとめ

今回は表の基本構造についてまとめました。HTML の表と近い構造ですので、理解しやすいかと思います。

次回からは、表の細かな設定についてまとめます。表には多種多様な機能・設定が存在します。検証できたものから順に紹介します。


前回 DXL Step-by-Step


2024/04/16

作ってみよう:#5)お小遣い帳 - Nomad 用フォームの作成

前回に引き続き、Nmad Mobile で使いやすい修正を加える作業を行います。今回の対象はフォームです。そのままの状態だと情報量の割に隙間が多く、画面を有効に使えていません。

この改善を含めて、Nomad 専用のフォームを追加します。


左右余白の削除とスクロールの停止

既存のフォームをコピー&ペーストして、Nomad 用フォームとして仕上げます。

まずは、フォームの左右に隙間をなくし、画面を有効活用します。これには、表の幅の設定を「ウィンドウに合わせる」に変更すると対応できます。

ただ、スクロールの発生に合わせて、上下だけでなく左右にまでスクロールできてしまいます。これだと使いずらく、結果的に状況は悪化しています。

今回のケースでは、フレームセットでスクロールを止める方法で対処します。


事前にフレームセット mfsExpence を作成します(モバイル用の設計要素には m を先頭についけています)。フレームは1つだけにして、名称を mfrmForm とし、スクロールを停止します。

Nomad 用フォーム側では、自動フレームの設定で、先ほど作成したフレームセットを指定します。


上下の余白削除とレイアウトの変更

タイトル行(”支出”と表示しているグレーの帯)の上下のスペースが無駄なので詰めます。また、スペースの有効活用とスマホ用の対応として次の修正を行います。

  • 表の上下を非表示に変更
  • 金額と日付を横並びに配置
  • 表の連結は不意におかしな動作をすることがあるので、表を項目ごとに分離
  • 各表の間のスペースは非表示
  • キーボードを使用するメモ欄を上に移動
  • 罫線が主張しすぎるので、色を薄くし、下側のみ表示
  • フォントを少し大きく

また、画面に隙間が空くなど美しくなかったので次の調整を行いました。

  • タイトル行の 行の間隔: 0 cm
  • 明細行の 行の間隔: 0.3 cm


リストボックスの利用

ダイアログリストの[▼]ボタンは少し小さいですね。また、タップ数も増えますので、リストボックスを採用し直接的な入力に変更します。

フィールドの種類をリストボックスに変更し、サイズを入力すると設定できます。また、列を追加してフィールドを横並びにして、操作しやすくしてみました。


なお、場所の項目は将来的に Google Map と接続する予定なので、そのままとしています。


フォーム名の設定

このフォームはノーツ用とは別の”スマホ用”ですので、次の通りフォーム名を設定します。

   m.支出 | mfExpence | fExpence

別名が 2 つ存在します。ノーツでは別名が複数あるフォームで文書を保存した場合、フォーム名が記録される Form フィールドには最後の別名がセットされます。

これを利用して、スマホから文書を作成する場合、このフォームで開くようにしています(後述)。また、Nomad 用のビューから開いた場合もこのフォームで開くようにフォーム式を設定します。


この設定により、環境に応じたフォームで表示するアプリが作成できます。Form フィールド的には 1 種類となりますので、ノーツとハイブリッド運用でも、既存環境への影響を低減できますね。


新規作成ボタン

文書を作成するためのボタンを配置します。ボタンを作成するフォームは前回作成した埋め込みビューを配置しフォーム mfViewByMon です。

ボタン
閉じる @Command([FileCloseWindow])
新規作成 @Command([Compose]; "mfExpence")

前述の通り、新規作成時のフォーム名が "mfExpence" となっているのがポイントです。



動作確認

これで、スマホ用の対応はいったん完了です。Nomad Mobile からアクセスして動作確認を行います。多少の改造は、必要ですが、見た目や操作性はよくなったと思います。


最後に

今回採用したフォームのスクロール停止方法では、縦方向のスクロールもできなくなります。フォーム内に項目が多くスクロールが必要になる場合には利用できませんので、ご注意ください。

フレームに横方向のスクロールだけ止める機能があるといいんですけどね...


前回 作ってみよう


2024/04/14

作ってみよう:#4)お小遣い帳 - Nomad からのアクセスとビューの作成

前回までで、支出が入力ができるアプリになりました。最終的には入力はスマホから行う予定ですので、Nomad Mobile からアクセスしてみましょう。


まずは、そのままの状態で Nomad Mobile で開いてみます。

ノーツのアプリがそのままスマホで利用できるのは本当にすごいと思います。ただ、使いやすいか?と言われると、残念ながらそうではありません。PC は横長、スマホは縦長の画面ですから仕方ありませんね。Nomad はスマホを横持ちすると横画面になりますが、それでも、画面解像度の差もあり厳しいです。

スマホで使いやすくするためには、スマホに合った開発が必要ということですね。今回は、スマホからの利用を想定したアプリですので、専用の設計を追加することとします。


ビューの作成

画面構成として、左にナビ、右にビューという画面構成に無理があります。まずは、最初の画面がビューだけになるようにしたいと思います。次のようなイメージです。

単一カテゴリ機能を利用して、カテゴリの開閉が必要のないすっきりとした画面を作成します。


ビューの作成

先にビューを作成します。

フォームの埋め込みビューの単一カテゴリ機能を使用します。重要なのは表示するデータの判定に使用される 1 列目ですね。次のような式を記述して、年月度でカテゴライズします。

   xY := @Text(@Year(Date)) + "年";
   xM := @Right("0" + @Text(@Month(Date)); 2) + "月";
   xY + " " + xM


フォームの作成

続いて、下記のような新規フォーム mfViewByMon を作成します。

まず次の 3 つのフィールドを作成します。

フィールド 種類 詳細
SaveOptions テキスト このフォームは保存する必要がないので設定。
デフォルト値は "0"。常時非表示。
CategoryKey_Lst テキスト 表示用の計算結果。複数値。常時非表示。
CategoryKey コンボボックス 「ウィンドウに合わせる」表の中に配置。

その下に埋め込みビューを配置します。先に作成したビューを埋め込みビューとして配置しして、画面いっぱいに表示されるように設定します。

続いて「単一カテゴリの表示」にフィールド CategoryKey を指定します。これで、CategoryKey フィールドで指定した年月のデータだけがビューに表示されるようになります。


最後に選択肢の設定を行います。

CategoryKey_Lst は選択肢を保持するための一時フィールドです。表示用の計算結果で、下記の式を指定します。

   xLst := @DbColumn("":"NoCache"; "":""; "mvExpenceByDate"; 1);
   @If(@IsError(xLst); ""; xLst)

埋め込みビューの 1 列目をリストで取得しています。


次にコンボボックスの CategoryKey フィールドです。

選択肢はフィールドプロパティで「式で選択肢を設定」を指定、以下の式を設定します。

   @GetField(@ThisName + "_Lst")

フィールドのデフォルト値には以下の式に設定します。

   xLst := @GetField(@ThisName + "_Lst");
   @Subset(xLst; 1)

CategoryKey_Lst の値を使って、複数回の検索(@DbColumn)を行わないようにしています。


フレームセットの作成

画面構成の一番外側にあたる上下 2 分割のフレームセット mfsMain を作成します。

上側のフレーム mfrmHeader はアプリのタイトルを表示するだけのフレームです。作成手順は記載しませんが、@DbTitle でアプリ名称を表示するだけの単純なページを指定し、都合のいい高さで固定します。

下側のフレーム mfrmView には先ほど作成したフォーム mfViewByMon を配置します。

なお、各フレーム内のスクロールは止めておきましょう。


最後に、アプリケーションのプロパティで作成したフレームセットを開く設定をして完了です。

これで View まわりの開発は完了ですので、Nomad で確認します。次のようにすっきりした画面で表示されます。

※ 2024/4/15 加筆 アクションボタンは次回の作成です。


参考文献

Nomad アプリの作り方は、下記セミナー資料に参考にさせていただいております。

HCL Nomad Mobile アプリ開発セミナー

Nomad の挙動や設定テクニックなど、網羅的に記載されているので、ぜひご確認ください。私自身、今後もさまざなシーンでお世話になることになるかと思います。


前回 作ってみよう 次回


2024/04/11

関数のインターフェースと独立性

最近『チーム開発』のラベルを付けて、記事を書いています。中でも『変数や関数の宣言とスコープ』や『関数の独立性』など、作成するプログラムをシンプルにする方法を紹介しました。

今回もその一環で、関数のインターフェースという側面でまとめます。


引数とは?

関数には通常引数が存在しますよね?

引数の役割は、一般的には関数内に値を渡すことを目的としています。

例えば、ある整数までの合計を算出して結果を返す関数を考えます。次のようなコードになるかと思います。

Function xSum(viIndex As Integer) As Integer
   Dim i As Integer
   Dim iSum As Integer

   iSum = 0
   For i = 1 To viIndex
      iSum = iSum + i
   Next

   xSum = iSum
End Function

この関数をメインプログラム(コールする側)では、次のように記述します。

   iTotal = xSum(10)

このように記述することで、1 から 10 までの総和が関数で算出され、最終定期に iTotal に結果が代入されます。

このメインプログラムの 10 を関数側の viIndex で受け取ります。このように、値を受け渡しする機能を引数と言います。

関数の定義では ( ) 内に引数を定義します。

Function xSum(viIndex As Integer) As Integer

この機能を利用すると、関数は、 5 までの総和、50 までの総和でも計算できる汎用的な機能を持ち、再利用しやすい状態にできるということですね。値(引数)を渡すとそれに見合った結果を返す、”関数” と呼ばれる所以ですね。


戻り値

先ほどの関数の定義は以下のようになっていました。

Function xSum(viIndex As Integer) As Integer

xSum が関数名と言い、外部のプログラムからアクセスするための名前です。前述の通り ( ) 内が引数、As 以降が戻り値の定義です。”As Integer” で戻り値は Integer 型だと宣言しています。

関数内では関数名が変数のように振舞い、そこに値をセットすることで戻り値を決定します。

   xSum = iSum
End Function

そして関数を抜けることにより、メインプログラムに戻り値を返します。これで関数内の結果がメインプログラムに与えられます。

   iTotal = xSum(10)

このように、関数名は変数のように振舞います。こういった側面では、関数と変数の宣言に大差がないことがわかりますね。


パブリック変数を使ってみたら

例えば上記関数をパブリック変数を使用して作成することはもちろん可能です。

Function xSum() As Integer
   Dim i As Integer
   Dim iSum As Integer

   iSum = 0
   For i = 1 To piIndex
      iSum = iSum + i
   Next

   xSum = iSum
End Function

呼び出し側では次のように記述することになります。

   piIndes = 10
         ・・・
   iTotal = xSum()

このように関数とパブリック変数の値のセットが近ければまだ救われます。しかし、

   Call xCalc()
         ・・・
   iTotal = xSum()

と記述されていたとしましょう。

たまたま xCalc() 関数を調査すると piIndex にアクセスしていました。

Function xCalc()
         ・・・
   piIndes = iOpt + 5
         ・・・
End Function

ただ、iOpt って何だろう...。という風に、芋づる式に調査範囲が広がります。


インターフェース

IT の世界では、インターフェースという言葉を頻繁に使用します。別のシステムとを接続するためのやり取りをする窓口的な意味合いで使用します。

先ほど紹介した関数の世界では、引数と戻り値が ”関数のインターフェース” になります。

パブリック変数など広いスコープが存在しない前提の場合、引数と戻り値以外のインターフェースが存在しないことになります。この前提に立つと、関数の機能は引数以外に影響を受けることはありません。独立性の高い関数となり、非常に閉じた世界のわかりやすいプログラムであることがわかります。

もし、パブリック変数を使用していた場合、その変数もインターフェースとなります。関数に与える影響を測定するには、先ほどの事例のように、関数を利用している箇所だけでなく、その変数にアクセスしている部分をすべて洗い出す必要があります。

どちらがメンテナンス性が高いか明白ですよね?


チーム開発とインターフェース

上記の通り、引数と戻り値以外、関数に影響を与えないようにすることで、独立性を高め、プログラムをシンプルにできます。スコープの広い変数を使用しないだけで、インターフェースを明確にできます。こういった点に配慮するだけで、後任者に伝わりやすいプログラムになると思います。

インターフェースを絞ったコーディングは少しだけ手間がかかります。しかし、一時の手間をかけるだけで、後任者の調査工数を削減する効果がありますので、是非ともチャレンジしてください。前回、独立性に関するお話を、部分的にでも具体的に紹介できたかと思います。参考になれば幸いです。

2024/04/10

関数の独立性

先日投稿した『変数や関数の宣言とスコープ』のまとめとして『チーム開発を意識したプログラミングではスコープはできる限り絞った方が良いことになります』と記載しました。これは、何か変更が必要となった時の影響範囲が狭くなる効果があるからです。

LotusScript で開発を行うと、ほぼ必ずと言っていいほど関数を作成します。この関数作成においても影響範囲は小さい方がよいとされています。理由はスコープと同じですね。

情報処理試験のようなお堅い世界では、この影響範囲の度合いを ”独立性” といいます。今回はこの独立性という側面から、関数とはどうあるべきか考えたいと思います。

なお、記載に当たっては ”モジュール” という一般的な言葉を使用しています。LotusScript の場合であれば、関数やサブルーチンを思い浮かるとよいかと思います。


独立性とは

”断捨離” なんて言葉ありますが、無駄なものを切り捨てて身辺整理するとシンプルな生活が送れます。モジュールも同様で、無駄を削いで、必要以上のしがらみを持たないようにすればシンプルにできます。その度合いを表すのが ”独立性” です。

独立性を表す指標には 2 つ存在します。『モジュール強度』と『モジュール結合度』です。少し硬い話になりますが簡単にまとめます。

なお、説明は LotusScript 的に表現するため、私なりに意訳しております。正確な情報が必要な場合には、信頼あるサイトや専門の文献をご確認ください。


モジュール強度

機能的な関連の強さを表す指標です。

モジュール強度が ”強い” ほど独立性は ”高い” といえます。

独立性 モジュール強度
低い







高い
弱い







強い
暗号的強度 単純にいくつかの関数に分割した場合など関数の機能を明確にできない場合
論理的強度 関連する複数の機能をまとめて作ったモジュールで、1つのインターフェースで複数の機能を実現する場合
時間的強度 初期設定など特定のタイミングで実行されるが他の機能に関して弱い関連しか持たない場合
手順的強度 バッチ処理のように複数の機能を逐次実行するが、各機能間の関連性は弱い場合(業務仕様上の関連は持っている)
連絡的強度 ある機能の結果が次の機能の入力になる場合など、手順的強度に加えデータも関連する場合
情報的強度 同じ構造のデータを扱う複数機能を持ったモジュール
機能的強度 1つの固有な機能のみを持つモジュールで、モジュール内の全ての要素がその機能に関連している場合


モジュール結合度

データ的な関連を視点に表す指標です。

モジュール結合度が ”強い” ほど独立性は "低い" ことになります。

独立性 モジュール強度
低い







高い
弱い







強い
内容結合 他のモジュールの内容を直接参照する場合やモジュールの一部を共有する場合
ロータススクリプトでは見られない結合
共通結合 共通のメモリ領域を共有するような場合
ロータススクリプトでは見られない結合
外部結合 パブリック変数を使用し、モジュール間でデータを共有する場合
パブリック変数をどう使っているかわかりづらく、再利用しにくい
制御結合 モジュールの動作を決定する情報をインターフェース(引数)で渡す結合
引数により制御されるためモジュール間の関連性は高い
スタンプ
結合
パブリック変数でない同じデータ構造をインターフェースを介して共有する結合
ただし、直接必要としないデータ(関連する他のモジュールで利用するデータ)を含む場合
データ結合 モジュールで必要なデータのみをインターフェースとする結合
独立性がもっとも高く、再利用が容易


独立性を高めるためには

さんざん細かいことをのたまったあとにどうかと思うのですが、上記の指標は事細かに記憶する必要はありません。「こんな風に作ると独立性は下がるんだ」という事例のようなイメージでとらえていただければと思います。

独立性を高めるポイントを端的に言うと、

  1. できる限り単機能にする
  2. 必要なデータのみで作る

となります。

関数の作成という視点では、単機能な関数を作成して、処理に必要なデータだけを引数で渡すということになります。


チーム開発と独立性

独立性を高めるとプログラムがシンプルになり、理解しやすくなりそうだということは、容易に想像できます。ただ、”うんちく” じゃなく ”使える” ネタが欲しいと思いますよね。

誤解を恐れずに言えば、次のような点を意識して開発すればよいかと思います。

◇ 関数は 1 画面に収める

一つの関数が複数の機能を持つと得てしてコードが長くなります。1画面に収まらない長さになった場合、複数の機能がないか確認し、分割を検討してみましょう。

単純な関数であればあるほど、変数の数も減りますし、機能を誤解なく表現できます。そして、後任者には、より明確に意図が伝わります。

◇ 関数の集合で機能を実現

単機能な関数ができたら、複数まとめて機能を実現する関数を作ります。手順的強度や連絡的強度の関数ということですね。

こういった関数は、処理の羅列となり、処理の流れがとらえやすく、わかりやすいプログラムになります。


ノーツ的な側面では、私は次のような点を注意して開発するようにしています。

◇ 外部結合に注意

notes.ini の環境変数や文書のフィールドなどの操作はパブリック変数のようなものです。関数の奥の方でこれらにアクセスしていると独立性は低くなると考えています。できる限り、外側で値を取得して、関数には値を渡すようにしましょう。

◇ 処理をそろえる

LotusScript では、文書を取得して、何らかの処理を加えて、保存するというような処理を書くことが多いですが、文書の取得と保存を同じ関数内に記述しましょう。こうすれば、取得から保存までの間のコードは ”文書に何らかの処理を加えているだけ” にでき、将来の改造にも強くなるといえます。これも一種の独立性だと思います。

改造を繰り返すアプリでは、一連の処理で何度も保存を繰り返しているのを見ることがあります。レスポンスの懸念以上に、作成者や読者などの権限系のバグにつながる可能性がありますので注意しましょう。


プログラミングは言語ですから正解は一つではありません。回りくどい表現であっても論理的に正しければ、正しい結果を得ることができます。とはいっても、わかりにくい表現、誤解を招くような表現はよくないですよね。特に、ビジネスやチームでの開発では、シンプルでストレートに伝わるコーディングを心がけることが重要です。


今回は最近の潮流であるノーコード、ローコードとは逆行するネタでした。独立性なんて意識せず ”使える” アプリができる時代がくればいいのですが、もう少し先ではないかと思います。現時点では、少し込み入った機能を作る際には必要な知識でしょう。

独立性に思いを馳せるだけでも、ステップアップになると思いますよ。

2024/04/08

つないでみよう:#13)インボイス API で登録番号をチェック

WebAPI 連携日記の第 13 回です。今回はいよいよ国税庁のインボイス API に接続する機能をノーツに組み込みます。


まずは、完成イメージです。

【検索】ボタンをクリックすると、入力した登録番号でインボイス API に接続し、検索結果を入手します。ヒットするとそこから登録名称を取得して画面に確認メッセージを表示します。


スクリプトライブラリの作成

フォーム内で使用する LotusScript エディタは仕様が古く使いずらいので、メインのプログラムはスクリプトライブラリに作成します。新たに lsInvoice ライブラリを作成して、開発をはじめます。

メインルーチン GetInvoiceName は登録番号を引数にコールするとインボイス API 経由で名称を返す関数です。事前に入手したアプリケーション ID は定数宣言しています。

Option Declare
Private Const xAPPLICATION_ID = "????????"    'アプリケーション ID
Public Const pcsERROR = "ERROR"

Public Function GetInvoiceName(ByVal vsInvoiceNumber As String) As String
   Dim ns As New NotesSession
   Dim sURL As String
   Dim http As NotesHTTPRequest
   Dim jnav As NotesJSONNavigator

   On Error GoTo Proc_Err

   'HTTP リクエストの準備
   Set http = ns.CreateHTTPRequest()

   'URL 作成
   sURL = "https://web-api.invoice-kohyo.nta.go.jp/1/num"
   sURL = sURL & "?id=" & xAPPLICATION_ID
   sURL = sURL & "&number=" & vsInvoiceNumber
   sURL = sURL & "&type=21&history=0"

   'API コール
   http.PreferJSONNavigator = True
   Set jnav = http.Get(sURL)

   '1件目の登録名称を取得
   GetInvoiceName = xGetInvoiceName_Nth(jnav, 1)

Proc_Exit:
   Exit Function

Proc_Err:
   GetInvoiceName = pcsERROR
   Resume Proc_Exit
End Function

API コールは単純です。登録番号をアプリケーション ID を使って URL を完成させます。API のレスポンスは NotesJSONNavigator オブジェクトで受け取ります。


1 件目の登録名称を取得する処理はサブ関数化しています。

関数化にあたっては、2つ目の引数で何件目の結果を利用するかを指定できるようにしました。今回は 1 で固定なんですけどね...

Function xGetInvoiceName_Nth(vjnav As NotesJSONNavigator, ByVal viIndex As Integer) As String
   Dim iCount As Integer
   Dim je As NotesJSONElement
   Dim ja As NotesJSONArray
   Dim jobj As NotesJSONObject

   'レスポンスの件数を取得
   Set je = vjnav.GetElementByName("count")
   iCount = CInt(je.Value)

   'エラーチェック
   If iCount = 0 Then Exit Function
   If iCount < viIndex Then Exit Function

   '引数で指定したレスポンスを取得
   Set je = vjnav.GetElementByName("announcement")
   Set ja = je.Value
   Set je = ja.GetNthElement(viIndex)

   '登録名称を取得
   Set jobj = je.Value
   Set je = jobj.GetElementByName("name")
   xGetInvoiceName_Nth = je.Value
End Function

はじめにレスポンスの件数を取得して、ヒットしたのか、引数の指定は結果の件数を越えていないか判定しています。それ以降が処理の本体ですね。

レスポンスの JSON の構造は 前回 紹介しています。必要に応じてご確認ください。

処理では、まず announcement エレメントを取得します。この値は配列なので、NotesJSONArray の変数に代入し、メソッド GetNthElement で引数で指定した結果を取得します。これを NotesJSONObject の変数に代入しています。これは、 GetElementByName メソッドで name エレメントを取得するためです。

NotesJSON* のプログラムでは、うまく利用すれは見やすいコードが書けますが、代入が多く、データ構造を把握してからでないと処理内容が把握しにくそうです。コメントを添えてカバーしておくべき点ですね。


エラー処理

インボイス API では、入力した登録番号の桁数が足りないと API コール時にエラーが発生します。

   Set jnav = http.Get(sURL)     ’ ← ここでエラーが発生

そこで GetInvoiceName 関数にはエラー処理を設定しています。エラーを検出したら処理を Proc_Err に飛ばし、戻り値に定数宣言済みの pcsERROR をセットして関数を抜けます。これで呼び出し元にエラーを伝えます。

Public Const pcsERROR = "ERROR"
Public Function GetInvoiceName(ByVal vsInvoiceNumber As String) As String
  ・・・
   On Error GoTo Proc_Err
  ・・・
   '1件目の登録名称を取得
   GetInvoiceName = xGetInvoiceName_Nth(jnav, 1)
  ・・・

Proc_Exit:
   Exit Function

Proc_Err:
   GetInvoiceName = pcsERROR
   Resume Proc_Exit
End Function


フォームの作成

テスト用のフォームを作成します。

登録番号と登録名称のフィールドを編集可能フィールドで作成し、事前に作成したスクリプトライブラリを呼び出します。

[検索]ボタンのコードは以下の通りです。

はじめに入力した登録番号から名称を取得しています。

取得できた場合は、確認メッセージを表示します。[はい]で了承したときのみ bOk に True をセットします。

Sub Click(Source As Button)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim sName As String
   Dim iMsg As Integer
   Dim sMsg As String
   Dim bOk As Boolean

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   'API に接続し登録名称を取得
   sName = GetInvoiceName(nd.Number(0))

   '結果の確認
   bOK = False
   If sName = "" Then
      sMsg = "一致する登録情報はありませんでした。" & Chr(10)
   Elseif sName = pcsERROR Then
      sMsg = "API 接続でエラーが発生しました。" & Chr(10) & "桁数など"
   Else
      iMsg = Messagebox("『" & sName & "』で正しいですか?", 36)
      If iMsg = 6 Then bOk = True
   End If

   '結果の処理
   If bOk Then
      '登録名称に反映
      Call nuid.GotoField("Name")
      Call nuid.FieldSetText("Name", sName)
   Else
      '登録番号入力の継続
      Call nuid.GotoField("Number")
      Call nuid.SelectAll
      sMsg = sMsg & "登録番号を確認してください。"
      Msgbox sMsg, 16
   End If
End Sub

結果の処理は bOk により分岐しています。

True では、取得した登録名称をフィールドに代入し、そこにカーソルを移動しています。

False の場合は、入力を継続することになるので、カーソルを登録番号に移動しています。また、再入力を強調するために、入力した番号を選択(反転)状態にしてから、メッセージを表示しています。


前回 連載:つないでみよう


2024/04/07

つないでみよう:#12)インボイス API の仕様

WebAPI 連携日記の第 12 回です。今回は国税庁のインボイス API の仕様を確認します。


API のドキュメント

まずは、API の仕様について確認します。詳細な仕様は PDF ファイルで提供されています。

Web-API機能のリクエストの設定方法及び提供データの内容について(Ver.1.0)


まとめると次の通りです。


リクエスト方法

まずは、WebAPI に接続するための URL は次の通りです。

https://web-api.invoice-kohyo.nta.go.jp/1/num?parameters

? 以降には、検索条件となるパラメータを指定します(後述)。使用する HTTP のメソッドは GET で、検索条件をすべて URL に記述するパターンとなります。

なお、URL 内の ”1” はバージョンで、現時点では 1 しか存在しません。


パラメータ

インボイス API で使用するパラメータをまとめます。

id アプリケーション ID
number 検索する登録番号(T + 13 桁の数字)
type 応答形式
 01:CSV形式
 11:XML形式
 21:JSON形式
history 履歴情報要否
 0:なし
 1:あり


API のテスト

リクエスト方法がわかったので、応答形式は JSON、履歴情報はなしでテストします。  Postman に次の通り、HTTP のメソッド、URL パラメータを入力します。登録番号はいつもお世話になっている ”鳥貴族” を使用しました。

実行すると、すぐレスポンスが返ってきました。API のテストツールは本当に便利ですね。


参考までにこのテストの URL は次の通りとなりました。これをプログラムで生成して送信すればいいというわけですね。

https://web-api.invoice-kohyo.nta.go.jp/1/num?
                id=xxxxxxxxxxxxx&
                number=T9120001011712&
                type=21&
                history=0


レスポンスのフォーマット

ドキュメントには、JSON 形式のレスポンスは下図の通り記載されていました。ヘッダ情報にヒット件数が入っています。公開情報セクションがヒットした情報の詳細で、件数分の配列となる構造となっています。

今回は、登録番号を使っての検索ですので、結果が 0 件の場合は入力ミスと言えます。ヒットした場合、名称を確認して正しいかを判断すればよいということですね。


前回 連載:つないでみよう 次回


2024/04/06

つないでみよう:#11)国税庁の WebAPI と利用登録

WebAPI 連携日記の第 11 回です。『つないでみよう』シリーズの第 4 のネタは、国税庁です。

昨年、インボイス制度が始まり、さまざまな用紙に適格請求書発行事業者の登録番号(長いので以下「登録番号」といいます)が必要になり、ノーツのフォームにも項目が増えたことだと思います。

国税庁が WebAPI を公開していることを知ったので『適格請求書発行事業者公表システム』に接続して、ノーツでの入力で利用できないかチャレンジしたレポートです。


Web API の種類

まず、国税庁の WebAPI には次の 2 種類があります。

法人番号システム Web-API(法人番号 API)

適格請求書発行事業者公表システムWeb-API機能(インボイス API)

それぞれデザインや内容に微妙な差はあるものの記載内容は似通っています。2 種類あることが理解できるまで、なんでこのサイトこんなに標記ブレが多いんだろう...としばらく混乱してしまいました...


アプリケーション ID の申請

どちらの API も事前に利用登録が必要です。ただ、インボイス API のアプリケーション ID があれば、法人番号 API も利用できるとのことです。

申請方法は少し違いがあり、インボイス API の申請の方が少し手間がかかります。今回は、こちらを申請します。

上記 インボイス API のサイト内にある『アプリケーションID発行手続きの流れ』に従い申請します。インボイス API の申請の場合には『適格請求書発行事業者公表システムWeb-API機能アプリケーションID発行申請書』なる Excel シートの提出が別途必要となります。少々面倒なのですが、記述してメールで送付します。

今回はサンプルアプリで操作検証が目的です。その旨を正直に記載して申請しましたが、無事審査が通ったようです。

しばらくすると API キーがメールで届きました。


これで準備が整いました。次回は API に接続します。


前回 連載:つないでみよう 次回


2024/04/02

Notes - Excel 連携:#35)Excel で使用する単位と変換

Excel を利用しているとさまざまな単位が登場します。

例えば、セルの幅や高さを変更しようとすると次のように表示されます。

幅の方が広いのに小さな数字となっており、明らかに単位が違います。横にピクセル値が表示されていることからもわかりますね。

また、文字の大きさを指定する際にはポイントを使用します。マージンの設定画面では、センチメートルで指定しますが、VBA のプロパティではポイントで指定する仕様となっています。

PageSetup.LeftMargin プロパティ (Excel)


単位の関係と列幅の単位

Microsoft Learn のドキュメントを調査していると、ヒントを発見しました。Excel ではなく Publisher ではありますが、次のメソッドがあります。

Application.PixelsToPoints メソッド (Publisher)

Application.PointsToInches メソッド (Publisher)

このドキュメントによると以下の関係で変換できるとのことです。

  • 1 ピクセル = 0.75 ポイント
  • 1 インチ = 72 ピクセル

25 ピクセルは 18.75 ポイントになりますので、高さの単位は ポイントであることがわかります。

幅については、72 ピクセルですので 1 インチの幅なのでしょうが、8.38 の単位ははっきりしません。Google 先生に聞いてみると『Excel 標準のフォントサイズ(11 ポイント)の時、1 セルに入る「文字数」』だそうです。なんだかいい加減な単位ですね。

ためしに Excel のオプション画面から既定フォントを変更してみました。

その上で、同じように 72 ピクセルとなるよう幅を調整すると 6.50 となりました。この結果からこの単位は定量的な長さの単位ではないことがわかります。

プログラムで指定するときには、『デフォルトの環境を基準に作成し、必要に応じて調整する』ぐらいのおおらかな気持ちで開発する必要がありそうですね。


変換関数の準備

Notes -Excel 連携アプリを作成するには、この単位変換が必要となるケースが多々あります。そこで、変換関数を lsXls ライブラリに作成します。

対象とする単位はインチ、ポイント、ピクセルに加えてなじみのあるセンチメールの4種類です。ちなみに、

  • 1 インチ = 2.54 センチメートル

です。

これら単位を相互に変換できるようにすべての組み合わせで準備しておけば変換で困ることはないでしょう。


◇ インチ ⇔ センチメートル

Public Function CMToInch(ByVal vdCM As Double) As Single
   CMToInch = vdCM / 2.54
End Function

Public Function InchToCM(ByVal vdInch As Single) As Single
   InchToCM = vdInch * 2.54
End Function


◇ インチ ⇔ ポイント

Public Function InchToPoint(ByVal vdInch As Single) As Single
   InchToPoint = vdInch * 72
End Function

Public Function PointToInch(ByVal vdPoint As Single) As Single
   PointToInch = vdPoint / 72
End Function


◇ インチ ⇔ ピクセル

Public Function InchToPixcel(ByVal vdInch As Single) As Integer
   InchToPixcel = CInt(vdInch * 96)
End Function

Public Function PixcelToInch(ByVal viPixcel As Integer) As Single
   PixcelToInch = viPixcel / 96
End Function


◇ センチメートル ⇔ ポイント、ピクセル

上記関数を利用して、対センチメートルの関数を作成します。

Public Function CMToPoint(ByVal vdCM As Single) As Single
   CMToPoint = InchToPoint(CMToInch(vdCM))
End Function

Public Function CMToPixcel(ByVal vdCM As Single) As Single
   CMToPixcel = InchToPixcel(CMToInch(vdCM))
End Function

Public Function PointToCM(ByVal vdPoint As Single) As Single
   PointToCM = InchToCM(PointToInch(vdPoint))
End Function

Public Function PixcelToCM(ByVal viPixcel As Integer) As Single
   PixcelToCM = InchToCM(PixcelToInch(viPixcel))
End Function


◇ ポイント ⇔ ピクセル

Public Function PointToPixcel(ByVal vdPoint As Single) As Integer
   PointToPixcel = CInt(vdPoint / .75)
End Function

Public Function PixcelToPoint(ByVal viPixcel As Integer) As Single
   PixcelToPoint = viPixcel * .75
End Function


前回 Notes - Excel 連携