2025/07/02

Notes - Excel 連携:#55)指定した Pixel 数で画像を出力する方法

前回は Export メソッドで画像を出力した際に希望通りのサイズとならないことがある現象(以下、誤差といいます)に関して、詳細な調査を行いました。

発見できた事実は、

  1. 縦と横で誤差が発生する条件が違う
  2. Point(実数)とPixel(整数)間の変換で発生する丸め誤差ではない
  3. 出力画像形式により、誤差が発生する条件が違う

という結果になりました。なぜこのような仕様(?)になっているのかわかりませんが、画像ファイルフォーマットの仕様や画像形式ごとにプログラマが違うなどが原因ではないかと想定します。


対応方針

前回、掲載したグラフから誤差が発生するパターンがつかめそうなのですが、いつ仕様の調整が入るかわかりません。そこで、特性に合わせた対応にするのではなく、出力画像が希望通りのサイズとなっているか確認し、誤差があったら調整して再出力する方式とします。

あまり美しい対応とは言えませんが、ご了承ください...


サンプルプログラム

画像として保存する新しい関数を作成しました。引数は、

  1. 画像化する Chart が配置された Shape オブジェクト
  2. 保存ファイル名
  3. 画像サイズ横(Pixel)
  4. 画像サイズ縦(Pixel)

です。

Function xSaveAsPicture_Px(voShape As Variant, ByVal vsFileName As String, ByVal viX As Integer, ByVal viY As Integer)
   Dim ns As New NotesSession
   Dim nst As NotesStream
   Dim iX As Integer
   Dim iY As Integer
   Dim iType As Integer
   Dim bOK As Boolean

   Dim dX_Org As Double '元の画像の幅(ポイント)
   Dim dY_Org As Double '元の画像の高さ(ポイント)
   Dim dX_Out As Double '出力する画像の幅(ポイント)
   Dim dY_Out As Double '出力する画像の高さ(ポイント)

   '元のサイズ取得
   dX_Org = voShape.Width
   dY_Org = voShape.Height

   '希望の画素数にリサイズ
   dX_Out = dX_Org * (PixcelToPoint(viX)/dX_Org)
   dY_Out = dY_Org * (PixcelToPoint(viY)/dY_Org)
   voShape.Width = dX_Out
   voShape.Height = dY_Out

   '画像として保存し、サイズを検証
   bOK = False
   Do
      '画像として保存
      Call voShape.Chart.Export(vsFileName)

      '出力された画像の種類とサイズを取得
      Set nst = ns.CreateStream()
      Call nst.Open(vsFileName)
      iType = xGetImageFileInfo(nst, iX, iY)
      Call nst.Close()

      '出力された画像の検証
      If iType = DXL_Image_Unknown Then
         'サポートできない画像なのでこれで OK とする
         bOK = True
      Else
         'サイズの確認
         If iX = viX And iY = viY Then
            '希望通りのサイズ
            bOK = True
         Else
            'サイズが違う
            If iX < viX Then
               '小さい場合は 0.5 point 加算
               voShape.Width = voShape.Width + 0.5
            ElseIf iX > viX Then
               '大きい場合は 0.5 point 減算
               voShape.Width = voShape.Width - 0.5
            End If
            If iY < viY Then
               '小さい場合は 0.5 point 加算
               voShape.Height = voShape.Height + 0.5
            ElseIf iY > viY Then
               '大きい場合は 0.5 point 減算
               voShape.Height = voShape.Height - 0.5
            End If
         End If
      End If
   Loop Until bOK = True

   '元のサイズに戻す
   voShape.Width = dX_Org
   voShape.Height = dY_Org
End Function

ポイントは Do ~ Loop の中です。画像を出力し、そのサイズを確認、希望通りになっていなければサイズを微調整して再出力するようにしています。微調整は 1 Pixel 分の 0.75 Point でも問題ないとは思うのですが、特性が完全につかめていないので、0.5 Point と少し遠慮しています。

なお、今回のサンプルも前回同様に『DXL Step-by-Step:#13)イメージの形式とサイズの取得』で掲載した、定数と 2 つの関数を流用しています。また対応していない画像形式に対しては、サイズのチェックを行わず、初回の出力をそのまま採用しています。


テスト結果

この関数を利用して、ある画像を 32 ~ 48 Pixel まで変換してみました。結果は、対応している画像 jpg, gif, png では、指定通りのサイズが出力されるようになりました。そして、対応していない bmp では、一部希望通りとなっていないサイズが発生しています。

通常使用する画像形式は正しく出力できることが確認できました。今後新たな問題は発覚しないことを祈りつつ、指定した Pixel 数で画像を出力する関数はこれで完成とします。


前回 Notes - Excel 連携


2025/07/01

Notes - Excel 連携:#54)指定した Pixel 数で画像が出力できない !?

#52)画像のリサイズ』で、既存の画像ファイルをリサイズして再保存する方法を紹介しました。出力画像の縦横のサイズを Pixel で指定する仕様だったのですが、先日、希望通りのサイズにならない現象に出くわしました。誤差が原因だろうと、『#40)訂正 - 名前アイコン生成 』で紹介した、0.5 Point ずらす技を使いましたが、それでも、100% 改善することができませんでした。

そこで改めて、希望の Pixel と出力される画像サイズを検証してみました。想定外の結果になったので、検証してよかったのですが、#40 で訂正した内容のさらなる訂正記事のような状態となります...。ご了承ください。

 

検証方法

まずは、改めて Excel の挙動を確認します。

1000 x 1000 Pixel の画像を用意して、それをリサイズして再保存し、保存された画像ファイルのサイズをチェックします。リサイズは 760 Point から 770 Point まで 0.1 刻みで変化させます。Pixel で言うと 0.075 刻みとなるので、今回の症状を確実に捕まえられるという算段です。なお、縦横とも同じ Point を指定した正方形の画像が出力し、縦横の挙動の違いも調査します。

Notes - Excel 連携の連載ですので、参考までではありますが、検証プログラムを掲載します。調査対象の画像形式は、asFmt にセットしている通り、jpg, gif, png の 3 種類です。

Option Declare
Use "lsXls"

Public Const DXL_Image_Unknown = 0
Public Const DXL_Image_JPEG = 1
Public Const DXL_Image_GIF = 2
Public Const DXL_Image_PNG = 3

Sub Initialize
   Dim oXls As Variant
   Dim oSheet As Variant
   Dim oShape As Variant
   Dim oImage As Variant
   Dim sFN As String
   Dim ns As New NotesSession
   Dim nst As NotesStream
   Dim iType As Integer
   Dim iSizeX As Integer
   Dim iSizeY As Integer
   Dim asFmt(2) As String
   Dim i As Integer
   Dim iRow As Integer

   asFmt(0) = "jpg"
   asFmt(1) = "gif"
   asFmt(2) = "png"

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

   '画像サイズ取得
   Dim dX As Double '画像のサイズ(ポイント)

   'Chart オブジェクトを画像の大きさで作成
   Set oShape = xAddChart(oSheet, 750, 750)

   'Chart 上に画像ファイルを読み込み
   sFN = "C:\Sample.jpg"
   Set oImage = oShape.Chart.Shapes.AddPicture(sFN, msoFalse, msoTrue, 0, 0, -1, -1)

   'リサイズのテスト
   oSheet.Cells(1, 1).Value = "Point"
   oSheet.Cells(1, 2).Value = "Pixcel"
   For i = 0 To UBound(asFmt)
      oSheet.Cells(1, (i*2)+3).Value = "x(" & asFmt(i) & ")"
      oSheet.Cells(1, (i*2)+4).Value = "y(" & asFmt(i) & ")"

      iRow = 1
      For dX = 760 To 770 Step 0.1
         iRow = iRow + 1

         '初回のループのみ Point と 単純変換した Pixel を記録
         If i = 0 Then
            oSheet.Cells(iRow, 1).Value = dX
            oSheet.Cells(iRow, 2).Value = dX / 0.75
         End If

         'リサイズ
         oShape.Width = dX
         oShape.Height = dX

         'リサイズ画像の保存
         sFN = "C:\Resized_" & CStr(CInt(dX*10)) & "." & asFmt(i)
         Call xSaveAsPicture(oShape, sFN)

         '保存した画像サイズの確認
         Set nst = ns.CreateStream()
         Call nst.Open(sFN)
         iType = xGetImageFileInfo(nst, iSizeX, iSizeY)
         Call nst.Close()

         '画像サイズを Excel シートに記録
         oSheet.Cells(iRow, (i*2)+3).Value = iSizeX
         oSheet.Cells(iRow, (i*2)+4).Value = iSizeY
      Next
   Next

   oXls.Visible = True
End Sub

lsXls ライブラリはこれまでの連載で使用してきたライブラリです。また、画像サイズの取得方法として、別の連載『DXL Step-by-Step:#13)イメージの形式とサイズの取得』で掲載した、定数と xGetImageFileInfo、xGetImageFileType の 2 つの関数を流用しています。


実行結果の確認

検証プログラムを実行すると Excel シート上に結果が表示されます(図はセル書式セット後)。

このままでは傾向がつかめないので、Excel でグラフ化します。まずは、jpg 画像の結果は次の通りです。横軸が Point で、縦軸が Pixel です。

この結果より、2 つのことがわかります。

まず、リサイズ後の画像の横幅が青で縦が赤なのですが、ずれていることがわかります。縦横で同じ Point を指定したにもかかわらず、結果に差が出るということは、Pixel に丸める際に、縦横で仕様に差があるということです。

2 点目は少々厄介です。グレーの線は Point を単純に Pixel に変換したもので、実数なので直線になっています。その ± 0.5 が上下の薄いグレーの線です。この範囲内に入っているなら、四捨五入など丸め誤差の範囲と判断できるのですが、上側にはみ出ている箇所があります。この結果より、丸め処理だけで解決できる問題ではないと判定できます。また、グラフの波形が特定のパターンを示しているので、論理的な ”仕様” と考えられます。

続いて、別の画像形式の結果をグラフ化してみました。gif は jpg と同じ傾向だったのですが、png 形式では横幅の結果だけ微妙に違っていました。


対策は次回

これらの結果より、指定した Pixel 数で正確に画像を出力するためには、出力画像タイプごとの特性を把握したうえで、それに応じた微調整を行う必要があります。なぜこういった仕様になっているかは不明ですが、コーティングする上で大変悩ましい状況になりました。

ただ、少々長くなったので、その対策については次回にまとめます。


前回 Notes - Excel 連携 次回


2025/06/30

LotusScript でメール送信 ② - フォームの送信とは?

前回は、LotusScript でメールを送信する基本的な方法を紹介しました。今回はその続きなのですが、テーマは構文の最初の引数 attachForm です。

Call notesDocument .Send( attachForm [, recipients ] )

1 attachForm BooleanTrue:フォームを送信
False:フォームを送信しない
2 recipients  文字列
文字列リスト
省略可能。
メールの宛先です。


テストフォームの準備

まずは、単純なフォームを用意します。

メールの宛先と件名、本文のリッチテキストを配置します。これらのフィールド名は『@MailSend の動作 ①』で紹介した予約フィールド名で作成します。

また、フォーム下部には、テスト用にメール配信とは関係のないフィールドも配置します。

アクションボタン[メール送信]には、次の LotusScript を記述します。画面上の文書を NotesUIDocument から取得して、メール送信します。Send メソッドコール時には、今回のテーマである attachForm に True を指定しています。

Sub Click(Source As Button)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument

   '現在の文書を取得
   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   'メールを送信
   Call nd.Send(True)
End Sub

既存文書内のフィールドを利用してメールするパターンですので、前回 よりシンプルなコードになっていますね。


フォーム送信の動作

フォームが完成したらテストデータを作成して、[メール送信]ボタンをクリックします。

すると、宛先に設定したユーザにメールが届きます。そのメールを開くと、送信した文書と全く同じ画面が開きます。

その状態で、データベースのプロパティを確認するとメールボックスを指しています。メールボックス内でありながら、アプリの画面が開いていることがわかります。

このように、フォーム送信機能は、メール内にフォーム設計を含めて送信する機能です。


フォーム送信利用時の注意

届いたメール上部には[メール送信]ボタンが表示されており、クリックするとサンプルフォームと同様にメールが送信されます。このように、フォームの送信ではフォームの見た目だけでなく、アクションボタンなどの機能までもが送信されます。

また、送信されるのはフォームのみとなる点も注意が必要です。例えば、フォームがスクリプトライブラリを参照していたとします。

このフォームの文書をフォーム付きでメール送信した場合、受け取ったユーザがメールを開くと、ライブラリのロードエラーが発生します。これはメールボックス内にスクリプトライブラリが存在しないからですね。

フォーム付きでメールを送信する場合、メール DB 内で確実に表示 / 動作するよう、フォームはシンプルな構造にしておく必要があります。


まとめ

今回は、Send メソッドの attachForm 引数を使用した、フォーム付きメール送信について紹介しました。フォームに配置されているボタンなどのプログラムもメールで通知されます。また、届いたメールのプロパティを確認すると、フォーム上に配置したフィールドも配置されています。これらを利用すれば、簡単なアンケートなど、さまざまな ”機能付きメール” を実現することができることがわかりますね。

今では、HTML メールのように見た目がよく、多少の機能がついたメールは普通になりましたが、ノーツは 30 年前からフツウに実現できていました。ノーツってすごいですよね。


2025/06/24

LotusScript でメール送信 ①

先日、@関数を使用した メール送信の方法についてまとめました。

今回は LotusScript でメールを送信する方法についてまとめます。


構文

LotusScript でメールを送信するには、NotesDocument クラスの Send メソッドを使用します。まずは、構文を確認しましょう。

Call notesDocument .Send( attachForm [, recipients ] )

引数は役割は次の通りです。

1 attachForm BooleanTrue:フォームを送信
False:フォームを送信しない
2 recipients  文字列
文字列リスト
省略可能。
メールの宛先です。


一般的な使い方

Send メソッドを使用した一般的はメール送信のプログラムは次の通りです。

Sub Click(Source As Button)
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim ndMail As NotesDocument

   Set ndb = ns.CurrentDatabase

   '送信する文書を新規作成
   Set ndMail = ndb.CreateDocument

   ndMail.Form = "Memo"     'フォームを指定
   ndMail.SendTo = "Masayuki Hama/NCOsaka"    '宛先をセット
   ndMail.Subject = "LotusScript でメール送信 ①"    '件名をセット

   '本文をセット
   ndMail.Body = "これは『LotusScript でメール送信 ①』のサンプルで送信したメールです。"

   'メールを送信
   Call ndMail.Send(False)
End Sub

処理の流れは単純です。新規文書を作成して、フォーム、宛先、件名をセットします。本文はプレーンテキストでよいのであれば文字列として与えるだけで OK です。送信する内容が出来上がったら Send メソッドをコールします。

届いたメールは次の通りです。指定した宛先とメールの内容がセットされています。

基本的な動作の仕組みは、@MailSend の引数なしのパターン と同じで、SendTo など文書内の予約フィールドを利用してメールが送信されます。


ちなみに、構文に記載した通り、Send メソッドの recipients 引数で宛先を指定できます。SendTo フィールドと 2 通りの宛先指定方法が提供されています。両方が指定されている場合、recipients 引数が優先され、SendTo フィールドは無視されます。動作がわかりにくくなるのでどちらか一方になるようにしたほうがいいですね。


メール送信の動作

@関数では画面上に存在するフィールドは、メール配信に関係していなくてもメール内に保持されました。上記プログラムでは、CreateDocument でメール文書を別途生成しているので、メール配信に不必要なフィールドがメールとともに配送されることはありません。無用な漏洩を止めることができますね。

また、Send メソッドでメールを送信すると、その内容はコピーされて、サーバ上のメールルータに渡されます。よって、保存しなくてもメールが送信でき、新規作成した文書はスクリプトの終了とともに消滅します。


フォームの指定

サンプルプログラムでは、Form フィールドに "Memo" と設定していました。この設定がなくてもメールとしては正常に送信されます。しかし、届いたメールには Form フィールドが作成されません。

ただ、Form フィールドがなくてもメールは通常通りのフォームで参照できます。これは、Memo フォームがデフォルトフォームに設定されているからです。

このように、たとえ忘れていても、正しく参照できるのですが、プログラムの意図を明示する観点からも設定しておくべきです。


2025/06/22

DominoHub 2025 Tokyo レポート

6 月 19 日・20 日の 2 日間にわたり、DominoHub 2025 Tokyo を開催いたしました。ご参加いただいた皆様に、心より御礼申し上げます。

今年のテーマは「#DX(Domino Experience)〜Dominoの真実を語る〜」。Notes/Domino の実力や可能性を紹介するセッションをはじめ、実際の活用事例、さらには開催週にリリースされたばかりの 14.5 に関する最新情報 など、充実した内容となりました。

今回のイベントが、皆様のビジネスに少しでもお役立ていただける機会となったのであれば幸いです。

イベントの内容を詳細にレポートしたいのですが、スタッフとしての参加だったので、セッションは、ほとんど聞くことができませんでした(面白そうなテーマが盛りだくさんだったので、残念でなりません)。そこで、今回は、私が担当した部分だけのまとめとさせていただきます。


オンラインセッション

初日は、『Notes をもっと魅力的に! 今すぐ使える Excel 連携術』と題して、オンラインセッションを行いました。このブログで連載 Notes - Excel 連携 を再編集して、Notes データから帳票やグラフを作成する方法の紹介でした。

また、後半は、Excel で作成したグラフ画像をノーツ文書に書き戻し、見栄のいいレポートを作成する方法です。こちらは DXL Step-by-Step で連載している DXL(Domino XML Language)の事例で、昨年リリースした dxlSuite for LotusScript の利用例を紹介させていただきました。

Excel 連携と DXL を使えば、Notes のニガテを克服でき、ノーツをもっとリッチに、もっと魅力的にできます。今回のセッションでは、具体的なコードとともに解説を加えました。また全コードは資料内に掲載しています。1か月間は DominoHub のポータルサイトで、セッション動画と資料が掲載されていますので、ご興味のある方はご参照ください。すでにイベントは終了していますが、後付けで登録いただいても参照可能です。


展示ブース

二日目はセミナー会場前で展示ブースを担当しました。これまで作成した数々のアプリケーションを一堂に展示しました(私が開発していないアプリも含まれます)。

AI 連携や Nomad アプリ、DXL や Excel 連携のアプリケーションを中心に、Notes/Domino の可能性を感じていただけるようなアプリが紹介できたかと思います。

また、会場セッションの合間に展示ブースを紹介する機会がありました。そこでは上記リストにない新しいアプリを紹介しました。

OpenAI 社の画像生成 AI である、Dall-e3 の API を利用して、Notes アプリのアイコンを生成させるアプリです。Dall-e3 で正方形の画像を生成すると 1024x1024 の画像となるので、それを Excel で 64x64 の画像に変換、DXL でアイコンとして nsf に登録する機能を持ちます。

Dall-e3 の API や DXL で DB アイコンを操作する方法については、いずれこのブログでまとめたいと思います。


さいごに

今年の会場セッションは CIRQ -シルク- 表参道 で行いました。白を基調とした上品な会場で清潔感がありました。また、スタッフはとても丁寧で気持ちが良い対応でした。そのおかげで、これまでにない上質なイベントとなったと思っています。

今後もこのような会場でイベントを行いたい思った反面、もっとスムーズに運営しなければ...と感じました(DominoHub は HCL Ambassador を中心に有志で運営しています)。


2025/06/17

ODS の強制

本日、Notes/Domino 14.5 がリリースされ、先ほどまでリリースのウェビナーが配信されていました。14.5 の新機能についてはこれから検証して少しずつまとめたいと思います。

ところで、Notes/Domino 11.0.x のサポート終了が近いこともあり、各所でバージョンアップが行われています。新バージョンがリリースされたこともあり、この流れはしばらく続くと思います。

このような状況で、複数の環境をサポートしていると nsf ファイルのやり取りが発生し、ODS を意識する機会が多くあります。しかも、先日 ODS を間違え、現場で開けないというミスを起こしたことがあります。今回は、今後そのようなことがないようにするための戒めと手順の整理のための記事となります。

前回、突然 ODS の話を始めたのは、この記事の情報を記録しておきたかったためなんです...


ODS の強制

データベースを他のユーザにファイルとして渡す場合、相手の Notes クライアントが対応している ODS にしておかないと、開くことができません。

以下は前回紹介した Notes 14 でコピーした ODS 55 の nsf を Notes 9 で開いた場合のエラーです。

これに対応する機能が、ODS の強制です。Notes クライアントでデータベースをコピーするとき、拡張子を指定すると特定の ODS にすることができます。例えば、Notes 14 を使用しても .ns9 と指定してコピーすると ODS は 52 となります。

各 ODS と指定すべき拡張子の関係は次の通りです。

ODS 拡張子
55 NS12
53 NS10
52 NS9
51 N85
48 NS8
43 NS7, NS6

そのままだと、ノーツデータベースとして OS が認識しません。また、ノーツクライアントのアプリケーションを開くダイアログにも表示されません。

コピーした後に、エクスプローラなどで拡張子を nsf に戻しておきましょう。


2025/06/16

ODS(On Disk Structure )とは?

ODS とは?

ODS とは、On Disk Structure のことで、Notes/Domino データベースの内部構造を表すバージョン番号のことです。Notes クラアインとの場合、データベースのプロパティの[情報]タブに表示されます。

また、Administrator クライアントの[ファイル]タブを使えば、一覧で確認できます。


Notes/Domino のバージョンと ODS

当たり前ではありますが、新しい Notes/Domino ほど、新しい ODS をサポートします。整理すると次の通りとなります。デフォルトは、通常新規作成したデータベースで採用される ODS で、最大は、そのバージョンでアクセスできる最大の ODS バージョンです。

Notes/Domino デフォルト 最大
14.0.x 55 55
12.0.x 52 55
11.0.x, 10.0.x 52 53
9.0.1 43 52
9.0, 8.5.x 43 51
8.0.x 43 48
7.0.x, 6.x.x 43 43

例えば 9.0.x を利用している場合、ODS 52 まではアクセスできますが、ODS 55 のデータベースにはアクセスできません。例えば、以下の図は、ODS 55 の nsf ファイルを Notes  9.0.1 で開こうとした場合のエラーです。

ただし、Domino 14 サーバ上にある ODS 55 のデータベースを Notes 9 クライアントがアクセスすることは可能です。サーバ上のデータベースは、クライアントが直接アクセスするのではなく、サーバを介してアクセスするためです。

なお、Notes/Domino は上位互換性があり、新しい Notes/Domino であれば、古いバージョンで使用されていた ODS のデータベースにアクセスが可能です。


ODS と機能

ODS のバージョンが高いほど、機能が多く、制限が少なく、パフォーマンスが高くなります。例えば、1データベースの最大容量は、ODS 52 では 64 GB なのですが、 ODS 53 では 256 GB まで拡張されています。

現時点で最新の ODS である 55 の新機能は以下のリンクにまとまっています。

Domino 12 の ODS 55 で導入された拡張機能

リンクのページによると、ACL のエントリ数が 950 件から 65,535 件まで増加、サマリーフィールドのサイズが 64 KB から 16 MB まで拡張されたと記載があります。制限が緩和されること自体は良いことなのですが、利用に当たって注意が記載されています。

  • 新機能を利用するためには設定が必要である
  • 古いバージョンではエラーが出るなどアクセスが制限される場合がある

このように下位のバージョンに対して互換性のない機能は、通常、有効にするために設定が必要になっており、ODS を上げると自動的に適用されるわけではありません。管理者は、社内環境で安心して利用できる機能か確認したうえで採用する必要があります。


2025/06/10

DXL Step-by-Step:#58)ノード操作 ⑪ - ノードの置き換え

ノード操作シリーズの最終回は、ノードの置き換え操作についてまとめます。


置き換え操作の必要性

例えば、リッチテキスト内のプレーンテキストに装飾をする場合を考えます。

それぞれを DXL で表現すると下図のようになるのですが、装飾を加えるためには、プレーンテキストを run や font ノードで装飾されたノードに置き換えることになります。


ReplaceChild メソッドの注意点

このような操作を行う際に利用するのが、ReplaceChild メソッドです。

ReplaceChild (NotesDOMNode - LotusScript®)

構文は以下の通りです。

Set notesDOMNode = notesDOMNode .ReplaceChild( newChild , oldChild )

引数には、置き換えるノードの新・旧を指定する仕様となっています。そのためか、メソッドをコールするノードは、RemoveChild メソッドと同じく、親のノードから実行する仕様になっています。NotesDOM??? クラスの設計者の親はよほど偉大な方なのかもしれませんね...

このヘルプを見ればわかるのですが、このメソッドには重大な注意点があります。それは戻り値が、置換されたノードであることです。


ReplaceChild メソッドの挙動

ヘルプを読んだだけではわかりにくいので、最初に記載したプレーンテキストを装飾する場合を例に、挙動を順に確認しましょう。

ReplaceChild 実行前はこのような状態となります。

紫色のノードが置換元のプレーンテキストのノードで、par ノードの配下に配置されています。置換先のノードは Create ??? Node メソッドを利用して新規で作成し準備します。これはノードの新規作成と同じ操作ですね。

この置換元と置換先のノードを引数に、親のノードである par ノードから ReplaceChild メソッドを実行することになります。実行後の状態は下図のような状態となります。

新規で作成したノードが par ノード配下に配置され、希望通りの結果になっています。問題は、はじき出されたノードです。このノードは、新規作成したてのノードと同じく、DXL ツリーには属さない宙に浮いた状態となっているのですが、このノードが ReplaceChild メソッドの戻り値となります。

DXL のコーディングをしていると、この挙動がとても煩わしく感じます。例えば、ノードを置き換えた後、後続の処理でそのノードを使いたくても、プログラム上は行方不明になってしまうからです。


ノードを置き換える関数

なぜこのようなインプリになっているか真意はわかりかねますが、この仕様では DXL を自由自在な操作に支障をきたします。そこで、次のような関数を作成して、標準メソッドの不便を解消しています。

Function xReplaceNode(vdnNew As NotesDOMNode, vdnOld As NotesDOMNode) As NotesDOMNode
   Dim dnParent As NotesDOMNode
   Dim dnNext As NotesDOMNode
   Dim dnPrev As NotesDOMNode

   ' 元の位置を保存
   Set dnNext = vdnOld.NextSibling           ’ 次のノード
   Set dnPrev = vdnOld.PreviousSibling  ' 手前のノード
   Set dnParent = vdnOld.ParentNode     ' 親のノード

   ' 置き換え
   Call dnParent.ReplaceChild(vdnNew, vdnOld)

   ' 行方不明の捜索
   If dnPrev.IsNull = False Then
      '手前のノードが存在したのでその次のノードが置換されたノード
      Set xReplaceNode = dnPrev.NextSibling
   ElseIf dnNext.IsNull = False Then
      '手前のノードがないので、次のノードの1つ前が置換されたノード
      Set xReplaceNode = dnNext.PreviousSibling
   Else
      '前後にノードがないので一人っ子、置換後も最初のノードが置換されたノード
      Set xReplaceNode = dnParent.FirstChild
   End If
End Function

関数内の処理のポイントは、置換前に前後と親ノードを取得しておく点です。これで、置換後、自分自身が行方不明になっても、前後関係や親から自分探しができるというわけです。


ノード操作まとめ

昨年の DominoHub 2024 のオンラインセッション『 ”DXL” でリッチテキスト縦横無尽』でご紹介した DXL ノード操作をまとめたこのシリーズですが、今回の ⑪ で終了です。ここで紹介したテクニックは、DXL を使ったプログラミングをする中で必要となった知識や関数たちで、実体験に即したものをまとめました。

これらテクニックを利用すれば、DXL を自由自裁、縦横無尽に操作できるようになります。

前回 DXL Step-by-Step


2025/05/27

DXL Step-by-Step:#57)ノード操作 ⑩ - ノードの複製

今回はノードを複製する方法についてまとめます。同じフォーマットの表を複数個所に配置するなど応用方法はさまざまですね。


ノードの複製

ノードを複製するには、 Clone メソッドを使用します。

Clone (NotesDOMNode - LotusScript®)

前回紹介した RemoveChild メソッドと同様で、このメソッドも NotesDOMNode クラスのメソッドとなります。ですので、それを継承している NotesDOMEmenetNode でも使用できます。

構文は次の通りです。

Set notesDOMNode = notesDOMNode .Clone( deepClone )

引数 deepClone で True を指定するとサブノードを含めて複製します。False だとそのノードだけとなるのですが、DXL ツリーの階層構造を考えると、通常は True での利用となりますね。


複製したノードの状態

CreateElementNode メソッドで新規作成したノードは、DXL ツリーには属さず宙に浮いたような状態であると #51)ノード操作 ④ - ノードの新規作成 で紹介しました。複製したノードも同様の状態となり、ParentNode が存在しない状態となります。

DXL ツリーに配置するためには、AppendChild メソッドや #52)ノード操作 ⑤ - ノードを挿入する方法 で紹介した方法を追加って、希望する位置に配置する必要があります。


複製時の注意

例えば、リッチテキストフィールド直下にある 2 つ目の表を複製して一番後ろに挿入したいとします。そのサンプルは次の通りです( #55)ノード操作 ⑧ - ノードの検索 で紹介した関数を使用)。

      ・・・
   'リッチテキスト直下の2つ目のテーブルを取得
   Dim denTbl As NotesDOMElementNode
   Set denTbl = xGetNthChildByName(denRT, "table", 2)

   'テーブルの後ろの段落を得取得
   Dim denPar As NotesDOMElementNode
   Set denPar = xGetNextSiblingByName(denTbl, "par")

   'テーブルを複製して追加
   Dim denNew As NotesDOMElementNode
   Set denNew = denTbl.Clone(True)
   Call denTbl.ParentNode.AppendChild(denNew)

   'テーブルの後ろに段落を追加
   Set denNew = denPar.Clone(True)
   Call denTbl.ParentNode.AppendChild(denNew)
      ・・・

xGetNthChildByName(denRT, "table", 2) で 2 つ目の表を取得して、denTbl.Clone(True) で複製しています。ただ、ポイントはココではありません。表を配置後、その後ろに段落(par ノード)を配置しています。table ノードは前後に par ノードが必要となるための対応なのですが、ノードをコピーして配置するだけではなく、前後関係に配慮して DXL ツリーとして破綻しないよう注意することが重要です。


前回 DXL Step-by-Step 次回


2025/05/19

環境変数の利用と注意

先日、notes.ini 利用したアプリでちょっとしたトラブルがあったので、まとめておきます。

notes.ini ファイルは、ノーツの動作設定が記録されているファイルで、ノーツクライアントのプログラムディレクトリに存在します。セットアップするなどノーツ担当者であれば知らないはずはないファイルですね。


notes.ini ファイルのアクセス

アプリ開発では、notes.ini にアクセスする機能が提供されています。

@関数 LotusScript(NotesSessionクラス)
設定 @Environment SetEnvironmentVar メソッド
取得 @Environment GetEnvironmentString メソッド

例えば、SrcStr フィールドの値を notes.ini に Denaoshi という名前で設定するには、次のように記述します。

@関数 @Environment("Denaoshi"; SrcStr)
LotusScript Dim ns As New NotesSession
Dim nuiw As New NotesUIWorkspace
Dim nuid As NotesUIDocument
Dim sStr As String

Set nuid = nuiw.CurrentDocument
sStr = nuid.FieldGetText("SrcStr")
Call ns.SetEnvironmentVar("Denaoshi", sStr)

実行すると、以下のように notes.ini に新しいエントリ "$Denaoshi" が追加され、値が設定されます。すでにエントリがある場合には、値のみ更新します。

参考までの情報ですが、全角文字の前に付加されている □ は、LMBCS(Lotus Multi-Byte Character Set)の日本語を表すコード(0x10)です。LMBCS の日本語は、0x10 + Shift-JIS の 3 バイトで構成されます。バイナリエディタで ”出” の文字を確認すると次のようになります(”出” の文字コードは 0x8F6F)。

notes.ini ファイルから値を取得して、IniStr フィールドにセットする方法は、次の通りです。

@関数 xIni := @Environment("Denaoshi");
@SetField("IniStr"; xIni)
LotusScript Dim ns As New NotesSession
Dim nuiw As New NotesUIWorkspace
Dim nuid As NotesUIDocument
Dim sIni As String

Set nuid = nuiw.CurrentDocument
sIni = ns.GetEnvironmentString("Denaoshi")
Call nuid.FieldSetText("IniStr", sIni)

取得した値には LMBCS の □ は付加されず、セットした通りの値が取得できます。LMBCS については notes.ini をテキストエディタなどで直接開いたときにだけ気にすればいいということですね。


notes.ini の使い方

上記の通り、notes.ini に値を入出力する方法を紹介しました。これを ”環境変数” と呼び、その名の通り、変数のように利用できます。notes.ini はノーツクライアントに一つですから、アプリをまたがっても値の共有が可能となる点がポイントです。

ワークフローアプリを例にすると、前回選択した上司を保持させ、別のワークフローアプリでそれを上司として初期表示するというような使い方ができます。notes.ini を使えば、簡単な仕組みで大きな効果を得ることができます。

ただ、次にその値を利用するタイミングが、明日なのか1年後なのか定かではありません。先の例のように人の情報の場合、退職などによりノーツユーザとして存在しない場合も考えられます。このような不整合に備え、チェックや再選択させる機能などの用意が必要です。


notes.ini 利用時の注意

最後に先日発生したトラブルについて紹介します。

ワークフロー系のアプリで、質問形式で申請内容を確認するウィザード形式のフォームがありました。ウィザードの最後では、必要な申請書を表示して、起票する機能を提供していました。ウィザードで入力した情報を notes.ini に書き込み、申請書起票時に読み込んで初期表示させ、二重入力を省く仕組みでした。

この機能で、ウィザードと申請書で値が変わる現象が発生しました。連携データに全角スペースが含まれていたことが原因でした。実験したところ、全角スペースは notes.ini に出力時は全角のままですが、読み込んだ際には半角スペースに変換されるようです(@関数、LotusScript とも同様)。

また、複数の全角スペースや複数の半角スペースも半角スペース1つに変換されることも発見しました。この結果より、notes.ini から値を読み込む際には、Trim と同等の機能が実行されいると想定できます。


まとめ

今回紹介した環境変数の利用は、うまく使えば非常に便利な機能です。ただ、Trim のような処理が行われている点には注意が必要です。ご利用の際には、環境変数に格納される値についても意識しておくことをおススメします。

また、環境変数はすべてのノーツアプリで共通で使用できます。便利な反面、大量に使用すると、それそれぞれの目的や設定タイミング、どのアプリで利用しているかが不明瞭になり、バグや仕様の複雑化の要因となります。LotusScript でパブリック変数を使用すべきでない理由と同じですね。

利用の際には環境変数の名称や設定される値、設定タイミングなどの仕様を決め、チーム全体で周知することが重要です。