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 でパブリック変数を使用すべきでない理由と同じですね。

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


2025/05/14

@MailSend の動作 ②

@MailSend の紹介です。この関数は、引数なしと引数ありの構文があり、前回は引数なしの動作について説明しました。今回は引数ありのパターンの使い方についてまとめます。


構文

まずは、構文の確認です。

@MailSend( sendTo ; copyTo ; blindCopyTo ; subject ; remark ; bodyFields ; flags )

各引数にセットする値と役割は次の通りです。

1 sendTo 文字列
文字列リスト
メールの宛先
宛先、CC、BCC の 1 つ以上に指定が必要
2 copyTo 文字列
文字列リスト
3 blindCopyTo 文字列
文字列リスト
4 subject 文字列 メールの件名
5 remark 文字列 メールの本文
6 bodyFields 文字列リスト 本文に追加するフィールド
7 flags キーワード メールに関するフラグ


宛先の設定

宛先に設定できるのは、ユーザ名、グループ名です。一人だけに送信する場合は文字列、複数人に送信する場合はリスト値で設定します。宛先、CC、BCC のうちどれか 1 つには指定が必要です。設定しない引数にはからの文字列 ""  を設定します。


件名の設定

メールの件名を文字列で指定します。文字列リストを指定した場合、最初の要素が使用されます。


本文の設定

@MailSend で送信するメールの本文は、プレーンテキストとなります。引数の文字列に @NewLine を含むことで、改行させることができ、体裁を整えることが可能です。


ここまでが一般的な使い方となるので、簡単なサンプルを作成します。

メール送信ボタンの式は次の通りです。

xSubject := "『" + Destination + "』 の申請";
xBody := "新しい申請書を申請しました。";
xBody := xBody + @NewLine + "承認してください。";

@MailSend(Approver; ""; ""; xSubject; xBody)

フォームをプリビューして、メールを送信すると、メールが送信されます。


本文に追加するフィールドの設定

この機能は文書内のフィールドをメール内に埋め込める機能で、リストで複数のフィールドを指定すると、それらすべてを本文に表示します。

xSubject := "『" + Destination + "』 の申請";
xBody := "新しい申請書を申請しました。";
xBody := xBody + @NewLine + "承認してください。";
xBodyFlds := "Date":"Destination":"Purpose":"Amount";

@MailSend(Approver; ""; ""; xSubject; xBody; xBodyFlds)

メール送信ボタンを修正して、メールを送信すると次のようになりました。

指定したフィールドの一部しか表示されいません。しかも、改行も間隔も開けずただ羅列されています。ヘルプによると、数値や日付フィールドには対応していないようです。なかなか使いどころがわからない機能です...。この仕様だと remark にがんばって指定したほうが、自由に体裁が決定できるのでいいですね。


フラグの設定

最後の引数であるフラグは、送信優先度や署名、暗号化など、メール配信の細かな設定ができます。通常のアプリで使いそうなフラグは次の 2 つです。

キーワード 機能
[INCLUDEDOCLINK] @MailSend が実行されたときに開いていた文書の文書リンクを本文の最後に追加します。
ただし、文書は保存されている必要があります。
[RETURNRECEIPT] 受信者がメールを開いたとき、開封確認を送る設定をします。

フラグはリストのように記述すると複数指定することができます。例えば、次のように設定すると、文書リンクと開封確認付きのメールとなります。

xSubject := "『" + Destination + "』 の申請";
xBody := "新しい申請書を申請しました。";
xBody := xBody + @NewLine + "承認してください。" + @NewLine;

@MailSend(Approver; ""; ""; xSubject; xBody; ""; [IncludeDoclink]:[ReturnReceipt])


まとめ

引数付きの @MailSend を使えば、宛先、件名、本文が自由に設定でき、引数なしの場合のようにフィールド名の縛りもありません。また、文書リンク付きのメールを発信できるのでワークフローアプリでも活用できますね。


2025/05/10

@MailSend の動作 ①

Notes アプリ開発とメールの送信は切っても切れない関係といえますね。今回は、式言語でメールを送信する @MailSend について紹介します。


構文

ヘルプによると @MailSend の構文は次のように、引数のあり/なしの 2 通りあります。

@MailSend

@MailSend( sendTo ; copyTo ; blindCopyTo ; subject ; remark ; bodyFields ; [ flags ] )

今回は、前者の引数なしのパターンを紹介します。


引数なしでメール送信できる訳

引数なしということは、宛先も指定しないの? と疑問がわきますが、大丈夫です。この機能は、文書内のフィールド値を使用して送信する機能となっています。

簡単なサンプルとして次のようなフォームを作成します。

フォームをプリビューして、宛先と件名を入力し、[メール送信]ボタンをクリックします。

すると、宛先に指定したユーザのメールボックスにメールが届きます。

とっても簡単ですね。そして仕組みも単純です。

@MailSend を実行する文書にある SendTo が宛先、Subject が件名として使用されます。このように、特定の役割を持ったフィールド名を予約フィールドといいます。


予約フィールド

メールの配信制御に関連する予約フィールドはヘルプに詳細が記載されています。

メールオプションを制御する予約フィールド


通常、メール送信で使用する予約フィールドをまとめると次の通りです。

予約フィールド 役割・使い方
SendTo 宛先(リスト値可)
CopyTo CC(リスト値可)
BlindCopyTo BCC(リスト値可)
Subject メールの件名
Body メールの本文
ReturnReceipt 受信者がメールを開いたとき、開封確認を送る場合 "1" を指定


すべてのフィールドを送信

@MailSend では、送信する文書内のすべてのフィールドをメールで配信されます。例えば、次のようにフィールドを 3 つ追加してメールを送信してみます。

すると届いたメールの文書プロパティでこれらフィールドが確認できます。

Notes メールは、メールとは関係ないフィールドがメール内にあっても、そのまま配信するようです。文書を丸ごと配信する感じですね。いかにもノーツらしい挙動です。


リッチテキストの送信

メールの本文(Body)フィールドはリッチテキストです。これに倣い、作成したフォームの Body フィールドをリッチテキストに変更します。文書を新規作成し、本文を記入して送信しても、届いたメールの本文は空っぽとなります。

リッチテキストは文書を保存しないと @MailSend で送信できない仕様のようです。ただ、文書を保存してから送信するとメールを開く際にエラーが発生します。

これは、送信元の Form フィールドまで含めて送信されているからです。すべてのフィールドが送信される機能の弊害と言えますね。ちなみにメール DB にあわせて、送信元のフォーム名を "Memo" としておくとこのエラーは回避できます。


まとめ

今回は @MailSend の引数なしで使用する方法を紹介しました。引数がないので@式はシンプルになりますが、フィールド名が固定されたり、フォーム名を意識したする必要がありました。また、すべてのフィールドが送信されることもあり、アプリで利用するには制限が多く、活用しづらい機能と言えます。

このような課題を解決するのが引数ありの @MailSend となります。次回、使い方を紹介します。



2025/04/30

DXL Step-by-Step:#56)ノード操作 ⑨ - ノードの削除

今回は DXL ツリーからノードを削除する方法についてまとめます。前回紹介したノードを検索したり、必要なノードを取得する方法で不要なノードを探し出し、削除するという使い方になります。


ノードの削除

ノードを削除するには、 RemoveChild メソッドを使用します。

RemoveChild (NotesDOMNode - LotusScript®)

NotesDOMNode クラスのメソッドですので、それを継承している NotesDOMEmenetNode でも使用できるメソッドとなります。

構文は次の通りで、引数に指定した子ノードを削除するメソッドです。

Set notesDOMNode = notesDOMNode.RemoveChild( child )

ということは、メソッド実行のためには、削除したい子ノードと親ノードが必要となります。親ノードは ParentNode プロパティで取得できますので、次のように記述すると簡単に削除できます。

Call denDel.ParentNode.RemoveChild(denDel)


削除はツリーまるごと!

当たり前かもしれませんが、ノードを削除した場合、配下のノードもまとめて削除されます。例えば、前回紹介した関数を使って2つ目の表を取得して削除すると、列の定義である tablecolumn や行 tablerow、セル tablecell など表とその中身すべてが削除されます。

   'リッチテキスト直下の2つ目のテーブルを取得
   Set denTbl = xGetNthChildByName(denRT, "table", 2)
   '取得したテーブルを削除
   Call denTbl.ParentNode.RemoveChild(denTbl)


 戻り値の状態

最初に紹介した構文にある通り、RemoveChild メソッドには戻り値があり、ヘルプによると削除したノードが返されるそうです。削除したのにどういうこと?と思ってデバッガで確認したところ、ParentNode が存在しないノードになっていました。

これは、ノードを新規作成し AppendChild する前と同じです。DXL のツリー内に存在せず、宙に浮いた状態ということですね。これをどのように活用するのは今一つ見えないですが...


前回 DXL Step-by-Step 次回


2025/04/28

DXL Step-by-Step:#55)ノード操作 ⑧ - ノードの検索

前回、ノードを検索する GetElementsByTagName メソッドを紹介しました。便利な命令なのですが、配下の階層すべてが検索対象となるため、複雑なコンテンツでは希望するノードが取得できない点に注意が必要となります。その解決策の一例として、引数で指定した名称の最初の子ノードを取得する関数を紹介しました。

今回はその続きで、ノード検索で使いそうな関数を一気に紹介します。


最後の子ノードを取得

前回の関数とほぼ同等ですが、最後から最初のノードに向かって順に走査しています。

Function xGetLastChildByName(vdenParent As NotesDOMElementNode, ByVal vsNodeName As String) As NotesDOMElementNode
   Dim dn As NotesDOMNode

   Set dn = vdenParent.LastChild
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               Set xGetLastChildByName = dn
               Exit Function
            End If
         End If
      End If

      Set dn = dn.PreviousSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend
End Function


次のノードを取得

最初が取得できるとその次のノードを取得したくなりますね。最初の表を探してその次の表を取得するという感じです。以下の関数では、引数のノードから兄弟ノードと取得して、走査しています。

Function xGetNextSiblingByName(vdnCur As NotesDOMElementNode, ByVal vsNodeName As String) As NotesDOMNode
   Dim dn As NotesDOMNode

   Set dn = vdnCur.NextSibling
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               Set xGetNextSiblingByName = dn
               Exit Function
            End If
         End If
      End If

      Set dn = dn.NextSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend
End Function


一つ前のノードを取得

続いては、引数のノードより前にあるノードを操作する関数です。PreviousSibling を使って兄弟をさかのぼって走査しています。

Function xGetPrevSiblingByName(vdnCur As NotesDOMElementNode, ByVal vsNodeName As String) As NotesDOMNode
   Dim dn As NotesDOMNode

   Set dn = vdnCur.PreviousSibling
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               Set xGetPrevSiblingByName = dn
               Exit Function
            End If
         End If
      End If

      Set dn = dn.PreviousSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend
End Function


子ノード数を取得

次は、子ノードを走査して、引数で指定した名称の子ノード数を数えて返す関数です。

Function xGetChildCountByName(vdenParent As NotesDOMElementNode, ByVal vsNodeName As String) As Integer
   Dim dn As NotesDOMNode
   Dim i As Integer

   i = 0
   Set dn = vdenParent.FirstChild
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               i = i + 1
            End If
         End If
      End If

      Set dn = dn.NextSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend

   xGetChildCountByName = i
End Function


n 番目の子ノードを取得

子ノード数がわかると位置を指定してノードが取得したくなりますね。次の関数では引数で指定した名称のノードの中から n 番目のノードを返します。

Function xGetNthChildByName(vdenParent As NotesDOMElementNode, ByVal vsNodeName As String, ByVal viNth As Integer) As NotesDOMElementNode
   Dim dn As NotesDOMNode
   Dim i As Integer

   i = 0
   Set dn = vdenParent.FirstChild
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               i = i + 1
               If i = viNth Then
                  Set xGetNthChildByName = dn
                  Exit Function
               End If
            End If
         End If
      End If

      Set dn = dn.NextSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend
End Function


まとめ

前回、今回とノードの検索方法や普段使いで必要となる関数を紹介ました。これら関数を利用すれば、複雑で広大な DXL の海原を自由自裁、縦横無尽に航海できますね。


前回 DXL Step-by-Step


2025/04/18

DXL Step-by-Step:#54)ノード操作 ⑦ - ノードの検索

今回は DXL ツリーからノードを検索する方法についてまとめます。


ノードの検索

GetElementsByTagName メソッドを使用するとノードの検索が可能です。

GetElementsByTagName (NotesDOMElementNode - LotusScript®)

このメソッドの使い方はシンプルで、引数に検索したいノード名を指定するだけです。例えば、以下のプログラムでは、リッチテキストを表す richtext ノードを検索しています。

   Dim ddn As NotesDOMDocumentNode
   Dim denDoc As NotesDOMElementNode
   Dim dnl As NotesDOMNodeList
      ・・・
   '文書取得
   Set denDoc = ddn.DocumentElement

   'リッチテキスト検索
   Set dnl = denDoc.GetElementsByTagName("richtext")

戻り値は、名称が一致したすべてのノードを検索された順序で返します。複数のノードの集合となりますので、NotesDOMNodeList のオブジェクトとなります。GetItem メソッドを使用して、必要なノードを取得できます。

   '最初のリッチテキスト取得
   Set denRT = dnl.GetItem(1)

この動作をサンプルの DXL 使って図にすると次のようになります。

図からもわかる通り、GetElementsByTagName メソッドでは、子ノードだけでなく、配下のすべてのノードが検索対象となるという点がポイントとなります。


子ノードだけを検索したい

リッチテキストでは表の中に表を配置するなど、階層化して複雑なコンテンツを表現できます。例えば、リッチテキストにタブ表を作成して、その中に 2 つの表を配置します。

これを DXL 化すると外側の表(赤線の table ノード)の中に内側の表(紫線の table ノード)が作成されます。

このフィールドに対して GetElementsByTagName を利用すると、すべての表がヒットしてしまいます。要は、検索結果では表の階層関係がわからず、希望した位置の表が取得できているのか判別が難しいのです。このような背景から、子ノードだけを検索したいことがしばしばあります。この要件に対応するメソッドはありませんので、自作しなければなりません。

次の関数は、引数で指定した名称の最初の子ノードを取得することができます。

Function xGetFirstChildByName(vdenParent As NotesDOMElementNode, ByVal vsNodeName As String) As NotesDOMElementNode
   Dim dn As NotesDOMNode

   Set dn = vdenParent.FirstChild
   While Not(dn Is Nothing)
      If dn.IsNull = False Then
         If dn.NodeType = DOMNODETYPE_ELEMENT_NODE Then
            If dn.NodeName = vsNodeName Then
               Set xGetFirstChildByName = dn
               Exit Function
            End If
         End If
      End If

      Set dn = dn.NextSibling
      If dn.IsNull Then
         Set dn = Nothing
      End If
   Wend
End Function


まとめと次回の予告

今回は GetElementsByTagName メソッドでノードの検索方法を紹介しました。フィールドを表す item ノードなど階層化されないノードの場合は有効かつ便利に使用できます。しかし、事例に上げたように階層化されるノードでは、関係が不明瞭になり効果的に利用できません。その対策として、指定した名前の最初の子ノードを取得する関数を紹介しました。

最初のノードが取得できれば、次のノードを取得したくなります。最後のノードを取得したり、その一つ手前の取得したいというようなこともあるでしょう。DXL のノードを自由自在に操作するためには必要な機能と言えます。次回は、これらの便利関数を紹介します。


前回 DXL Step-by-Step 前回


2025/04/16

Domino 14 マイグレーション: ”特に追加の設定は不要”ではなかった Windows アカウントの変更

2025 年 6 月 25 日をもって Domino 11 のサポートが終了します。

HCL Domino v11.0.x の EOM (End of Market) および EOS (サポート終了、End of Support) について

この手の話はフェイクニュースのようなフィッシング系の営業活動に利用されがちなので、あえて言います。Domino がなくなるのではなく、新しいバージョンがあるからそっちを使ってね!ということです。単に、古いバージョンのサポートが終了するだけで、Windows 10 のサポート終了と同じです。

新しいバージョンを導入すればさまざまな新機能が活用できます。私がサポートしている Domino サーバもいよいよバージョンアップすることになり、Domino 14.0 の検証をしています。

最近の Domino は互換性が非常に高く、基本的には検証時間がもったいないと感じるほど、フツーにそのまま動作します。そうはいっても、いくつかは気を付けるべき点があり、今回はその中から私の環境では比較的大きかった問題を紹介します。


Domino 14 のバージョンアップ情報

Domino 14 導入に関連して、バージョンアップの手順や注意点に関しては、HCL Ambassador の Kazumasa Hayashi さんのブログ、Domino Lab で詳しくまとめられています。私もこの記事を一読したうえで検証にとりかかりました。

Domino Server をV14 にバージョンアップ!

私が担当する環境では XPages を利用していなので、何も問題はないだろう、これまでのバージョンアップとは変わらないなと判断しました。


遭遇した問題

検証環境に Domino 14 をインストールしました。インストール作業自身は順調に進み、なんの問題もなく終了しました。ところが、通常アプリとしては正常に起動するのですが、サービス起動ができないのです。

Domino Console を起動して確認すると次のようなパニックエラーが発生していました。

[156C:0002-1570] HCL Domino (r) Server (64 Bit), Release 14.0FP3 HF33, February 05, 2025
[156C:0002-1570] (C) Copyright HCL Technologies. 1987, 2023

[156C:0002-1570] comp = 11, fnc = 81, probeid = 79, errcode = 5010, extsympt = 0065
69200000
Unexpected internal error returned to logger: 0x20692010

[156C:0002-1570] Thread=[156C:0002-1570]
[156C:0002-1570] Stack base=0x6487D080, Stack size = 12640 bytes
[156C:0002-1570] PANIC: Unexpected internal error returned to logger: 0x20692010


原因はフォルダのアクセス権限

HCL Ambassador 仲間やカスタマーサポートの協力で、早い段階から以下の記事に遭遇しました。

HCL Domino 14 (Windows 版) インストーラーウィザードで追加された Windows ユーザー名の入力画面について

そういえば、インストール時に見たことない画面が増えたなと感じてはいたのですが、この記事内に『デフォルトのまま NT AUTHORITY\LocalService を指定してインストールした場合は、特に追加の設定は不要です。』とあったので、黙殺していました...。

その後、以下の記事の 2 をみて、気が付きました。

HCL Domino 14 バージョンアップ時の注意事項


Domino 14  から サービス起動する際のアカウントが、 ”Local System” から ”Local Service” へ変更となりました。そして、Domino 14 のインストーラではその権限設定を自動で変更する仕様のようです。

ただ、自動で変更されるのはプログラムディレクトリとデータディレクトリのみとなっています。それ以外のフォルダを利用している場合、手動にて権限変更する必要があるのです。


外部フォルダの利用

検証環境のDomino ではさまざまな外部フォルダを利用していました。まず notes.ini では、次のような設定をしていました。

Technical Support ログ LogFile_Dir=D:\Lotus\Domino\TechnicalSupport
トランザクションログ TRANSLOG_Path=D:\Lotus\Domino\trans_log
全文索引 FTBasePath=D:\Lotus\Domino\Index_FT

また、アプリやメールなど DB の用途ごとにフォルダを切り、ディレクトリリンク機能で Domino サーバに認識させていました。

このような細かな設定は

  1. Disk の枯渇対応
  2. レスポンス改善を目的に一部を高速なストレージへ移行
  3. フォルダごとのサイズを把握しやすくする
ことが目的です。特に 1 と 2 は物理サーバで、リソースを柔軟に増減できず、SSD のような高速ストレージが希少だった時代の名残です。将来のリソース不足に備え、部分的に移行しやすくするために行った設定でした。


権限の設定と解決

これら外部フォルダに対して、順に Windows の権限を付与します。下図の通り、LOCAL SERVICE に対してフルコントロールを設定します。

すべてのフォルダに設定が完了したら、無事サービス起動するようになりました。PANIC のメッセージを出す前に少しでもヒントとなるような情報を出してくれてれば、もっと早く気が付けたんですけどね...。


おわりに

今回のトラブルは Domino サーバに対して追加で行った設定があだとなって発生しました。ただ、どの設定も正規の Domino の機能として提供されたものです。開発したアプリが外部のローカルフォルダをアクセスしているならまだしも、正規の機能の範囲は対応してほしいところですね。

特に、トランザクションログは、データディレクトリとは別の RAID カードのディスクに配置することを推奨する記述が Administrator ヘルプにあったと思います。要は、トラブルの種を推奨していたということなので、たくさんの Domino ユーザが該当すると思います。これからバージョンアップをご検討の方は、十分お気をつけください。


2025/04/15

DXL Step-by-Step:#53)ノード操作 ⑥ - 属性の操作

今回は NotesDOMElementNode が持つ属性の操作についてまとめます。


属性とは

下図は、#49)ノード操作 ② - ノード間の関係と取得方法 に掲載した DXL のノードを分類した図ですが、ピンクの下線が属性です。

属性は、NotesDOMElementNode の < > 内に定義され、属性名='値' で構成されます。値は、id='1' や def="1" のような数値でも、color='#ffe118' のような 16 進数でも、name='Body' のような文字列と同じ表現になります。つまり、属性の値の型には文字列しかないということですね。

また、font ノードのように複数の属性を持つこともあれば、richtext や run ノードのように属性を持たないこともあります。


属性の操作

属性を管理する NotesDOMAttributeNode というクラスが定義されています。しかし、属性は NotesDOMElementNode のメソッドから操作ができます。よって、NotesDOMAttributeNode クラスは普段使いでは使用しません。

NotesDOMElementNode のメソッド 機能
GetAttribute 属性の取得
SetAttribute 属性の設定
RemoveAttribute 属性の削除


属性の取得

属性の取得は、GetAttribute メソッドで属性名を引数に指定するだけです。戻り値はその属性の値が文字列で返されます。

attr$ = notesDOMElementNode .GetAttribute( attributeName )

なお、引数で指定した名前の属性が存在しない場合は null (空の文字列)が返されます。


属性の設定

属性の設定には、SetAttribute メソッドを使用します。引数は、属性の名称と値で、それぞれ文字列で指定します。

Call notesDOMElementNode .SetAttribute( attributeName , attributeValue )

属性がない場合には、新規で属性が追加され値がセットされます。すでに同名の属性が存在する場合には、値が上書きされます。

なお、値に null(空の文字列)を指定すると、属性が削除されるのではなく、値が空の属性が作成されます。

   Call denItem.SetAttribute("name", "")


属性の削除

属性を削除するには、RemoveAttribute メソッドを使用します。引数は削除したい属性名です。

Call notesDOMElementNode.RemoveAttribute( attributeName )

存在しない属性名を指定した場合、エラーは発生せず、事実上何も実行されません。削除前の存在チェックは不要ですので、使いやすいメソッドですね。


まとめ

今回は NotesDOMElementNode の属性操作に特化してまとめました。DXL のコーディングはなかなかクセがあって思い通りいかないと感じているのですが、属性操作に限って言えば、非常にシンプルで直感的なわかりやすい機能になっています。


DXL Step-by-Step 次回