出直し!! ヘルプ

連載中

連載 終了

2024/10/31

ノーツ・しこく・フェスタ 2024:AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集

2024年 10月 25日にうどん(香川)県 高松市で開催された『ノーツ・しこく・フェスタ 2024』のレポートです。イベント本体はもちろん有意義なものでしたが、前後のエクストラツアーも充実しており、全方位で魅力的なイベントでした(結果的に 4 日間もうどん県に滞在...)。

イベントの全体像については、以下のブログを参照ください。一緒に参加した仲間がすでに公開してくれています。

ノーツコンソーシアムブログ:ノーツ・しこく・フェスタ2024 開催

Domino Lab:NSF 2024 エクストラツアー


私はこのイベントで、ミニセッションを担当しました。『AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集』というタイトルで ”一席” 設けさせていただきました。

生成 AI 実装事例ということで、OpenAI の gpt-4o の API と連動したノーツアプリを 4 本を一気に紹介させていただきました。

どんなアプリなのか、ざっくり紹介させていただきます。


1. AI じゃダメなんですか? - AI 文書仕分

文書作成者がタイトル、カテゴリ、要約を記入していると、その人の知識や経験、好みや文章力で統一感がなくなるという、文書管理のお悩みあるあるを AI で解決しようというサンプルです。

Notes/Domino に関する HCLSoftware Blog をコピペしてノーツ文書として保存し、勝手に事例にさせていただきました。もとの文書を AI に送信し、タイトル、カテゴリ、要約を生成させています。

ビューで比較すると効果は一目瞭然です。AI ってすごいですね。

カテゴリは、ドキュメントタイプ、テーマ/トピック、キーワード(タグ的なもの)の 3 種類生成しています。これらカテゴリを複数の人間が的確に行うのは不可能に近いと思います。

ビューのカテゴリ分けはノーツの特徴的な機能で大変便利です。それを活かすためにもカテゴリの適切な設定、平準化は重要ですよね。


2. AI が導く、次世代の学習体験 - AI Learning

eLearning のコンテンツって作成時間がかかる割に利用が一瞬だったり、さまざまなレベルの利用者を前提したコンテンツが難しかったりと、作成作業は非常に非効率ですよね。これを AI に助けてもらおうというサンプルです。

次のように学習のテーマと目標を入力し、学習をスタートするだけで、コンテンツは AI が生成してくれるというアプリケーションです。

生成されたコンテンツは、次の通り、体裁を整えたノーツ文書として提供されます。キーワードはリンクになっていて、クリックするその単語をより詳しく教えてくれます。

これを繰り返し、どんどん調査(学習)を繰り返すという仕組みです。生成されたコンテンツはノーツの返答文書として階層管理されます。返答文書のツリーをビューで確認すると学習の進捗がわかります。クリックしたキーワード別のビューでは用語集になり、全ユーザ分をキーワードでカテゴライズすると同じ悩み(学習課題)を持つ仲間を探したりできます。

当日のセッションでは解説しなかった技術的な補足を少しだけします。

コンテンツはすべて AI に生成させています。各セクションのタイトルや文章以外にリンクにすべきキーワードも AI 任せです。ノーツの文章として表現する(プログラムで利用する)ためにこれらが的確に判定できる必要があります。これを実現するために、gpt-4o で使用できる Structured Outputs という機能を利用しています。この機能は、AI の返答をあらかじめ指定したスキーマに従わせることができます。要は返答がこちらが指定する JSON フォーマットになるので、どの部分がタイトルで、キーワードは何かが後続のプログラムで簡単に判別できます。

ちなみに、コンテンツの生成は DXL を使用しています。フォントサイズや行間の設定はもちろん、リンクも生成しています。LotusScript で記述されたホットスポットのアクションで、その段落内の文章とともにクリックしたキーワードについて AI に問い合わせしており、連続性のあるコンテンツが生成されるようにしています。


3. 添付画像の説明を生成 - PicInsight

古いノーツで添付した文書はサムネイルが表示されません。また、XPages や LotusScript で添付した文書も同様です。こういった画像ファイルを AI に食べさせ画像に何が写っているか説明させようというものです。

この結果をビューに表示すると、文書や画像を開くことなく、どのような画像か判別できます。より詳細に確認したい画像だけ開けばいいので、便利ですね。


4. AI はとびきりの愛なんだよ! - 忖度メール AI(アイ)

こちらは AI Learning と同様、文章生成系のアプリです。メールの宛先となる方の性格や趣味・趣向と要件を入力すると、メールの文面を考えてくれるアプリです。

メールの作成条件を入力してから、文面作成を依頼します。

すると、次のように素晴らしい文章が生成されます。新しいバイクが欲しいという欲求をダイレクトに伝えることなく、買ってもいいかな?買ったほうがいいかな?と思わる文章になっていますね。私には書けない文章です...

文面の作成は何度も実行できますので、気に入った文書が出るまで繰り返すことができます。実用においては、相手との関係性などを考慮して、言葉尻や事実関係などの調整が必要かもしれませんが、話の流れや構成はそのまま利用できると思います。


まとめ

これまで、AI との連携はつながるかどうかの検証など、技術的な側面からだけ見ていました。今回のセッションはサンプルアプリの紹介がテーマだったので、利用シーンなどを考えながらアプリ開発を行い、AI 利用の可能性を大きく感じました。

AI は膨大な知識を持っています。API を利用するとその知識や情報量を利用したアプリが作成できます。知らないことはもちろんですが、自分にない発想で回答してくれます。新たな気づきが得られることに感動しました。

AI への問い合わせは、標準化された方法で問い合わせします(連載『つないでみよう』参照)。パターンが決まっているということは、応用が簡単ということです。今回紹介した 4 アプリとボツとなった 2 本を合わせて、6 DBを作成しました。9月 19日 の DominoHub Osaka が終わって本格的に作成したので、期間は約 1 ヶ月間でした。Notes/Domino が備える基本機能の高さもありますが、開発効率が高いと感じました。スピード感をもって対応できますね。

AI × Notes/Domino。これまでとは違う新しい世界、そしてその可能性を感じました。今後の展開が楽しみです!


最後に、今回のセッションはノーツコンソーシアム 大阪研でともに活動している 林哲司さん と共同で行いました。前半は私の事例紹介で、後半は林さんの『Notesアプリと生成AIを融合 生成AI実装のポイントと課題』でした。

AI 連係の実装については林さんのブログ『Notes開発者のためのXPagesデザインレシピ』も参考にしてください。

AI 関連の記事はこちら


2024/10/23

Notes - Excel 連携:#44)軸のフォントサイズ設定と PlotArea の調整

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。いよいよプロット領域を調整するコーディングを始めます。


軸のフォント調整の必要性

#42 で PlotArea の位置調整で無理な値を設定した場合、エラーもなく適当な値に設定されることを紹介しました。そして、PlotArea の Left や Width が指すのは、軸エリアを含んでいました。ということは、PlotArea の調整の前に軸エリアのサイズを把握しておく必要があります。

例えば軸エリアの幅は桁数だけでなく、フォントサイズにも依存します。そこで、Excel が自動で設定するフォントサイズではなく、明示的に指定することで、見た目の統一感と軸エリアのサイズを固定化を狙います。


軸のフォントサイズ設定

エージェントに以下のコードを追加して軸のフォントサイズを明示的に設定します。

Sub Initialize
         ・・・
   'Y 軸補助目盛線の設定
   oChart.Axes(xlValue).MinorUnit = 250
  
   '軸フォントサイズの指定
   oChart.Axes(xlValue).TickLabels.Font.Size = 10
   oChart.Axes(xlCategory).TickLabels.Font.Size = 10

  
   oXls.Visible = True
End Sub

軸のアクセスは #16#17 で紹介しました。Chart オブジェクトの Axes メソッド でアクセスするんでしたね。引数に xlValue を指定すると縦軸、xlCategory を指定すると横軸となります。

この Axes メソッド はヘルプでの説明が不明瞭です。戻り値には Object としか書いていません。Microsoft Learn の Excel 内を調査したところ軸に関する 2 つのオブジェクトが見つかりました。

Axes 指定したグラフ内のすべての Axis オブジェクトのコレクション
Axis グラフの 1 つの軸を表します

この結果より Chart オブジェクトの Axes メソッド で xlValue や xlCategory を指定することにより、Axis オブジェクトを返すと想定できます。Axis オブジェクトには、軸に関するさまざまなプロパティが存在し、TickLabels プロパティから取得する同名のオブジェクトの Font プロパティを利用して、フォントサイズがセットできます。

Axis.TickLabels プロパティ (Excel)

TickLabels.Font プロパティ (Excel)


PlotArea の調整

今回、作成しているグラフのサイズは 500 x 200 ポイントでした。

   'グラフの作成
   Set oShape = oSheet.Shapes.AddChart2(, 4, 200, 1, 500, 200)

このサイズに合わせて、グラフの配置を決定します。

横幅の設定は縦軸エリアを 50 ポイント、グラフエリアを 350 ポイント、残りをグラフタイトルや凡例エリアとします。

続いて高さについてです。軸ラベルの高さは文字サイズの 2 倍程度の幅が必要そうです。また、縦軸を見ると、一番上の縦軸ラベルが上にはみ出ています。文字がセンタリングされているので、軸のフォントサイズ 10 ポイントの半分です。これを考慮して、7 ポイント下に下げ、少し余裕を持たせることにします。以上の結果からプロット領域の高さは 70 ポイントとすればよさそうです。

これらの設定を行うプログラムは下記の通りです。いたってシンプルなコードなのですが、正常に動作させるために重要なポイントがあります。

一部は #42 で触れていますが、わかりますか? 

Sub Initialize
         ・・・
   '軸フォントサイズの指定
   oChart.Axes(xlValue).TickLabels.Font.Size = 10
   oChart.Axes(xlCategory).TickLabels.Font.Size = 10
  
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170


   oXls.Visible = True
End Sub

正解はプロパティを設定する順番です。

デフォルトで作成されるグラフは、横幅がエリア全体を使っていますが、高さはグラフタイトルと凡例が配置され、プロット領域は狭くなっています。

今回のように、プロット領域を右にずらそうとすると、先に幅を縮める必要があります。逆に高さは広げることになるので、先に上に移動しておかないと必要な高さに変更できないということですね。


実行結果

ここまでの修正で作成されるグラフは次のようになります。

プロット領域の配置はこれで OK ですね。次回は、ラベルの調整を行います。


前回 Notes - Excel 連携 次回


2024/10/22

Notes - Excel 連携:#43)作業前のコードの確認

前回は PlotArea のプロパティについて調査しましたので、今回からはその設定を行うコーディングを始めます。

今回以降の作業は『#18)グラフの目盛線』のプログラムをベースに行います。ただ、その記事から期間が空いていること、連載が長くなっていることから、いったん全体のコードを掲載します。

今回は、作業のスタート地点の確認だけの記事です。ご了承ください。


設計要素としては、エージェントとスクリプトライブラリとなります。


エージェント

内容は #18 と同じですが、#25 でスクリプトライブラリ名を lsXls に変更したので、その点だけ修正しています。

Option Declare
Use "lsXls"

Sub Initialize
   Dim oXls As Variant
   Dim oSheet As Variant
   Dim oShape As Variant
   Dim oChart As Variant

   'Excel の準備
   Set oXls = CreateObject("Excel.Application")
   Call oXls.Workbooks.Add
   Set oSheet = oXls.Workbooks(1).WorkSheets(1)

   'サンプルデータのセット
   Call xSetSampleData(oSheet)

   'グラフの作成
   Set oShape = oSheet.Shapes.AddChart2(, 4, 200, 1, 500, 200)
   Set oChart = oShape.Chart

   'データソースの指定
   Call oChart.SetSourceData(oSheet.Range("A:A, B:B"))

   'タイトルのセット
   oChart.ChartTitle.Text = "ユーザ数の推移"

   'Y 軸の設定
   oChart.Axes(xlValue).MinimumScale = 500
   oChart.Axes(xlValue).MaximumScale = 2000
   oChart.Axes(xlValue).MajorUnit = 500

   'X 軸の設定
   oChart.Axes(xlCategory).CategoryType = xlTimeScale
   oChart.Axes(xlCategory).MajorUnit = 7
   oChart.Axes(xlCategory).TickLabels.NumberFormatLocal = "m/d;@"

   '目盛線の追加
   Call oChart.SetElement(msoCategoryGridLinesMajor)
   Call oChart.SetElement(msoValueGridLinesMinorMajor)
   Call oChart.SetElement(msoCategoryGridLinesMinorMajor)

   'Y 軸補助目盛線の設定
   oChart.Axes(xlValue).MinorUnit = 250
  
   oXls.Visible = True
End Sub

Function xSetSampleData(voSheet As Variant)
   Dim i As Integer
   Dim vDT As Variant
   Dim iUsr As Integer
   Dim iVal As Integer
   Dim iMax As Integer

   '列フォーマットの設定
   voSheet.Columns(1).NumberFormatLocal = "yyyy/m/d"
   voSheet.Columns(2).NumberFormatLocal = "#,##0_ "
   voSheet.Columns(3).NumberFormatLocal = "#,##0_ "

   'ヘッダ行のフォーマット設定
   voSheet.Rows(1).NumberFormatLocal = "@"

   'カラム名称の設定(ヘッダ行)
   voSheet.Cells(1, 1).Value = "日付"
   voSheet.Cells(1, 2).Value = "ユーザ数"
   voSheet.Cells(1, 3).Value = "増減"

   'サンプルデータの設定
   Randomize
   iMax = 30
   vDT = Today - iMax
   iUsr = 1000

   For i = 2 To iMax + 1
      vDT = vDT + 1
      iVal = Int(Rnd()*100) - 30
      iUsr = iUsr + iVal
      voSheet.Cells(i, 1).Value = vDT
      voSheet.Cells(i, 2).Value = iUsr
      voSheet.Cells(i, 3).Value = iVal
   Next
End Function


スクリプトライブラリ

スクリプトライブラリ lsXls は #18 より後に掲載した内容も含め、現時点での最新の状態としています。少し長くなりますが、下記の通りです。

Option Declare

'XlAxisType 列挙 (Excel)
Public Const xlCategory = 1
Public Const xlValue = 2

'XlCategoryType 列挙 (Excel)
Public Const xlAutomaticScale = -4105
Public Const xlCategoryScale = 2
Public Const xlTimeScale = 3

'MsoChartElementType 列挙 (Excel)
Public Const msoCategoryGridLinesMajor = 334
Public Const msoValueGridLinesMinorMajor = 331
Public Const msoCategoryGridLinesMinorMajor = 335

'XlBordersIndex 列挙 (Excel)
Public Const xlEdgeLeft = 7 '範囲の左側の罫線
Public Const xlEdgeTop = 8 '範囲の上側の罫線
Public Const xlEdgeBottom = 9 '範囲の下側の罫線
Public Const xlEdgeRight = 10 '範囲の右側の罫線
Public Const xlInsideVertical = 11 '範囲の外側を除くすべての垂直罫線
Public Const xlInsideHorizontal = 12 '範囲の外側を除くすべての水平罫線

'XlBorderWeight 列挙 (Excel)
Public Const xlHairline = 1 '細線 (最も細い罫線)
Public Const xlMedium = -4138 '普通
Public Const xlThin = 2 '極細
Public Const xlThick = 4 '太線 (最も太い罫線)

'XlLineStyle 列挙 (Excel)
Public Const xlContinuous = 1 '実線
Public Const xlDouble = -4119 '2 本線
Public Const xlLineStyleNone = -4142 'なし
Public Const xlDash = -4115 '破線

'XlHAlign 列挙 (Excel)
Public Const xlHAlignLeft = -4131 '左揃え
Public Const xlHAlignCenter = -4108 '中央揃え
Public Const xlHAlignRight = -4152 '右揃え
Public Const xlHAlignGeneral = 1 'データの種類に従って揃える

'XlPaperSize 列挙 (Excel)
Public Const xlPaperA3 = 8
Public Const xlPaperA4 = 9
Public Const xlPaperB4 = 12
Public Const xlPaperB5 = 13

'XlPageOrientation 列挙 (Excel)
Public Const xlPortrait = 1 '縦モード
Public Const xlLandscape = 2 '横モード

'XlFixedFormatType 列挙
Public Const xlTypePDF = 0
Public Const xlTypeXPS = 1

'MsoAutoShapeType 列挙 (Office)
Public Const msoShapeOval = 9 '楕円

'MsoTextOrientation 列挙 (Office)
Public Const msoTextOrientationHorizontal = 1 '横方向

'MsoVerticalAnchor 列挙 (Office)
Public Const msoAnchorMiddle = 3 '垂直方向に中央揃え

'MsoParagraphAlignment 列挙 (Office)
Public Const msoAlignCenter = 2 '中央揃え

Public Function GetRangeString(_
         ByVal viRowFm As Integer, ByVal viColFm As Integer, _
         ByVal viRowTo As Integer, ByVal viColTo As Integer) As String
   Dim s As String

   s = RCToA1(viRowFm, viColFm)
   s = s & ":" & RCToA1(viRowTo, viColTo)

   GetRangeString = s
End Function

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

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

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

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

Public Function RGB(ByVal vbyR As Byte, ByVal vbyG As Byte, ByVal vbyB As Byte) As Long
   RGB = vbyR + CLng(vbyG) * 256 + CLng(vbyB) * 256 ^ 2
End Function

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

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

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

Function x9ToA(ByVal viColNumber As Integer) As String
   Dim i As Integer
   Dim i1 As Integer
   Dim i2 As Integer
   Dim s As String

   i = viColNumber - 1
   i1 = (i Mod 26) + 1
   i2 = Int(i / 26)

   If i2 > 0 Then s = Chr(64 + i2)
   s = s & Chr(64 + i1)

   x9ToA = s
End Function

Public Function CCToAA(ByVal viColFm As Integer, ByVal viColTo As Integer) As String
   CCToAA = x9ToA(viColFm) & ":" & x9ToA(viColTo)
End Function

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

Public Function RCToA1(ByVal viRow As Integer, ByVal viCol As Integer) As String
   RCToA1 = x9ToA(viCol) & CStr(viRow)
End Function

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

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

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

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


前回 Notes - Excel 連携 次回


2024/10/21

Notes - Excel 連携:#42)PlotArea とサイズ

前回から、”使える”グラフにするための操作をまとめています。今回は具体的なコーディングに入る前に、グラフの作画エリアの挙動を確認します。


作画エリアの操作

Excel のグラフを作画しているエリアは VBA で PlotArea オブジェクトとして管理されています。オブジェクトの取得は Chart オブジェクトの PlotArea プロパティから取得します。

Chart.PlotArea プロパティ (Excel)

PlotArea オブジェクト (Excel)


作画エリアのサイズ調整は PlotArea オブジェクトのプロパティから行います。上記リンク先を眺めるとサイズにかかわるプロパティが 2 セット存在します。

Height InsideHeight
Left InsideLeft
Top InsideTop
Width InsideWidth

それぞれの違いについて理解する必要がありそうです。ヘルプを確認すると、次の通り記載されていました。グラフ領域とグラフの端、プロット領域の違いが不明瞭ですね...

Left グラフ領域の左端までの距離
InsideLeft グラフの端からプロット領域の左端までの距離


Left と InsideLeft の違い

ヘルプだけではよくわからないので、各プロパティの値をメッセージボックスで確認します。あわせて、軸エリアの幅も表示させてみました(以下の画像は合成して作成)。

InsideLeft がおおよそ 24、軸エリアの幅が 23 となっています。この結果より、InsideLeft には軸エリアを含んだ値であることがわかりますので、Inside* プロパティが指し示すエリアは下図の赤枠の部分で、ヘルプで言う ”プロット領域” だと推察できます。

Left や Width が指すのは、軸エリアを含む緑枠のエリアのようです。ただ、今一つ不明瞭なので点線としています。

少なくともX軸とY軸の交点を固定するためには、Inside* プロパティを使えはできそうですね。


幅の操作

プロット領域の位置を調整するテストをしていて設定どおりにならないことが、何度もありました。これは位置の設定はグラフ領域を超えないからです。

例えば、事例に上げているグラフでは、グラフ領域はほぼエリアいっぱいに設定されています。この状態で Left プロパティに 100 をセットしても、実行後の Left の値は 18 となりました。デフォルトの状態で右側にあったマージンの分だけ、右側に移動したということだと思います。

このように範囲外の数値を設定した場合、エラーも発生させず、適当な値に設定されます。希望通り配置できないことがあれば、各プロパティの値を表示させ確認しないと、バグなのか計算間違いなのか判定しづらくなります。

私は、通常 Pixel で考えるのですが、これらプロパティの単位はすべて Point ととなります。これも、状況を複雑にさせる要因ですね。Excel の単位については、以前まとめたので以下のリンクをご確認ください。

#35)Excel で使用する単位と変換


位置合わせの謎

次の実験は、Inside* プロパティで位置をセットしたグラフを画像にした場合、希望通りの位置に配置されるかを確認します。

テストとしては、InsideWidth = 400 にセットしてから、InsideLeft = 50 にセットします。グラフ領域のサイズが 500 なので、中央に配置する算段です。

その結果は次の通りでした。

いい感じなのですが、微妙に右にずれているような気がします。そこで、このグラフをペイントにコピペして、サイズをチェックします。

すると次の通りでした。

やはり右にずれていますね。プロット領域の 535 ピクセル は 401.25 ポイントなので、おおむね指定したサイズになっています。左の 72 ピクセルは 54 ポイントとなり指定通りとなっていません。

試しに、設定後のプロパティを表示したところ、50 ポイントとなっています。

前述のテストで不明瞭だった Left の値は約 27 となっており、軸エリアの幅を足すと InsideLeft と合致します。よって、Left に起因する問題ではなさそうです。


現時点では残念ながら原因は不明でした。少し右にずれるという点を認識して先に進めます。原因が判明したら別途レポートさせていただきます。


まとめ

今回は、プロット領域 PlotArea について調査しました。一部不明瞭な点がありましたが、軸をそろえることは今回の調査の範囲で実現できそうです。

次回からは、この情報を活用しつつ、実際にコーディングしていきます。


前回 Notes - Excel 連携 次回


2024/10/18

Notes - Excel 連携:#41)グラフの調整

この連載の #11 ~ #19 で Notes から Excel のグラフを作成する方法を紹介しました。そして、前回まで 3 回にわたり紹介した名前アイコンの生成では、画像ファイルとして保存できるようになりました。これらを組み合わせると、LotusScript だけでノーツのデータをグラフ化できます(裏で Excel は使用しますが...)。

そして、別の連載 DXL Step-by-Step の『#41)インラインイメージの貼り付け』を利用すれば、作成したグラフを見える状態(インラインイメージ)で貼りつけることができます。ここまでできるようになると、別の次元のノーツ文書が作成できるようになりますね。


グラフの調整が必要となるシーン

ただ、これまでに紹介したグラフの作成方法は、Excel が自動で行ってくれる機能を多分に利用しています。これでもそれなりに美しいグラフは作成できるのですが、業務で実用しようとすると問題点が出てきます。

例えば、複数のグラフを作成して比較する場合です。#18 のプログラムは 30 日分のユーザ数の推移をグラフにするサンプルでした(下図の上のグラフ)。これを修正して Y 軸の桁数が違うグラフを作成して並べてみます(下図の下のグラフ)。すると、X 軸と Y 軸の交点の位置がずれてしまいます。これでは比較しづらく、レポートして及第点はもらえないですよね。

今回からしばらくは、この問題の解決を題材とします。具体的には、グラフの軸ラベル、タイトル、凡例などを制御して、”使える”グラフにするための操作をまとめます。


作成するグラフ

今回チャレンジするグラフのイメージは次の通りです。

まずは、メインの目的であるグラフエリアのサイズを固定します。

左には軸ラベルを表示するエリアを含め一定の幅を確保します。タイトルと凡例はグラフ右に集め、表示するエリアとして、こちらも決められた幅を確保します。これらは位置はグラフエリアを最大化して値の変動を確認しやすくするのが狙いです。


前回 Notes - Excel 連携 次回


2024/10/13

DXL Step-by-Step:#41)インラインイメージの貼り付け

DXL 活用の調査・検証で、実現できたことや発見したことご紹介する『DXL Step-by-Step』シリーズの第 41 回です。しばらく表の話が続いたので少し流れを変えます。

今回はリッチテキストに直接貼り付ける画像についてまとめます。 ノーツクライアントでは、下図のようにプロパティで見るとソース欄に[インラインイメージ]と表示されるパターンですね。

DXL を使えば、リッチテキストに画像を貼った文書を作成することができます。また、文書から画像をダウンロードしてファイルに保存することも可能です。


インラインイメージの DXL

先ほどのリッチテキストを DXL で書き出すと次のようになります。

画像を表す png タグがあります。その配下には暗号化されたような文字列がテキストノードとして大量に出力されています。この構造は『#8)イメージリソース(設計要素)の DXL』で紹介した、イメージリソース内に保存されている画像データと同じですね。

画像データの1つ上は picture ノードとなっています。このノードは『#26)イメージリソースの DXL』で、リッチテキストにイメージリソースを配置する際に出てきました。

リッチテキストに配置したインラインイメージとイメージリソースの違いは、配下のノードが画像データか、リソースの参照 imageref かだけです。


サンプルプログラム

それでは、実際にインラインイメージを配置するプログラムを作成します。『#27)イメージリソースの表示』で作成したプログラムをベースに作業します。

エージェントをコピペして xSetDXL_ImageResource 関数を次の通り修正します。

Function xSetDXL_InlineImage(vddn As NotesDOMDocumentNode, vdenRT As NotesDOMElementNode, ByVal vsFileName As String)
   Dim denPar As NotesDOMElementNode
   Dim denPic As NotesDOMElementNode
   Dim den As NotesDOMElementNode
   Dim dtn As NotesDOMTextNode

   Dim nst As NotesStream
   Dim iType As Integer
   Dim iSizeX As Integer
   Dim iSizeY As Integer

   '画像ファイルの確認
   Set nst = xns.CreateStream()
   Call nst.Open(vsFileName)
   iType = xGetImageFileInfo(nst, iSizeX, iSizeY)

   If iType <> DXL_Image_Unknown Then

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

      '画像の作成
      Set den = vddn.CreateElementNode("picture")
      Call den.SetAttribute("width", CStr(iSizeX) & "px")
      Call den.SetAttribute("height", CStr(iSizeY) & "px")
      Set denPic = denPar.AppendChild(den)

      'インラインイメージの作成
      Set den = vddn.CreateElementNode(xGetImageNodeName(iType))
      Set den = denPic.AppendChild(den)

      '画像データセット
      Set dtn = vddn.CreateTextNode(StreamToBase64(nst))
      Call den.AppendChild(dtn)
   End If
End Function


画像ファイルの確認

xSetDXL_InlineImage 関数には、引数 vsFileName を追加しました。これはインラインイメージとして貼り付ける画像のファイル名です。指定したファイルは、関数の最初で画像の形式を確認しています。

画像ファイルを調査して、画像のフォーマットとサイズを返す関数 xGetImageFileInfo をコールしています。この関数は、『#13)イメージの形式とサイズの取得』で紹介しています。xGetImageFileInfo とサブ関数 xGetImageFileType をコピペします。


画像の作成

picture ノードの作成では、画像のサイズ width と height 属性を追加しています。イメージリソースを呼び出す場合はサイズ指定がなくても表示されたのですが、インラインイメージの場合は指定しないと画像が表示されませんでした。


インラインイメージの作成

インラインイメージの場合、画像の形式により作成するタグが変わります。画像ファイルに合致したタグ名を xGetImageNodeName 関数から取得し、ノードをを作成しています。

xGetImageNodeName 関数は次の通りです。

Function xGetImageNodeName(ByVal viImageType As Integer) As String
   Dim s As String

   If viImageType = DXL_Image_PNG Then s = "png"
   If viImageType = DXL_Image_GIF Then s = "gif"
   If viImageType = DXL_Image_JPEG Then s = "jpeg"

   xGetImageNodeName = s
End Function


画像データセット

画像データは Base64 でエンコードされた文字列を指定する必要があります。変換する関数 StreamToBase64 は『#10)イメージリソースの新規作成』で紹介しているので、コピペします。


呼び出し元の修正

xSetDXL_InlineImage 関数が完成したら、呼び出しもとを修正します。

Function xSetDXL(vdprs As NotesDOMParser)
         ・・・
   '段落定義
   Call xSetDXL_pardef(ddn, denRT)

   'インラインイメージの追加
   Call xSetDXL_InlineImage(ddn, denRT, "E:\411.PNG")
End Function

これでサンプルは完成です。実行するとこの記事の最初の画像のようにインラインイメージが貼り付けられたリッチテキストが作成されます。


まとめ

今回はインラインイメージをリッチテキストに追加する方法を紹介しました。LotusScript 標準の RichText クラスではできない機能ですので、これができるようになるとプログラム経由で作る文書の表現力が格段にアップしますね。


前回 DXL Step-by-Step 次回


2024/10/11

DXL Step-by-Step:#40)表のスタイル - タブ形式

ノーツの表には特殊な設定があって、これを利用するとタブ形式やアコーディオン形式の UI が作成できます。表のプロパティでは[表の行]タブの設定になります。今回はこの設定を DXL で操作してみましょう。

タブ形式の表では、表の行数分のタブが表示され、タブをクリックすることでその行を表示します。要はタブのクリックで表示する行を切り替える機能ということですね。ですので、プロパティでは『1 行のみ表示』を選択することになります。

そして、スタイルは『行の表示方法』で指定します。『タブボタンで行を選択』でタブ形式、『表題で行を選択』でアコーディオン形式となります。タブの表題は『タブラベルと表題』にしています。これは、行ごとに設定することになります。


表のスタイルの設定

DXL で表のスタイルを設定するには、table ノードの rowdisplay 属性を設定します。これまでこの属性を指定していなかったので、通常の表として表示されていたということですね。

属性 設定値 補足
rowdisplay (なし) 通常の表
'tabs' タブボタンで行を選択(タブ形式)
'captions' 表題で行を選択(アコーディオン形式)


タブの表題の設定

タブの表題は行ごとに指定することから tablerow ノードの属性として設定します。属性名は tablabel です。


サンプルプログラム

#32)表の作成と行・列の間隔 では、3 行 2 列の単純な表を作成するサンプルプログラムを紹介しました。今回はこれを改造します。

まず、表を作成する関数 xSetDXL_table を修正します。赤字が修正箇所です。

Function xSetDXL_table(_
            vddn As NotesDOMDocumentNode, _
            vdenRT As NotesDOMElementNode, _
            vvTabName As Variant, 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)
   'タブ形式の表
   Call denTbl.SetAttribute("rowdisplay", "tabs")

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

   '行の作成
   For iRow = 0 To UBound(vvTabName)
      Set den = vddn.CreateElementNode("tablerow")
      Set denRow = denTbl.AppendChild(den)
      'タブ名称
      Call denRow.SetAttribute("tablabel", vvTabName(iRow))

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

   Set xSetDXL_table = denTbl
End Function

まず、引数では行数を指定するのではなく、タブの表題を文字列型の配列で渡す仕様に変えています。これに伴い、行を作成するループの指定が配列の要素数分に変更されています。

そして、本題のタブの設定は、table ノードに rowdisplay 属性、tablerow ノードに対して tablabel 属性をセットしている 2 か所となります。


関数の修正に伴い、呼び出しもとを次のように修正します。

Function xSetDXL(vdprs As NotesDOMParser)
         ・・・
   '表前の段落追加
   Call xSetDXL_par(ddn, denRT)

   'タブ表の追加
   Dim asTabName(1)
   asTabName(0) = "Sample Tab 1"
   asTabName(1) = "タブ2"
   Set denTbl = xSetDXL_table(ddn, denRT, asTabName, 1)


   '間隔の設定
         ・・・
End Function

タブ名称を asTabName 配列にセットして、関数をコールしているだけです。


実行結果

修正したプログラムを実行すると、次のようにタブが 2 つの表が作成されます。

また、rowdisplay 属性を captions に変更すると次のようにアコーディオン形式となります。


前回 DXL Step-by-Step 次回


2024/10/09

リスト配列

先日、LotusScript でリスト配列を使う機会がありました。初めての利用だったのでヘルプで調べながら開発を行いました。せっかく調べたので、機能を整理しておきます。


リスト配列とは?

LotusScript では、Boolean、Integer、String などさまざまなデータ型が存在します。そして、同じデータ型の値をまとめて管理する構造体があります。配列というとなじみがありますよね。List も配列の一種です。

通常の配列は a1(0) など変数の後ろにカッコでインデックス番号(添え字や要素番号とも言います)を付けると値にアクセスできます。一方、List の場合は、インデックス番号の代わりに文字列(以下リストタグと呼ぶ)を使用します。

例えば、ユーザとそのユーザの点数を管理する場合を考えてみましょう。それぞれ、次のようなコードとなります。

List の場合 配列の場合
aiScore("Ito") = 75
aiScore("Sato") = 90
asName(0) = "Ito"
asName(1) = "Sato"
aiScore(0) = 75
aiScore(1) = 90


宣言方法

リスト配列を宣言するには次のように記述します。

   Dim aiScore List as Integer

aiScore("Ito") と配列のように記述するのですが、宣言に ( ) は必要ありません。要素や領域の確保は不要ということですね。また、配列のように多次元にすることはできません。


値の追加方法

リスト配列に値を追加するのは簡単です。次のように要素にリストタグを指定するだけです。

    aiScore("Ito") = 75

リスト配列内に "Ito" という要素がなければ新規で追加され、値がセットされます。すでに存在した場合は、値が更新されます。配列のように ReDim 操作が不要なので便利ですね。


値のアクセス

リスト配列の値のアクセスも値との追加と同じように、リストタグを指定するだけです。例えば、以下のコードはリストタグ Ito の値を 10 加算しています。

    aiScore("Ito") = aiScore("Ito") + 10


ループ

平均値を出力するなど、リスト内のすべての要素にアクセスしたい場合があります。このような処理には Forall ループを使用します。

例えば、次のコードではリスト配列 aiScore の合計 iSum と 件数 iCnt を算出し、平均を表示しています。

   ForAll iScore In aiScore
      iSum = iSum + iScore
      iCnt = iCnt + 1
   End ForAll

   MsgBox "平均点 = " & Format(iSum / iCnt, "#0.0")

Forall で使用している iScore がリスト配列の各要素(値)が順に入る変数です。


リスト配列の各種操作

上記情報で、リスト配列の通常使用は可能だと思います。ここから先はリスト配列利用に関連する各種操作をまとめます。チェックなど込み入った操作をする場合などに参考にできます。


◇ リストタグの取得

Forall ループの処理では、各要素の値がループ変数にセットされました。その値のリストタグを取得する関数が ListTag 関数です。

次のコードではリスト配列 aiScore のすべてのリストタグを順に表示します。

   ForAll iScore In aiScore
      MsgBox ListTag(iScore)
   End ForAll


◇ 要素の存在確認

続いては指定したリストタグが、リスト内に存在するか判定する方法です。次のコードではリスト配列 aiScore に Tanaka の要素があるか判定します。

   If IsElement(aiScore("Tanaka")) Then
      MsgBox "リスト配列内に存在します!"
   Else
      MsgBox "リスト配列内に存在しません。"
   End If


◇ 要素の削除

Erase ステートメントを使用するとリスト内の要素を削除することができます。

   Erase aiScore("Hata")

もし、指定した要素がリスト配列内に存在しない場合、エラーが発生します。


◇ リスト配列の初期化

Erase ステートメントを使用するとリスト配列内のすべてのエントリを削除し初期化することができます。

   Erase aiScore

リストタグを指定せず変数名を指定するとすべての要素を削除するということですね。


◇ リスト変数の判定

DataType 関数を使用すると変数のデータタイプが取得できます。

   Dim aiScore List As Integer
   MsgBox DataType(aiScore)

リスト配列は 2048 となりますが、上記の結果は 2050 となります。これは 整数型のリスト配列のためで、リスト配列 2048 + 整数型 2 = 2050 となります。型により値が変わるので注意が必要です。

また、リスト配列かを判定するだけであれば IsList 関数が便利です。

   If IsList(aiScore) Then
      MsgBox "aiScore はリスト配列です。"
   Else
      MsgBox "aiScore はリスト配列ではありません。"
   End If


◇ 空の判定

リスト配列を宣言した直後や Erase で初期化した後は、中身が空っぽの状態となります。この判定の方法については明確な関数は発見できませんでした。Ubound や Lbound のような関数はないようです。また、残念ながら IsEmpty の戻り値は True となり判定できませんでした。

Forall ループでは、ループ内の処理が一切実行されないので、それで判定する方法が良いかもしれませんね。


まとめ

今回は、リスト配列の使い方についてまとめました。配列と同様に、同じデータ型の値をまとめて管理できる変数でした。要素のアクセスは リストタグ と呼ばれる文字列で指定する点がポイントですね。

この記事の最初に『ユーザとそのユーザの点数を管理する場合』の例を上げ、配列との比較をしました。どちらが優れているという話をしたかったわけではありません。配列とリスト配列、近しい機能ではありますが、それぞれ違いがあります。作成するプログラムの仕様に合わせて、適材適所、使い分けができるといいですね。


2024/10/06

意外と難しかった四捨五入の作成

前回 は Round という関数は四捨五入ではなかったという話をしました。アプリ開発では必要となるシーンがありますので、ないのなら作ってしまいましょう。

ということで、今回は四捨五入する処理の作成を行います。そのサンプルを紹介するだけのつもりだったのですが、想定外の障害に出くわし、思いのほか難航しまた。そこで、その問題点も含めてまとめておきます。


LotusScript の場合

まずは、簡単に解決できる LotusScript での処理です。Round という関数が存在するので RoundOff 関数としました。

引数は Round 関数と同じで、丸めたい値 vdNum と 丸める桁 viPlaces です。

Function RoundOff(Byval vdNum As Double, Byval viPlaces As Integer) As Double
   Dim dDigit As Double
   Dim d As Double

   '丸めたい桁を1の位にする係数を作成
   dDigit = 10 ^ viPlaces

   '丸めたい桁を1の位にする
   d = vdNum * dDigit

   '四捨五入(0.5 を加算して切り捨て)
   d = Int(d + 0.5)

   '桁を戻す
   RoundOff = d / dDigit
End Function

コメントを記述しているので解説は不要かと思います。ポイントは四捨五入の代わりに 0.5 を足して、少数を切り捨てている点です。


式言語で問題発生

続いては@関数の四捨五入を作成してみます。@Round に倣って、丸めたい値 xNumber と 丸める桁 xFactor を用意して演算させます。

式の構成は上記 LotusScript の関数と揃えています。ただ、丸める桁の値の持たせ方が違いますので、乗除記号が逆転している点に注意してください。

   xNumber := 0.25;
   xFactor := 0.1;

   REM {丸めたい桁を1の位にする};
   xTmp := xNumber / xFactor;

   REM {四捨五入(0.5 を加算して切り捨て)};
   xTmp := @Integer(xTmp + 0.5);

   REM {桁を戻す};
   xTmp * xFactor

式ができ上がって検証しているときに事件が発生しました。なぜか、0.350 の場合に切り捨てが発生してしまいました !?

xNumber の値 0.249 0.250 0.251 0.349 0.350 0.351
実行結果 0.2 0.3 0.3 0.3 0.3 0.4

式をいくら見直しても原因がわかりません。そこで、式の途中経過を順に確認しました(数値フィールドの計算結果として確認)。

検証した式 結果(0.250) 結果(0.350)
xNumber / xFactor 2.5 3.5
xNumber / xFactor + 0.5 3 4
@Integer(xNumber / xFactor + 0.5) 3 3

@Integer に原因があることが明白になりました。そこで、Google 先生に聞いてみたところ以下のリンクが見つかりました。

@Integer 関数で小数を扱った場合の動作について

リンク内の事例でも @Integer の結果が 1 少なくなる場合があることが示されています。今回もそれが原因なのでしょう。

回避策を探して Workaround を確認すると、@Round を使えと書いてあります。@Round の四捨五入が使えないから @Integer を頼ったのに...。頭が混乱してきました。


このページの一番最後にこの現象の原因解説のリンクがありました。

浮動小数点演算と丸め誤差について

細かな話は分かりませんが、少数の値は 2 進数で表すと有限桁数で表せない場合があり、それに起因する誤差によるものだそうです。


式言語の四捨五入

少数の値とその誤差が問題原因なのであれば、できる限り少数を使わなければ抑制できるかもしれません。今回の事例では、丸めたい値が少数なのは仕方がないとして、それ以外の少数利用を避けてみようと考えました。

具体的には次の部分、誤差が混入しうる値(要は小数値)を演算に使用している点に着目しました。

   xTmp := xNumber / xFactor;


対策は次の通りです。

丸める桁の値の持たせ方を LotusScript の Round と合わせて、次のように記述しました。これで、元の値以外は整数(もしくは整数部)となります。

   xNumber := 0.350;
   xPlaces := 1;

   REM {丸めたい桁を1の位にする係数を作成};
   xDigit := @Power(10; xPlaces);

   REM {丸めたい桁を1の位にする};
   xTmp := xNumber * xDigit;

   REM {四捨五入(0.5 を加算して切り捨て)};
   xTmp := @Integer(xTmp + 0.5);

   REM {桁を戻す};
   xTmp / xDigit

サポート情報にある Workaround では誤差を @Round でごまかす方法を取っていましたが、誤差が混入しにくくすることで回避できないか挑戦してみるという方法ですね。


結果は 0.250 であっても 0.350 であっても正しく四捨五入できました。念のため、さまざまな値や桁数を変えてテストしましたが、問題はなさそうでした。


まとめ

今回は、四捨五入の処理を@関数と LotusScript で作成してみました。@関数では想定外のトラブルに見舞われ、思いのほか時間がかかってしまいました。ただ、問題の原因がわかれば、症状が出にくいように対策すれば回避できる事例になればと思い、紹介しました。


原因となった『@Integer 関数で小数を扱った場合の動作について』なのですが、この現象がいつから発生しているのかは記載がありません。原因からするとはるか昔から延々と受け継がれているような気がします。そして、対応のステータスは Deferred となっています。要は対応する気がないのだと理解しました。

ノーツは 30 年以上前のプログラムがそのまま動作する互換性が高い製品です。これがノーツをビジネスで利用する上で、重要な利点のひとつだと思っています。

だからと言って、バグまで互換性維持する必要はないですよね...。せっかくの利点が悪しき文化にならないように祈ります。


2024/10/05

四捨五入にご用心!

今回は、@関数と LotusScript の丸め処理、なかでも四捨五入についてまとめます。Notes の世界で四捨五入するには Round という関数を使用すると理解していましたが、結果的には ”四捨五入” ではない部分がありました。順に確認しましょう。


@Round 関数

式言語で丸め処理のを行う関数に @Round があります。

@Round (式言語)

説明には『指定された数値を最も近い整数に四捨五入します。』とあります。そして、構文は以下の通りとなっています。

   @Round( number ; factor )

2 つ目の引数 factor が丸める桁を指定します。0.1 と指定すると少数 1 桁の値に丸めます。


LotusScript の Round

Round 関数は LotusScript にも存在ます。

Round 関数 (LotusScript 言語)

説明には『値を指定した小数で丸めます。』とあり、構文は次の通りです。

   Round ( numExpr , places )

@Round と同様に 2 つ目の引数が丸める桁の指定です。ただし、『目的の小数点以下桁数を表す数式』となっています。少数 1 桁の値に丸めたいときには 1 と指定することになります。


丸め処理の不思議

2 つの関数を紹介しましたが気になることはありませんでしたか?

そうなんです。@Round は ”四捨五入” となっていますが、Round は ”丸め” となっています。これが翻訳の問題なのか、機能が違うのか確認しましょう。

@関数と LotusScript で少数 1 桁の値に丸めるプログラムを作成します。数値 x の値を変えながら動作を観察します。すると次のようが結果が出ました。

x 0.249 0.250 0.251 0.349 0.350 0.351
@Round(x; 0.1) 0.2 0.3 0.3 0.3 0.3 0.4
Round(x, 1) 0.2 0.2 0.3 0.3 0.4 0.4

いかがですか?

どちらも四捨五入ではありませんでした。しかも、ちょうど 5 の時の処理は @関数は”奇数”になるように調整、LotusScript は”偶数”となるように調整していました。

なぜ、このような違いになっているのか不思議ですね...


まとめ

今回の検証で、Round は、@関数、LotusScript とも四捨五入ではないことがわかりました。これら関数を利用する際には特性を理解した上で使用しましょう。正しい結果が得られない可能性がありますので...。

ちなみに、ノーツの四捨五入について調査していると以下のリンクに出くわしました。

LotusScript での四捨五入について

本家のサポート情報で、LotusScript で四捨五入を行う関数は用意されていないので Evaluate で @Round を実行する方法が紹介されています。今回の検証結果によるとうまくいかないはずなのですが...。

注意しましょう!


2024/10/03

@Text と Format

数値や日付データを文字列に変換する方法についてまとめます。特に書式を指定して変換する関数として、@関数では @Text、LotusScript では Format があります。これら関数は、使用頻度も高く便利なのですが、多機能な分、引数の指定が複雑です。ついついヘルプを見てしまう関数ですね。そこでよく使う変換をまとめておきます。


構文の確認

まずはそれぞれの関数の構文を確認します。

@関数の @Text は次の通りです。

   @Text( value ; format-string )

LotusScript の Format 関数は次の通りです。

   Format[$] ( expr [ , fmt ] )

引数の順は同じですね。どちらも 2 つ目の引数で変換する書式を文字列で指定する仕様となっています。今回はこの引数の指定方法についてまとめることになります。


数値の場合

数値を文字列に変換する際に行う処理としては、小数点以下の桁数をそろえる、3 桁ごとにカンマで区切る(カンマ編集)、一定の桁数に満たない場合 0 で埋める(ゼロ埋め)があります。それぞれ下記の通りです。

1234.567 という値の変換についてまとめました。

変換結果 @Text の書式 Format の書式 補足
1235 F0 0 整数のみ
1234.6 F1 0.0 少数 1 桁
1234.57 F2 0.00 少数 2 桁
1,234.6 F,1 #,##0.0 カンマ編集
01235 (なし) 00000 ゼロ埋め

@Text ではゼロ埋めする指定がないようなので、式で記述する必要がありそうです。

表示用の計算結果フィールドの式の例です。SourceNumber フィールドの値をゼロ埋めしています。xNumber に変換する値、xDigit に桁数を代入します。

   xNumber := SourceNumber;
   xDigit := 5;

   REM {エラー処理};
   @If(xNumber = ""; @Return(""); @Success);

   REM {指定した桁数でゼロ埋め};
   @Right(@Repeat("0"; xDigit) + @Text(xNumber; "F0"); xDigit)

なお、この式は桁あふれの場合、注意が必要です。例えば 1234567 の場合、"34567" となります。LotusScript の Format では "1234567" となります。


日付/時刻の場合

日付/時刻データでは、@関数と LotusScript で対比できるほど近しい指定が少ないので別々に記載します。

LotusScript の場合

Format 関数の引数の指定は単純です。年 月 日 時 分 秒 のそれぞれの英単語の頭文字を使って指定します。ただし、月 と 分 は同じ m となるため、分 では n を使用します。

また、1 文字ではゼロ埋めなし、2 文字でゼロ埋めとなります。これもわかりやすいですね。

例えば、2024/1/2 3:04:05 の場合、変換結果と引数の文字列は次のようになります。

変換結果 Format の書式 補足
1/2 m/d 月日(ゼロ埋めなし)
2024/01 yyyy/mm 年月(ゼロ埋め)
2024/01/02 yyyy/mm/dd 年月日(ゼロ埋め)
3:04 h:nn 時分(時はゼロ埋めなし)
3:04:05 h:nn:ss 時分秒(時はゼロ埋めなし)
2024/01/02 03:04:05 yyyy/mm/dd hh:nn:ss 年月日時分秒(ゼロ埋め)


@関数の場合

@Text 関数で日付/時刻値に対して指定できる記号は次のようなものがあります(抜粋)。

書式記号 補足
D0 年月日
D1 月日、現在の年でなければ年
D2 月日
D3 年月
T0 時分秒
T1 時分
S0 日付のみ
S1 時刻のみ
S2 日付と時刻
S3 日付と時刻(今日、昨日に限り文字で表示)

これらを組み合わせて指定すると希望する書式に変換できます。

変換結果 @Text の書式 補足
2024/01 D3S0 年月
2024/01/02 D0S0 年月日
03:04 T1S1 時分
2024/01/02 03:04:05 D0T0S2 年月日時分秒

@Text による変換の場合、必ずゼロ埋めになるようです。


@Text 仕様値の注意点

@Text を使用して日付を変換した場合、そのフォーマットは OS の日付の設定に依存します。例えば、日付(短い形式)を次の通り設定すると日付の区切り文字は ”-” となります。

次のように 月/日/年 の順にするとその順になってしまいます。

OS の設定に応じて結果も変わるところがポイントです。特に、グローバルな会社では注意が必要となりますね。


日付/時刻の変換

私はというとクライアント環境に依存して動作が変わることを避けたいので、@関数を使って日付値を文字列にする場合以下の式を使用しています。SourceDatetime が変換したい日付/時刻値が入ったフィールドです。

   xSrc := SourceDatetime;

   @If(xSrc = ""; @Return(""); @Success);

   REM {桁ごとに変換(不要な桁は削除)};
   xY := @Text(@Year(xSrc));
   xM := @Text(@Month(xSrc));
   xD := @Text(@Day(xSrc));
   xH := @Text(@Hour(xSrc));
   xN := @Right("0" + @Text(@Minute(xSrc)); 2);
   xS := @Right("0" + @Text(@Second(xSrc)); 2);


   REM {日付文字列を生成};
   xDate := @Implode(@Trim(xY:xM:xD); "/");
   xTime := @Implode(@Trim(xH:xN:xS); ":");

   @Implode(@Trim(xDate:xTime); " ");

赤字の部分がポイントです。年月日時分秒の各桁ごとに文字列で変換しています。0 埋めしたい場合は、分 や 秒のように @Right を使用しています。

もし、秒 が不要であれば xS の行を消すだけとなります。時刻が不要であれば、xH, xN, xS の行を消します。この操作だけで、後述の @Trim と @Implode がうまく調整してくれて、希望の文字列が出力されます。