出直し!! ヘルプ

連載中

連載 終了

2025/10/31

Notes - Excel 連携:#56)ビューをそのまま Excel へ ①

ノーツのビューを Excel に書き出したいという要望は、ユーザ部門だけでなく、システム部門でもたびたび発生します。ノーツクライアントには、書き出し機能や表形式でコピーなどさまざまな方法がありますが、ビューを見た目通りに書き出せるわけではありません。

そこで今回は、ビューをビューっぽく Excel に出力する方法に挑戦します。


基本方針

Excel エクスポートの機能を作成する場合、出力する列の数や出力するフィールドや計算式などを定義して、それに従い Excel シートに書き出すパターンが多いと思います。

今回は、新しい試みとして、ビューの設計に従って出力する方法に挑戦します。例えば、下図のように、列の並びと値をビューから取得する感じです。文字の色や列幅も極力再現します。

カテゴリは、Excel のグループ機能を活用し、開閉することとします。グループ機能は小計を表示するような機能であるため、明細データの下側に合計が配置されます。ノーツのカテゴリと大きく違う点ですね。

ワークシートの左側に閉じたり開いたりする機能がありますが、行としてカテゴリ(合計行)であることわかりやすくするため、背景をグレーにします。ビューとは印象が違いますが、それなりにわかりやすくできます。


次回の予告

設計要素から定義を読み込むことになるので DXL の出番かと考えていましたが、標準のノーツクラスでも設計要素の情報が取得できました。今回は、標準クラスで取得できる範囲で、可能な限り、ビューをそのまま出力してみます。うまく実現できればビューを指定するだけで、フォーマットとデータを ”まるっと” 出力できるようになります。

次回から実現する機能と利用するメソッドを解説しながら開発を進めます。


前回 Notes - Excel 連携 次回


2025/10/30

CSV ファイルの読み込み ④ - ファイルの読み込み 文字コードの設定

今回の記事にあたって郵便局の郵便番号ダウンロードサイトを見ていると新しいデータ形式のダウンロードが始まったことを確認しました(新しいといっても 2023 年のことで、すでに 2 年以上前ですが...)。

住所の郵便番号(1レコード1行、UTF-8形式)(CSV形式)

従前のデータでは、条件によっては 1 郵便番号が 2 レコードになるパターンがあったようなのですが、このデータでは発生しないようです。これが当たり前だと思うのですが、このデータであれば安心して利用できますね。また、カタカナが半角から全角文字になっているのもありがたいと感じます。

ただ、このデータはこれまでと違い UTF-8 形式となっています。ということで、今回は文字コードを意識したファイルの読み込みを行います。


文字コードの指定

CSV ファイルの読み込み ① - ファイルの準備』で Open ステートメントを紹介しました。この時はあえて省略したのですが、オプションで文字コードセットを指定する機能があります。例えば UTF-8 を指定する場合は次のようになります。

Open "c:\tmp\utf_ken_all.csv" For Input As #iFP Charset = "UTF-8"

この部分だけは VBA の名前付き引数のような記述で、LotusScript ではあまり見ない感じですね。VBA の名前付き引数の指定は := ですが、ここでは = だけなので注意してください。

= の後ろが指定できる文字コードセットで UTF-8 以外にも多数の指定ができます。詳しくはデザイナーヘルプの『MIME 文字セット名』を参照してください(Open ステートメントのヘルプからリンクされています)。


読み込みの仕様

まず、読み込んだデータを保存するフォームを作成します。

今回作成するサンプルで CSV データから取得する項目は赤字の部分となります。

01101,"064 ","0640941","ホッカイドウ","サッポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ケ丘",0,0,1,0,0,0

最初のカラムは全国地方公共団体コードです。前 2 桁は都道府県コード(JIS X0401)、後ろ 3 桁は市区町村コード(JIS X0402)です。何かで利用できそうなので、別フィールドにそれぞれ保存することとします。


サンプルプログラム

UTF-8 形式の郵便番号 CSV ファイルを読み込むサンプルプログラムは次の通りです。ファイルオープン時に UTF-8 を指定しています。

前回と同様に読み込んだデータは xCSVtoList 関数で配列化しています。この配列にあわせて、セットするフィールド名を配列 asFldLst にあらかじめ用意しておきます。これらを利用して、文書に値をセットをループで実現し、シンプルなメインルーチンにできました。前回 のまとめで ”構造化との相性が高い” としたのはこういった記述ができるからですね。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nd As NotesDocument

   Dim iFP As Integer
   Dim vLine As Variant
   Dim sLine As String
   Dim i As Integer
   Dim l As Long

   Dim asFldLst() As String     '保存フィールド名

   '保存フィールド名初期化
   ReDim asFldLst(8)
   asFldLst(0) = "JisCode"
   asFldLst(2) = "ZipCode"
   asFldLst(3) = "Address1_Kn"
   asFldLst(4) = "Address2_Kn"
   asFldLst(5) = "Address3_Kn"
   asFldLst(6) = "Address1_Kj"
   asFldLst(7) = "Address2_Kj"
   asFldLst(8) = "Address3_Kj"


   '初期化
   Set ndb = ns.CurrentDatabase

   'ファイルオープン
   iFP = FreeFile()
   Open "c:\tmp\utf_ken_all.csv" For Input As #iFP Charset = "UTF-8"

   On Error GoTo Err_Loop        ’エラー処理有効化

   '1 行目取得
   Line Input #iFP, sLine
   Do Until EOF(iFP)
      l = l + 1

      'CSV の 1 行を項目ごとの配列に分離
      vLine = xCSVtoList(sLine)

      '読み込んだデータを文書に保存
      Set nd = ndb.CreateDocument()

      nd.Form = "fZip"

      For i = 0 To UBound(asFldLst)
         If asFldLst(i) <> "" Then
            Call nd.Replaceitemvalue(asFldLst(i), vLine(i))
         End If
      Next


      '全国地方公共団体コード の処理
      nd.JisCode_Pref = Left(vLine(0), 2)      '都道府県コード(JIS X0401)
      nd.JisCode_City = Right(vLine(0), 3)   '市区町村コード(JIS X0402)

      Call nd.Save(True, True)

Loop_Next:
      '次の行取得
      Line Input #iFP, sLine
   Loop

   Exit Sub

Err_Loop:
   Print CStr(l) & " 行目の処理でエラーが発生しました。"
   Resume Loop_Next
End Sub

なお、関数 xCSVtoList は前回掲載したまま変更がないので割愛しています。


2025/10/29

CSV ファイルの読み込み ③ - ファイルの読み込み Line Input #

前回は Input # による CSV ファイルの読み込みについて紹介しました。今回はもう一つの読み込み命令である Line Input # を紹介します。


Line Input #

この命令はテキストファイルから 1 行分のデータを読み込む命令となります。Input # とは違い行単位で処理できるので、CSV だけでなく可変長のテキストファイルなど、より自由なフォーマットのファイル操作にも対応できます。

Line Input #fileNumber , varName

fileNumber データを読み込むファイル番号
varName 読み込んだ 1 行分のデータをセットする変数


サンプルプログラム

では、Line Input # を使用した CSV ファイル読み込みエージェントを紹介します。

前回の Input # のサンプルと違うのは、CSV データ 1 行分を変数 sLine に読み込み、関数 xCSVtoList で項目ごとに分離している点です。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nd As NotesDocument

   Dim iFP As Integer
   Dim vLine As Variant
   Dim sLine As String

   '初期化
   Set ndb = ns.CurrentDatabase

   'ファイルオープン
   iFP = FreeFile()
   Open "c:\tmp\KEN_ALL_ROME.CSV" For Input As #iFP

   '1 行目取得
   Line Input #iFP, sLine

   Do Until EOF(iFP)
      'CSV の 1 行を項目ごとの配列に分離
      vLine = xCSVtoList(sLine)

      '読み込んだデータを文書に保存
      Set nd = ndb.CreateDocument()

      nd.Form = "fZip_Rome"

      nd.ZipCode = vLine(0)
      nd.Address1_Kj = vLine(1)
      nd.Address2_Kj = vLine(2)
      nd.Address3_Kj = vLine(3)
      nd.Address1_En = vLine(4)
      nd.Address2_En = vLine(5)
      nd.Address3_En = vLine(6)

      Call nd.Save(True, True)

      '次の行取得
      Line Input #iFP, sLine
   Loop

   Close
End Sub

Private Function xCSVtoList(ByVal vsLine As String) As Variant
   Dim v As Variant
   Dim i As Integer
   Dim s As String

   'カンマで分離(文字列内のカンマは考慮しない)
   v = Split(vsLine, ",")
   For i = LBound(v) To UBound(v)
      '各要素の前後のダブルクォーテーションを削除
      s = v(i)
      If Left(s, 1) = |"| Then
         s = Right(s, Len(s)-1)
      End If
      If Right(s, 1) = |"| Then
         s = Left(s, Len(s)-1)
      End If
      v(i) = s
   Next

   xCSVtoList = v
End Function

今回の CSV ファイルでは文字列内にダブルクォーテーションやカンマが入る前提としていないので、xCSVtoList 内の処理は非常に単純な仕様としています。


まとめ

今回は Line Input # を使用した CSV 取り込みを紹介しました。Line Input # と前回の Input # と基本的な機能で比較してみます。

Line Input # Input#
読み取り単位カンマや改行で区切られた項目単位1 行単位
クォート(")の扱い そのまま文字として読み取る 文字列では自動削除
型変換 常に文字列(自身で変換)
変数に応じて自動変換
CSV 向き? Split などで手動で分離 簡易的な CSV なら自動対応

一見 Input # の方が優勢に見えますね。ただ、プログラミングに力点を置いて比較すると立場が逆転します。

Line Input # Input#
応用性 高い 低い
CSV 以外にも対応
クォート、可変列なども自力で自由に処理
CSV 以外はほぼ無理
構造化との
相性
高い 微妙
配列処理やクラス化しやすい
型変換や検証が柔軟
直接変数に読み込むため構造化に不向き
データ検証 容易 困難
生データを扱えるので、欠損や不正などチェックしやすい エラーが発生せず、初期値となることがある
例外処理の
自由度
高い 低い
自力で自由にチェックできる 想定外のデータでずれ、エラーが出ない
保守性 高い 低い
読み込みルールを変更しやすい 変数定義と一致させるため、全体修正が必要となることも

与えられるデータに含まれる文字や精度、将来想定される修正などを考慮の上、状況に応じた使い分けが必要な感じです。まるで、ローコードとプロコードの比較のようですね。


2025/10/28

CSV ファイルの読み込み ② - ファイルの読み込み Input #

前回に続いて CSV の読み込み方法を紹介します。

まず、利用する CSV のサンプルですが、郵便局の Web サイトからダウンロードできる 郵便番号(ローマ字) を利用します。フォーマットは 1 郵便番号当たり 7 項目の CSV ファイルとなっています。

"0600000","北海道","札幌市 中央区","以下に掲載がない場合","HOKKAIDO","SAPPORO SHI CHUO KU","IKANIKEISAIGANAIBAAI"
"0640941","北海道","札幌市 中央区","旭ケ丘","HOKKAIDO","SAPPORO SHI CHUO KU","ASAHIGAOKA"
     ・・・

CSV ファイルなどのテキストファイルの読み込みには Input # と Line Input # の 2 つの方法があるのですが、今回は Input # を紹介します。


Input #

このステートメントの構文は次の通りです。

Input #fileNumber , variableList

fileNumber データを読み込むファイル番号
variableList 読み込んだデータをセットする変数リスト(カンマ区切り)

変数リストの設定は LotusScript のプログラミングであまり登場しないパターンで、データを受け取る変数名を必要な数だけカンマ区切りで列挙します。例えば、事例の CSV の場合では、以下のように記述します。青字部分が変数リストとなります。

   Dim iFP As Integer
   Dim sZip As String
   Dim sKj1 As String
   Dim sKj2 As String
   Dim sKj3 As String
   Dim sEn1 As String
   Dim sEn2 As String
   Dim sEn3 As String

   'ファイルオープン(読み込みモード)
   iFP = FreeFile()
   Open "c:\tmp\KEN_ALL_ROME.CSV" For Input As #iFP


   Input #iFP, sZip, sKj1, sKj2, sKj3, sEn1, sEn2, sEn3
         ・・・

実行すると下図のように CSV 内の各カラムの値がそれぞれの変数に代入されます。

カンマをデータの区切りとして分離、文字列の始めと終わりのダブルクォーテーションを削除してくれることが特徴です。

この挙動からわかる通り、Input # は CSV ファイルの読み込みを意識した仕様になっていることがわかります。


サンプルプログラム

では、具体的に事例の CSV ファイルをフォームに読み込むエージェントを紹介します。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nd As NotesDocument

   Dim iFP As Integer
   Dim sZip As String
   Dim sKj1 As String
   Dim sKj2 As String
   Dim sKj3 As String
   Dim sEn1 As String
   Dim sEn2 As String
   Dim sEn3 As String

   '初期化
   Set ndb = ns.CurrentDatabase

   'ファイルオープン
   iFP = FreeFile()
   Open "c:\tmp\KEN_ALL_ROME.CSV" For Input As #iFP

   '1件目取得
   Input #iFP, sZip, sKj1, sKj2, sKj3, sEn1, sEn2, sEn3

   Do Until EOF(iFP)
      '読み込んだデータを文書に保存
      Set nd = ndb.CreateDocument()

      nd.Form = "fZip_Rome"

      nd.ZipCode = sZip
      nd.Address1_Kj = sKj1
      nd.Address2_Kj = sKj2
      nd.Address3_Kj = sKj3
      nd.Address1_En = sEn1
      nd.Address2_En = sEn2
      nd.Address3_En = sEn3

      Call nd.Save(True, True)

      '次のデータを取得
      Input #iFP, sZip, sKj1, sKj2, sKj3, sEn1, sEn2, sEn3
   Loop

   Close
End Sub

Do ~ Loop の判定で使用している EOF(iFP) はそのファイルが最後(End Of File)であるかを判定する命令です。ループの最後の Input # で次のデータを読み込んでループを繰り返しています。これでファイルを順に最後まで読み込むこととなります。


Input # の注意点

上記の通り Input # は CSV 読み込みの処理ではとても便利に利用できます。ただ、おいしい話には裏があるもので、注意点がいくつかあります。


◇  数値変換できない場合は 0 となる

例にはありませんが、数値変数を指定すると数値として読み込んでくれる機能があります。うまく使えば、データの型操作を最小限にすることができるのですが、利用上の注意があります。それは、読み込んだデータが数値に変換できない場合は、0 となることです。

例えば、郵便番号 sZip を数値(Integer 型)に変更して実行します。

   Dim sZip As Integer

変数が数値型であるため、ファイルから変数に割り当てられるデータは、ダブルクォーテーションが付いた "0640941" のような値となります。ダブルクォーテーションは数値に変換できないので変数の値は 0 となり、結果作成される文書の値も 0 となってしまいます。


◇ 特殊文字に注意

CSV ファイルのフォーマットでは、ダブルクォーテーションで括られた範囲内が文字列となります。ただ、文字列内にダブルクォーテーションを含める場合には、"" のように二重に書くことになっています(ダブルクォーテーションエスケープ)。ところが、Input # では対応していないようです。

少々意地悪なテストデータを与えてみました。Input # 経由で読み取れたデータに色を付けると次のようになりました。『以下に"掲載"がない場合』と読み取ってほしいところが、3 データに分離されてしまいました。どうやら、文字列内のダブルクォーテーションは区切り文字として判定するようです。

"0600000","北海道","札幌市 中央区","以下に""掲載""がない場合","HOKKAIDO","SAPPORO SHI CHUO KU","IKANI,KEISAI,GANAIBAAI"

ちなみに、最後のカラムにあるように、文字列内の ,(カンマ)は区切り文字ではなく、データとして正しく取り扱いました。文字列内のカンマは区切り文字と判定しないようです。

特殊文字を含む CSV の場合は対応が完全ではないので、注意がいるようですね。


◇ レコードという考え方がない

Input # は区切り文字に従い順にデータを読み込むことに特化しています。例えば以下のように読み込み変数を一つだけ指定して実行してみます。

Input #iFP, sZip

すると郵便番号を読み込んだ後、次は同じ1行目の都道府県名が読み込まれました。行単位で読み込みを行う場合には、行内に存在するすべてのカラム数分の変数を記述する必要がある点に注意が必要です。

もし、一部のカラムしか処理に必要ない場合には、ダミーの変数をセットすることができます。例えば最初のカラム以外不要なのであれば、次のように記述できます。

Input #iFP, sZip, sTmp, sTmp, sTmp, sTmp, sTmp, sTmp

無用な変数宣言を減らせるので、少しだけコードが短く、見やすくできますね。


まとめ

今回は CSV 取り込みに特化した Input # を紹介しました。便利な機能ではあるのですが、注意点がいくつか存在しました。

特に、カラムがずれる現象が発生には要注意です。ずれが発生すると後続のデータでは、数値カラムに文字が入ったりします。通常 LotusScript では型変換エラーが発生して気付けるのですが、上記の通り Input # ではエラーが発生しません。レコード単位で処理する機能がないことと相まって、いったんずれが発生すると、元に戻ることがありません。

絶望的な結果となったにも関わらず、何となく動いてしまい、正常終了扱いとなっていることがあります...。


2025/10/27

CSV ファイルの読み込み ① - ファイルの準備

LotusScript でファイルを操作する方法には NotesStream クラスを利用する方法と下記のステートメントを使用する方法の 2 種類があります。ざっくりいうと前者はバイナリファイル、後者はテキストファイルの操作に向いています。

Open 読み書きできるようファイルを開く
Close 開いているファイルを閉じる
FreeFile 使用していないファイル番号を返す
Input #
Line Input #
ファイルからデータを読み込む
Print #
Write #
ファイルにデータを書き込む

今回は、これらステートメントを利用した CSV ファイルの読み込みについて紹介します。


Open

ファイル操作を開始するには、まず Open 命令でファイルを開く必要があります。構文は下記の通りで、開くファイルと開き方を指定します。

Open fileName For mode As #fileNumber

filename 開くファイルを文字列で指定
フルパスで指定しない場合、カレントディレクトリを基準に開く
mode ファイルの開き方を指定(下表参照)
fileNumber 1 ~ 255 までの整数
他のファイル操作コマンドは、この番号でファイルを参照

指定できるモードの種類は以下の通りです。CSV ファイル操作では Input / Output / Append を使用します。

Input シーケンシャル入力モードで開く
テキストファイルの読み込みで使用
Output シーケンシャル出力モードで開く
テキストファイルの書き込みで使用
Append Outputと同じく書き込み用
既存のファイルに追記する
Random ランダムアクセスモードで開く
ファイルはレコード番号でアクセスし、Get # / Put # ステートメントで読み書きする
Binary バイナリファイルモードで開く
Get # / Put # ステートメントで読み書きする


FreeFile

Open ではファイル番号(fileNumber)をしていました。このファイル番号を指定してファイル操作を行うことになるのですが、その番号を取得するのが FreeFile ステートメントです。

fileNumber = FreeFile()

戻り値は、使用していないファイル番号です。この命令を使えば、何番を使えるのか意識する必要がなく、使用できる番号を簡単に取得できます。


Close

開いているファイルを閉じます。引数で閉じるファイル番号を指定できますが、省略するとすべてのファイルを閉じます。

Close fileNumber

LotusScript スクリプトが終了すると開いているファイルは自動的に閉じるようですが、プログラムの意図を明確にするためにも処理が終わったら閉じるようにしましょう。


サンプル

CSV ファイル(テキストファイル)を読み込みモードで開く処理は次のように記述します。

Sub Initialize
   Dim iFP As Integer

   'ファイルオープン(読み込みモード)
   iFP = FreeFile()
   Open "c:\tmp\KEN_ALL_ROME.CSV" For Input As #iFP
   
         (ファイル読み込み処理)
   
   'ファイルのクローズ
   Close
End Sub

FreeFile で使用するファイル番号を取得して、Open で使用しています。変数名 iFP の前に # が付いていますが、これはファイル番号を表すキーワードだと覚えてください。LotusScript 登場のはるか前にあった BASIC 言語の名残となります。


次回の予定

これで CSV ファイルの読み込み準備ができました。次回はファイルからデータを読み込む処理について紹介します。


2025/10/24

ZIP ファイルの解凍

今回は ZIP 圧縮されたファイルを LotusScript で解凍する方法を紹介します。


ZIP ファイルの解凍

利用するのは PowerShell の Expand-Archive コマンドです。構文やパラメータの詳細は Microsoft Learn の Expand-Archive をご確認ください。

Expand-Archive
      [[-DestinationPath] <String>]
      -LiteralPath <String>
      [-Force]
      [-PassThru]
      [-WhatIf]
      [-Confirm]
      [<CommonParameters>]

利用するパラメータは次の3つです。

-DestinationPath 解凍先のフォルダ
 -LiteralPath 解凍する ZIP ファイルのパスとファイル名
-Force 上書き保存する場合に指定

ちなみに PowerShell は Windows で利用できるスクリプトやコマンドの実行環境で、Windows 7 以降で利用できます。Windows 登場以前のコマンドプロンプト(cmd.exe)、Windows 98 以降で利用可能のとなった WSH(Windows Script Host)を統合して進化させたような存在といえます。


サンプルプログラム

C ドライブの Tmp フォルダにある ZIP ファイルを解凍するサンプルは次の通りです。

sCMD には PowerShell で実行する解凍コマンド、sPS には WSH で PowerShell を実行させるコマンド文字列をセットしています。PowerShell を実行するのに 前回 紹介した WSH を利用している点がポイントですね。

   Dim oWSH As Variant
   Dim sZIP As String    ' ZIP ファイルのパスとファイル名
   Dim sFol As String    ' 解凍先のフォルダ
   Dim sCmd As String
   Dim sPS As String

   ' 初期化
   sZIP = "C:\Tmp\ken_all.zip"
   sFol = "C:\Tmp\"

   ' PowerShell コマンドを作成
   sCmd = |Expand-Archive|
   sCmd = sCmd & | -LiteralPath '| & sZIP & |'|                ' ZIP ファイル
   sCmd = sCmd & | -DestinationPath '| & sFol & |'|      ' 解凍先
   sCmd = sCmd & | -Force|        ' 上書き

   ' PowerShell の -Command で ZIP 解凍を実行するコマンド文字列を作成
   sPS = |powershell -Command "| & sCmd & |"|

   ' PowerShell を実行(ウィンドウ非表示、実行終了を待つ)
   Set oWSH = CreateObject("WScript.Shell")
   oWSH.Run sPS, 0, True

   ' 解凍先フォルダをエクスプローラで開く
   oWSH.Run |explorer.exe "| & sFol & |"|


2025/10/23

フォルダをエクスプローラで開く

CSV や画像などのファイルを操作するアプリを開発をしているとそのフォルダを確認したくなることがあります。そこで今回は WSH を利用して、エクスプローラで開く方法を紹介します。


WSH とは

WSH(Windows Script Host)は Windows 上で、スクリプトを実行するための仕組み(スクリプト実行環境)のことです。拡張子が .vbs(VBScript)や .js(JScript)などのスクリプトファイルを Windows 上で直接実行できるようにするものです。詳しくは Microsoft Learn のドキュメントをご確認ください。

Windows Script Host

リファレンスにメソッドやプロパティが列挙されています。WSH でどういったことができるのか確認できますね。


プログラムを実行するには

利用するのはプログラムを実行するための Run メソッド です。構文と引数は次の通りです。

object.Run(strCommand, [intWindowStyle], [bWaitOnReturn])

1 strCommand 文字列 実行するコマンド
2 intWindowStyle 数値 ウィンドウの外観
指定できる値は  解説 を参照
3 bWaitOnReturn Boolean True:プログラムの実行が終了するまでスクリプトの実行は中断

今回はこのメソッドを使用してエクスプローラ(explorer.exe)を起動します。


サンプルプログラム

以下のプログラムは、ノーツクライアントのデータディレクトリをエクスプローラで開きます。

   Dim ns As New NotesSession
   Dim oWSH As Variant
   Dim sData As String

   'データディレクトリ取得
   sData = ns.GetEnvironmentString("Directory", True)

   'WSH オブジェクトの作成
   Set oWSH = CreateObject ("WScript.Shell")

   ' エクスプローラを実行(データディレクトリを指定)
   oWSH.Run |explorer.exe "| & sData & |"|

WSH を LotusScript で利用するには、CreateObject で "WScript.Shell" を指定してオブジェクトを取得します。あとは Run メソッドを使って、explorer.exe を実行するだけです。開くフォルダはオプションとして指定します。exe の後ろにスペースで区切ってフォルダ名を指定するのですが、パスやファイル名にスペースが混ざることに備えて全体を "" でくくっています。

なお、データフォルダのパスは、GetEnvironmentString メソッドを使用して notes.ini の Directory エントリから取得しています。


2025/10/21

共通部品にチャレンジ:#14)LotusScript 実行ログ - ライブラリの完成と利用

前回作成したクラス化したライブラリを実際に使用してみます。

#7)LotusScript 実行ログ - 基本機能のテスト」で作成したエージェントをサンプルとして書き換えてみます。修正箇所がわかりやすいよう修正前後を並べて表示すると以下の通りとなります。修正後の 赤字 は追加、修正前の 青字 は削除となります。

修正後 修正前
Option Declare
Use "StdLog"   '実行ログ の組み込み

Sub Initialize
   Dim i As Integer
   Dim iMax As Integer
   Dim iSum As Integer
   Dim s As String
   Dim oLog As New StdLog("StdLog のテスト")

   On Error GoTo ErrProc

   '初期化
   iMax = 10

   'ログの開始
   Call oLog.LogOpen()
   Call oLog.PrintLog("1 ~ " & CStr(iMax) & " までの総和を求めます...")

'1 ~ iMax までの総和を計算
   For i = 1 To iMax
      s = CStr(i) & ") " & CStr(iSum) & " + " & CStr(i) & " = "
      iSum = iSum + i
      '計算経過の出力
      Call oLog.PrintLog(s & CStr(iSum))
   Next

   Call oLog.PrintSummary("1 ~ " & CStr(iMax) & " までの総和は " & CStr(iSum) & " です。")

   'ログの終了
   Call oLog.LogClose()

   Exit Sub

ErrProc:
   Call oLog.PrintError(Error$ & " (Err = " & CStr(Err) & ", Erl = " & CStr(Erl) & ")")
   End
End Sub
Option Declare
Use "StdLog"    '実行ログ の組み込み

Sub Initialize
   Dim i As Integer
   Dim iMax As Integer
   Dim iSum As Integer
   Dim s As String


   On Error GoTo ErrProc

   '初期化
   iMax = 10

   'ログの開始
   Call LogOpen("StdLog のテスト")
   Call PrintLog("1 ~ " & CStr(iMax) & " までの総和を求めます...")

   '1 ~ iMax までの総和を計算
   For i = 1 To iMax
      s = CStr(i) & ") " & CStr(iSum) & " + " & CStr(i) & " = "
      iSum = iSum + i
      '計算経過の出力
      Call PrintLog(s & CStr(iSum))
   Next

   Call PrintSummary("1 ~ " & CStr(iMax) & " までの総和は " & CStr(iSum) & " です。")

   'ログの終了
   Call LogClose()

   Exit Sub

ErrProc:
   Call PrintError(Error$ & " (Err = " & CStr(Err) & ", Erl = " & CStr(Erl) & ")")
   End
End Sub

プログラムの基本構造に変化はありません。クラスのオブジェクト oLog を作成し、そのメソッドをコールしている点だけが違いますね。


クラス化の必要性は、「#12)LotusScript 実行ログ - 実際の利用と課題」でまとめまた通りなのですが、このライブラリを組み込む場合の制限が減ることが最大の利点です。

少々極端な例ですが、クラス名と同じ変数が宣言されていても問題ありません。クラス名と変数名が混同されることはないようです。

Option Declare
Use "StdLog"    '実行ログ の組み込み

Sub Initialize
   Dim StdLog As String
   Dim oLog As New StdLog("StdLog のテスト")

   '初期化
   StdLog = "Test"

   'ログの開始
   Call oLog.LogOpen()
   Call oLog.PrintLog(StdLog)
         ・・・

うまくいかないパターンといえば、ライブラリと同じ名前のクラスが組み込み側にあった場合となります。ただ、ライブラリとして提供するクラス名には Std の接頭文字をつけるなどルールを決めておけばこのような問題も起こらないでしょう。


まとめ

「LotusScript 実行ログ機能」の共通部品化(ライブラリの作成)作業はこれでいったん終了です。現時点ではログ出力の必要最低限の機能となっており、経過時間の閾値の設定や出力 DB を指定などの機能強化が欲しいところです。機会があれば別途まとめたいと思います。

ところで、この記事を書いていて、デザイナーのバグ(?)を一つ発見しました。スクリプトライブラリの名称とクラスの名称が同じ場合、ポップアップヘルプが正しく表示されないようです。

たまたま、ライブラリの名称を変えてみたら、正しく表示されるようになりました。何が原因か想像できない意味不明なバグ(?)ですね。デザイナーが不安定になっているのかと思い、何度も再起動してしまいました...


前回 共通部品にチャレンジ


2025/10/19

共通部品にチャレンジ:#13)LotusScript 実行ログ - クラスに書き換え

汎用ライブラリは、できる限り組み込みやすい状態するため、クラス化することをお薦めしました。今回は、これまでに作成したライブラリをクラス化する作業を行います。


クラスの新規作成

まずは、クラスの器を作成します。クラス名は StdLog とします。

スクリプトライブラリの Options セクションに Public Class StdLog と入力し、Enter キーを押します。新しいクラス StdLog が定義され、画面が切り替わります。


変数の移設

Declarations に定義した Private 変数から、クラスごとに値を保持させるべき変数宣言をクラス内に移動します。

NotesSession や 現在の DB(xndbCur)は不変的なので、Declarations に残したままとします。

ログ DB(xndbLog)は、将来ログ出力 DB を指定する機能を作成する可能性もあるので、クラス内に移動しています。


関数の移設

Initialize と Terminate を除くすべての関数をクラス内に移動します。

Public の関数はそのままでクラス外からアクセスできるメソッドとなり、Private な関数はクラス内だけで利用できる関数となります。

移設が完了するとスクリプトライブラリのオブジェクトは下図のようになります。


初期化処理の移設

Initialize の処理からクラス単位で行う処理をクラスの初期化処理に移設します。

具体的には、Declarations に残した変数 xns と xndbCur の処理(赤枠)以外をクラス内の New サブルーチンに移設します。

なお、クラス化に当たり、ログの名称はクラスの初期化で指定することとします。よって、New にログ名称を受け取る変数 vsLogTitle を引数として定義しています。

ログの名称は、クラス内の変数 xsLogTitle で保管します。下図の通り変数の定義と New の引数で受け取ったログ名称を保管するよう追加します(赤枠)。


LogOpen の修正

ログの名称を New で指定するように変更したので、LogOpen の引数を削除します(紫枠)。ログ文書の初期化処理では、前段で保管した xsLogTitle を使って処理するように変更します(赤枠)。


終了処理の調整

Terminate で行っていた終了処理をクラス内に移設します。初期化 Initialize と同様に Delete サブルーチンを追加して、その中に移動します。


クラス化完了

ここまでの作業が完了すると、下図のようにエラーのない状態になっているはずです。

最後は機能には関係しませんが、コードを見やすくするために命名規則にあわせる調整を行います。今回はクラス内に定義されたプライベートな変数と関数の接頭文字を修正します。変数の命名規則ついては こちら にまとめています(下表はその抜粋)。

接頭語 用途
x スクリプトライブラリやフォーム内で有効な変数(パブリックではない)。
z クラス変数などスコープがクラス内に限定される場合に指定。
x と区別するために z とする。

関数名の接頭文字を zx としました。修正作業が完了すると以下のようになります。インターフェースとなるプロパティやメソッドとクラス内の Private 変数、関数が分離されて表示されるので見やすくできます。

次回は、このクラスに合わせてサンプルエージェントを修正します。


前回 共通部品にチャレンジ


2025/10/18

共通部品にチャレンジ:#12)LotusScript 実行ログ - 実際の利用と課題

前回までで LotusScript 実行ログを出力するライブラリ StdLog を作成しました。既存プログラムでこの機能を利用して、ログを出力するには、まずライブラリを DB 内にコピーしたうえで、Use で組み込みます。

Use "StdLog"

するとライブラリ内の Public な関数を利用してログ出力できます。提供する関数は次の通りでした。

関数名 機能
LogOpen ログをオープンします
PrintLog ログ(通常メッセージ)を出力します
PrintError エラーログを出力します
PrintSummary サマリーログを出力します
LogClose ログをクローズし、ログ文書を保存します

ここで問題となるのが、関数名の重複です。既存プログラム内に同名の関数がすでに存在した場合、そのままでは利用できません。かなり極端な事例ですが、次のような感じです。


この先ライブラリを機能拡張すると Public な関数が増えることになります。同様のエラーが発生する可能性が高くなり、まずます組み込みにくいライブラリとなってしまいます。


クラス化で解決

この問題を改善するのがクラス化です。ノーツの文書を取り扱う機能が NotesDocument クラスにまとまっているように、実行ログを出力する機能をユーザ定義クラスとしてまとめてしまう方法です。

次の例では、StdLog というクラスを作成して、ログ出力を実現しています。クラス内の PrintLog メソッドとエージェントの PrintLog サブルーチンはスコープが別になるのでエラーとなりません。将来、機能拡張があっても既存プログラムの影響は少ないことがわかりますね。

なお、クラス化については別の連載『クラス化に挑戦』でまとめています。必要に応じてを参照ください。


柔軟な利用

クラス化によって、ログ出力機能をオブジェクト内に完結させることができます。

これにより、1 つのエージェント内で複数のログ出力オブジェクトを同時に扱うことが可能になります。たとえば、デバッグ用とユーザ向けのログを分けて出力することで、見る人に応じた内容を整理して記録できます。

   Dim oLogAdmin As New StdLog("デバッグ用の詳細ログ")
   Dim oLogUser As New StdLog("ユーザ向け処理結果")

さらに、oLogAdmin や oLogUser を引数として他の関数に渡すことで、プログラムの意図がより明確になり、独立性の高い構造を実現できます。


開発効率の向上

クラス化する利点はほかにもあります。

組み込みのノーツクラスと同様に . を入力すると、利用できるプロパティやメソッドがリストアップされ、タイプアヘッドで選択できます。さらにライブラリに記載したコメントがポップアップヘルプとして表示されます。

コメントをしっかり書いておけば、ライブラリを開くことなく利用できますので、開発効率が劇的に上がります。


まとめ

今回は、通常の関数で作成したライブラリとクラス化したライブラリの差についてまとめました。クラス化したほうが汎用性が高くなり、ライブラリをより活用できることが確認できたかと思います。

そこで次回は、前回まで作成したライブラリをクラス化する作業を行います。


前回 共通部品にチャレンジ 次回


2025/10/16

文書の貼り付け禁止

ノーツはクリップボードを使用して文書(データ)を簡単に複製(レプリカではない)できます。これはこれで便利なのですが、アプリの仕様によっては制限したい場合があります。

そこで今回は、文書の貼り付けを禁止する方法についてまとめます。


実現方法

文書の貼り付け操作を行うとビューで 2 つのイベントが発生します。QueryPaste は貼り付ける前、PostPaste は貼り付けた後に発生します。今回は貼り付け操作を制御するので QueryPaste イベントを利用することになります。

他の Query??? イベントと同じく引数に Continue が存在します。この変数に False を設定すると貼り付けがキャンセルできるということですね。

Sub QueryPaste(Source As NotesUIView, Continue As Variant)
   Continue = False
End Sub


実用的なサンプル

上記のように、なんのメッセージもなしにキャンセルするとユーザは何が起こったのかわかりません。このアプリでは、文書の貼り付けを制限していると伝えてあげるべきです。

また、アプリの運用担当者など特権ユーザだけは、貼り付けを許可したい場合もあるかと思います。このような機能を実現するサンプルコードを紹介します。

Sub Querypaste(Source As Notesuiview, Continue As Variant)
   Dim v As Variant
   Dim i As Integer
   Dim sRole As String
   Dim sMsg As String

   ' 初期化
   Continue = False      ' いったん貼り付け禁止
   sRole = "[Admin]"    ' 貼り付け OK のロール
   sMsg = "アプリの仕様上、文書の貼り付けは制限されています。"

   ' OK のロールを持っているなら "OK" を返す
   v = Evaluate(|@If(@IsMember("| & sRole & |"; @UserRoles); "OK"; "NG")|)

   If v(0) = "OK" Then
      ' OK なので実行を確認
      i = Msgbox(sMsg & Chr(10) & "それでも貼り付けますか?", 36+256, sRole & " 保持者")
      If i = 6 Then
         ' [はい] をクリック = 貼り付け OK
         Continue = True
      End If
   Else
      ' 一般の利用者
      Msgbox sMsg, 16, "文書貼り付け"
   End If
End Sub

特権ユーザはロールを保持しているかで判定しています。初期化で sRole にそのロール名をセットしていますので、違う名称で判定したい場合にはこの値を調整してください。

実行すると保持しているロールに応じてメッセージが変わります。



まとめ

今回は文書の貼り付けの制御についてまとめました。

この機能はビューのイベントに設定します。ビューのイベントはビューごとに存在しますので、ユーザが表示できるすべてのビューに同じようにセットしないと抜け道ができてしまいます。特に後からビューを追加する場合に忘れがちなので、注意してください。


2025/10/14

Environ 関数 (LotusScript)

先日、Dominoデザイナーヘルプを見ていて Environ 関数をたまたま発見しました。LotusScript を使い始めてほぼ 30 年になるのですが、恥ずかしながらこの関数の存在を知りませんでした。

今回は備忘録を兼ねて Environ 関数についてまとめます。


Environ 関数

Windows の環境変数を取得する関数です。構文は次の通り。

Environ[$] ( { environName | n } )

引数は以下のどちらかを指定します。

1 environName  文字列 環境変数名
n 数値 n 番目の環境変数

戻り値は、Environ$ とした場合は文字列、Environ の場合は Variant 型となります。


環境変数の取得

以前『Windows のテンポラリフォルダの取得』の記事では、Windows API を使う方法を紹介しました。しかし、Environ 関数を使えば、1 行で解決できます。

sTmpFol = Environ$("TEMP")

こんな便利な命令があったんですね。デザイナーヘルプをもっと隅々までチェックしておけばよかったです...。


テンポラリフォルダ以外にもさまざまな環境変数があります。使いそうなものを抜粋すると次の通りです。

TEMP
TMP
テンポラリフォルダ
HOMEDRIVE ホームドライブ
HOMEPATH ホームパス
Path パスの一覧
OneDrive OneDrive フォルダ
windir Windows フォルダ

また、環境変数というだけあって、Windows や PC に関する基本的な情報も取得できます。

USERNAME ユーザ名
COMPUTERNAME コンピュータ名
PROCESSOR_ARCHITEW6432 CPU
PROCESSOR_IDENTIFIER CPU の詳細


環境変数の種類

引数に数値を指定すると n 番目の環境変数が取得できます。これを利用してすべての環境変数を順に確認することができます。

Option Declare

Sub Initialize
   Dim b As Boolean
   Dim i As Integer

   i = 0
   Do
      i = i + 1
      b = ShowEnv_n(i)
   Loop Until b = False
End Sub

Function ShowEnv_n(ByVal viIndex As Integer) As Boolean
   Dim s As String

   s = Environ$(viIndex)
   If s <> "" Then
      MsgBox CStr(viIndex) & ") " & s
      ShowEnv_n = True
   End If
End Function

実行すると、次のようになります。


上記のサンプルのように、引数に数値を指定した場合は「環境変数名=環境変数の値」を返します。引数に環境変数名を指定すると値だけを返すので注意してください。


2025/10/13

クラス化に挑戦: #14)クラス化とコメント

クラス化は開発効率を高めるために有効な開発手法となるのですが、コメントの書き方でさらに効果を高めることが可能です。


メソッドのコメント

一般的に通常の関数には関数の機能や使い方をヘッダコメントに記述します。クラスのメソッドも同様です。以下のコードは、前回 Location クラスに追加した CalcDistance メソッドにコメントを追加した例です。メソッドの使い方、引数の型と説明、戻り値、必要に応じて利用時の注意などを記載します。

%REM
自身の GPS 座標と引数で指定した座標との距離(m)を返します。

◆ 引数
voDestination Location 距離を求める GPS 座標

◆ データ型(戻り値)  Double
%END REM

   Public Function CalcDistance(voDestination As Location) As Double
      CalcDistance = distance(zdLatitude, zdLongitude, voDestination.Latitude, voDestination.Longitude)
   End Function

このようにメソッドの直上に記述すると、デザイナーでメソッドを使用する際にポップアップヘルプとして表示されます。

クラスの利用者が欲するコメントを書いておくと、それだけで開発がすすめられます。ただのコード内のコメントではなくなりますので、記述するモチベーションが上がりますね。


プロパティのコメント

プロパティの定義は Get / Set の 2 つのモジュールに分かれます。コーディング上はそれぞれコメントを記述できますが、ポップアップヘルプに表示されるのは先に記述されたコメントとなります。ですので Get と Set のモジュールは連続して記述してその上にコメントを書くといいでしょう。

%REM
検索キーワードを表します。

◆ データ型  String
◆ アクセス  取得、設定
%END REM

   Public Property Get Keyword As String
      Keyword = zsKeyword
   End Property

   Public Property Set Keyword As String
      zsKeyword = Keyword
   End Property


クラスのコメント

クラス定義の上に記述したコメントがポップアップヘルプとして利用されます。このヘルプはクラス定義に対してと New でそのクラスを選んだ場合とで共通です。


ですので、記述するコメントはクラスの機能とオブジェクトの作成方法である New の引数を記述しておきましょう。また、このクラスで必要となる他の他の設計要素についても記述すると間違いなく利用できますね。

%REM
Location クラス ( lsGoogleMap ライブラリ )
---------------------------------------------------
GPS の位置情報を管理します
 
◆ オブジェクトの作成 ( New の引数 )
vdLatitude      Double   緯度
vdLongitude  Double   経度

◆ 関連設計要素
  (なし)
%END REM

Public Class Location
         ・・・
   Public Sub New(ByVal vdLatitude As Double, ByVal vdLongitude As Double)
         ・・・
   End Sub
         ・・・
End Class


まとめ

今回はクラスライブラリのコメントと効果的な使い方についてまとめました。汎用的なクラスになればなるほど、自分以外の開発者が使うことになるので、それを意識してわかりやすく、使いやすいコメントを記述するようにしましょう。


前回 クラス化に挑戦