出直し!! ヘルプ

連載中

連載 終了

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 次回


2025/04/12

全銀仕様で使用できる文字の判定

前回は全角/半角の入力チェックが Nomad で動かず、対策した話をしました。文字コードがノーツクライアント(Windows)と違うことが原因だったのですが、同様の理由でもう一つ問題が発生した機能があります。それが全銀仕様で利用できる文字化チェックする機能です。


利用できる文字と 判定方法

全銀仕様で利用できる文字に関しては、ネットを調べるとすぐにわかります。何のご縁もないのですが但馬信用金庫さんの以下のサイトわかりやすかったので参考にさせていただきました。

全銀仕様データレコード使用可能文字 

半角カタカナが許可されているのですが、ヲ と ー(長音)は除外されています。そして、-(ハイフン)や括弧など一部の記号が使えます。

これまで、文字の判定は Asc 関数で文字コードを取得して判定していましたが、Nomad は、文字コードが違うためそのままでは利用できません。例えば、半角の ア は、Windows では 177、Nomad iOS では 15711665 となります。


サンプルプログラム

文字コードに応じた判定プログラムを作成することは可能かと思います。ただ、実行環境の文字コードを判定したり、利用可能文字の文字コードを調べるのは面倒です。

そこで、今回は使用できる文字が限られていることを利用します。使用可能文字のリストを作成し、チェックする文字がそこに含まれているのか確認する方法で対応します。

サンプルプログラムを以下に紹介します(スクリプトライブラリ内に記述する前提)。


◇ 定数宣言

まず、使用可能文字を定数として宣言します。

Option Declare

Private Const xsZengin_Ei = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Private Const xsZengin_Su = "1234567890"
Private Const xsZengin_Kana = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン"
Private Const xsZengin_Special = "゙゚\「」()/.- "

◇ 全銀仕様の文字列か判定する関数

引数の文字列をチェックして、全銀仕様の文字で構成されている文字列なら True を返します。使用可能文字を変数 s に集めて、サブ関数 IsSubsetOfChars に渡してチェックしています。

Public Function IsZengin(ByVal vsTarget As String) As Boolean
   Dim s As String

   '許可された文字列
   s = xsZengin_Ei      '全銀半角英字
   s = s & xsZengin_Su     '全銀半角数字
   s = s & xsZengin_Kana      '全銀半角カナ
   s = s & xsZengin_Special      '全銀半角記号

   IsZengin = IsSubsetOfChars(vsTarget, s)
End Function

◇許可文字だけか判定

チェック対象の文字列 vsTarget が、許可された文字 vsAllowed のみで構成されているかどうか判定する関数です。

Public Function IsSubsetOfChars(ByVal vsTarget As String, ByVal vsAllowed As String) As Boolean
   Dim i As Integer
   Dim j As Integer
   Dim s As String
   Dim b As Boolean

   b = True

   '1 文字ずつ取得して許可されている文字か判定
   For i = 1 To Len(vsTarget)
      s = Mid(vsTarget, i, 1)
      j = InStr(vsAllowed, s)
      If j = 0 Then
         '見つからない(= 許可されていない文字)
         b = False
         Exit For
      End If
   Next

   IsSubsetOfChars = b
End Function

許可された文字列に含まれているかの判定は InStr 関数を利用して単純化しています。


まとめ

今回紹介したサンプルでは文字コードを使用した判定を行わず、実際の文字と比較しています。よって、プログラムが実行される OS など、環境に依存しないプログラムにできたと思います。


2025/04/11

入力チェック:全角/半角の判定と Nomad

Nomad は、Notes クライアントで使用していたアプリケーションをそのまま利用できることが特徴で、労せずモバイル対応できてしまう素晴らしいシステムです。

とはいっても、プラットフォームが違うわけですから、再現率は 100% ではありません。画面サイズや縦横比の違い、Excel  に帳票を出力するなど Windows の機能を利用するアプリなど一部に制約があります。

今回紹介する現象は、もっと細かな話になります。私の思慮が足りなかっただけといえばそれまでですが、全く想定していなかったことなので紹介いたします。


発生した現象

一部のアプリケーションで入力された文字の全角/半角チェックをする機能がありました。その判定ロジックは、Len で文字数を取得、LenBP でその文字列のバイト数を取得、文字数 = バイト数なら半角、文字数 x 2 = バイト数なら全角と判定していました。

判定のための関数は次の通りなのですが、これが Nomad で正常に動作しなかったのです。

◇ すべて半角文字かチェック

Public Function IsZenkaku(ByVal vsTarget As String) As Boolean
   Dim iLen As Integer
   Dim iLenBP As Integer

   iLen = Len(vsTarget)
   iLenBP = LenBP(vsTarget)
   If (iLen * 2) = iLenbp Then
      '文字数の 2 倍がバイト数(= すべて全角文字)
      IsZenkaku = True
   Else
      '文字数の 2 倍がバイト数ではない(= 半角文字が混入)
      IsZenkaku = False
   End If
End Function

◇ すべて全角文字かチェック

Public Function IsHankaku(ByVal vsTarget As String) As Boolean
   Dim iLen As Integer
   Dim iLenBP As Integer

   iLen = Len(vsTarget)
   iLenBP = LenBP(vsTarget)

   If iLen = iLenbp Then
      '文字数とバイト数が同じ(= すべての文字が半角)
      IsHankaku = True
   Else
      '文字数とバイト数が違う(= 全角文字が混入)
      IsHankaku = False
   End If
End Function


文字列の長さ

LotusScript で文字列の長さを取得する関数には Len、LenB、LenBP の 3 種類があります。デザイナーヘルプをまとめると次のような機能です。

関数 機能
Len 文字列の文字数
LenB 文字列の長さを示すバイト数
LenBP 文字列の長さを示すバイト数(プラットフォーム固有の文字セット)

簡単なテストフォームを作成し、それぞれの関数の挙動をチェックします。

結果は次の通りでした。この結果すぐに気が付きました。全角や半角カナが 3 バイトとなっているので、Nomad iOS は Unicode で動作していると思われます。

文字 ノーツ(Windows) Nomad iOS
Len LenB LenBP Len LenB LenBP
(全角) 1 2 2 1 2 3
A(半角英字) 1 2 1 1 2 1
(半角カナ) 1 2 1 1 2 3

LenB 関数はどういった機能を提供してくれているのか今一つ理解できませんが、Len 系の関数だけで全角/半角の判定は難しそうです。


対策

◇ すべて半角文字かチェック

半角から全角文字の変更は必ずできます。全ての文字が全角であるかチェックするには、これを利用すれば簡単です。文字列を全角に変換して、元の文字列と一致するか確認するだけです。変化があれば半角文字が混ざっていたということですね。

Public Function IsZenkaku(ByVal vsTarget As String) As Boolean
   Dim sZen As string
   Dim v As Variant

   v = Evaluate(|@Wide("| & vsTarget & |")|)    '半角から全角に変換
   sZen = v(0)

   If sZen = vsTarget Then
      '全角に変換しても変化がないのですべて全角文字
      IsZenkaku = True
   Else
      '全角に変換すると変化したので半角文字が混入
      IsZenkaku = False
   End If
End Function

◇ すべて全角文字かチェック

全て半角であるかについては少し厄介です。漢字など半角に変換できない文字があるからです。まず、全角/半角変換の関係を整理します。

チェックする文字 半角に変換 全角に変換 備考
(全角) (全角) (全角) 半角にできない全角
(全角) A(半角) (全角) 半角にできる全角
(半角) (半角) (全角) 半角にしても変化なし

この関係を利用して関数を作成します。1 文字ずつ上表に当てはめて判定します。1 文字でも全角文字を発見したら、判定を終了し False 返しています。

Public Function IsHankaku(ByVal vsTarget As String) As Boolean
   Dim sSrc As String
   Dim sHan As String
   Dim sZen As String
   Dim v As Variant
   Dim i As Integer

   IsHankaku = True
   '1 文字ずつ確認
   For i = 1 To Len(vsTarget)
      sSrc = Mid(vsTarget, i, 1)
      '全角から半角に変換
      v = Evaluate(|@Narrow("| & sSrc & |")|)
      sHan = v(0)
      '半角から全角に変換
      v = Evaluate(|@Wide("| & sSrc & |")|)
      sZen = v(0)

      If sSrc = sHan And sSrc = sZen Then
         '全角(半角にできない全角文字)
         IsHankaku = False
         Exit For
      ElseIf sSrc <> sHan And sSrc = sZen Then
         '全角(半角に変換できる全角文字)
         IsHankaku = False
         Exit For
      Else
         '半角(全角でなく、半角に変換しても変化しない)
         '→ 処理を継続して次の文字をチェック
      End If
   Next
End Function


おわりに

今回は、全角/半角のチェックを例に Nomad 利用時に発生した課題を紹介しました。

私は長年 Notes クライアント用のアプリばかりを作っています。Web 化や XPages 化も詳しくありません。Windows アプリばかりだったせいもあり、文字コードには無頓着だったということですね。Nomad ≒ Notes クライアントではあるのですが、ところどころ落とし穴があるので注意が必要です。

ところで、紹介したコードでは、全角/半角の変換に Evaluate 関数を介た@関数を利用していますが、これには理由があります。LotusScript の関数 StrConv はノーツクライアントと Nomad 環境で結果が変わり、判定に使用できなかったからです。この点も Nomad 運用時には注意が必要です。

Notes は複数の言語をサポートしており、開発の自由度も高いです。歴史が長いこと幅が広いことで混乱することもありますが、探せば回避策が見つかるところはありがたいといえます。宝さがしみたいで面白いですね。