2023/09/29

DXL Step-by-Step:#4)文書の更新

今回は DXL で文書を更新する方法についてまとめます。前回取得した文書を更新してみましょう。


フィールドの追加

今回は文書を再保存する方法が主題なので、保存する文書の更新は、テキストフィールドを一つ追加するだけとしたいと思います。

ただ、以前と同じようにベタ書きしてもつまらないので、テキストフィールドを追加する関数 xAppendField_Text を作成しました。これで、メインルーチンでは簡単にフィールドを追加できます。

Function xAppendField_Text( _
                            vddn As NotesDOMDocumentNode, _
                            ByVal vsFldName As String, _
                            ByVal vsText As String) As Boolean
   'document ノード取得
   Dim denCur As NotesDOMElementNode
   Set denCur = vddn.DocumentElement

   'item ノード作成とフィールド名の指定
   Dim denNew As NotesDOMElementNode
   Set denNew = vddn.CreateElementNode("item")
   Call denNew.SetAttribute("name", vsFldName)
   Set denCur = denCur.AppendChild(denNew)

   'text ノードと値のセット
   Dim dtnNew As NotesDOMTextNode
   Set denNew = vddn.CreateElementNode("text")
   Set denCur = denCur.AppendChild(denNew)
   Set dtnNew = vddn.CreateTextNode(vsText)
   Call denCur.AppendChild(dtnNew)
End Function

引数は、文書を表す NotesDOMDocumentNode と 作成するフィールド名 vsFldName、設定する値の vsText となっています。


文書の更新と DocumentImportOption プロパティ

新規文書の保存は、NotesDXLImporter クラスの Import メソッドで、DXL をデータベースに反映しました。文書の更新も同じく Import メソッドを使用します。

ただし、Import メソッドは、文書を保存する設定をコントロールするオプション DocumentImportOption プロパティにより動作が変わります。

DocumentImportOption (NotesDXLImporter - LotusScript®)

具体的には、インポートする DXL を使って文書をどのように処理するか指定することになります。今回は文書の更新なので 5 を指定します。

デフォルト値である 2 は新規文書を作成する設定です。第1回、第2回でこのプロパティを指定せずに新規作成できたのは、デフォルト設定の効果ですね。


文書を保存する関数

DXL を Import する関数も修正します。今回は、保存も更新もできるよう、DocumentImportOption に使用する値を引数に組み込みます。また、DXL もテキストではなく、NotesDOMParser のオブジェクトに変更しています。

Function xImportDXL(vdprs As NotesDOMParser, _
           ByVal viOption As Integer) As Boolean
   'DXL の抽出準備
   Dim nst As NotesStream
   Set nst = xns.CreateStream()
   Call vdprs.SetOutput(nst)
   Call vdprs.Serialize()

   '保存(インポート)
   Dim ndb As NotesDatabase
   Dim dimp As NotesDXLImporter
   Set ndb = xns.CurrentDatabase
   Set dimp = xns.CreateDXLImporter()
   dimp.DocumentImportOption = viOption
   Call dimp.Import(nst.ReadText(), ndb)

End Function


メインルーチン

関数が一通り完成したのでメインルーチンを作成します。

ビューで選択した文書に対するエージェントとして作成し、最初の文書に対して実行します。重要な部分は次の 3 点です。

  1. 第 3 回で作成した関数を利用し、DXL を操作するための NotesDOMParser クラスのオブジェクトを取得
  2. "NewFld" という新規フィールドを作成し、値をセット
  3. 文書を更新として保存

Option Declare
Private xns As NotesSession

Sub Initialize
   Dim ndb As NotesDatabase
   Dim ndc As NotesDocumentCollection
   Dim nd As NotesDocument

   Set xns = New NotesSession
   Set ndb = xns.CurrentDatabase
   Set ndc = ndb.UnprocessedDocuments
   Set nd = ndc.GetFirstDocument()

   Dim dprs As NotesDOMParser
   Set dprs = xDocToDXL(nd)
   Call xAppendField_Text(dprs.Document, "NewFld", "#4 文書の更新")
   Call xImportDXL(dprs, 5)

End Sub


エージェントの実行

ビューで第 2 回で新規作成した文書に対して実行してみます。結果は次のように、希望通りフィールドが追加されました。

ところが、同じ文書に対してもう一度エージェントを実行すると、同じ名前のフィールドがもう一つ追加されてしまいました !?

DXL でフィールドを追加する場合、同名のフィールドが存在するかは関係なく追加できてしまうようです。実際にアプリで利用する際には、NotesDocument クラスの ReplaceItemValue のような関数が必要となりそうですね。


前回 DXL Step-by-Step 次回

2023/09/27

DXL Step-by-Step:#3)文書を DXL で取得

今回は、既存文書を DXL でアクセスする方法を整理します。前回作成した新規文書を DXL を取得して、挙動を確認します。


既存文書を DXL で取得

既存文書を DXL でアクセスする次の通りです。

  1. NotesDXLExporter の入力に文書をセット
  2. NotesDOMParser の入力に NotesDXLExporter のオブジェクトをセット
  3. NotesDXLExporter の Process メソッドで DXL の変換を実行
具体的なコードは次の通りです。今回は後続のプログラムで取得した DXL を操作することを前提に、NotesDOMParser クラスのオブジェクトを返す関数として作成してみました。

Function xDocToDXL(vnd As NotesDocument) As NotesDOMParser
    Dim ns As New NotesSession

    'Dominoデータ を DXL に変換する準備
    Dim dexp As NotesDXLExporter
    Set dexp = ns.CreateDXLExporter()
    Call dexp.SetInput(vnd)

    'パーサーに変換する DXL をセット
    Dim dprs As NotesDOMParser
    Set dprs = ns.CreateDOMParser()
    Call dprs.SetInput(dexp)

    'DXL 変換を実行
    Call dexp.Process()

    Set xDocToDXL = dprs
End Function


既存文書の DXL

この関数の引数 vnd は、DXL に変換したい文書を指定します。この引数に前回 DXL を使って作成した新規文書を指定して、DXLを抽出すると次のようになりました。

保存(DXL の Import)時に指定した DXL(黄色網掛け部分)以外にもたくさんの項目が出力されています。文書 ID や作成日/作成者など、ノーツ文書の基本的な情報が入っています。DXL で文書を保存する場合も、必要最低限の情報を指定すれば、必要な情報は自動で付加されることがわかります。

NotesDocument クラスを使用して文書を作成しても、フィールドの設定以外は細かな操作は行わず保存するだけなので、同じようなものですね。


前回 DXL Step-by-Step 次回

2023/09/23

DXL Step-by-Step:#2)文書の新規作成 - DOM クラスの利用

前回 は、DXL を使って文書を新規作成する方法を紹介しました。ただ、DXL の作成は文字列を連結しただけの単純な方法でした。今回は、その DXL の作成を DXL 関連の Notes DOM クラスを使用して作成する方法を整理します。

プログラムの構造は 前回 と揃えています。よって、修正するのは、DXL の文字列を作成する関数 xGetDXL だけとなります。関数で作成する DXL は前回と同じ構造の次の通りとします。

<document form='DXL_SbS'>
   <item name='Title'>
      <text>
         #2 DOM クラスで DXL 文字列を作成
      </text>
   </item>
</document>


DOMクラスの利用準備

まず、DOM クラスを利用して DXL を操作するためには、NotesDOMParser クラスが必要でした。このクラスは、DXL を LotusScript で扱いやすくなるよう、構造(DOM 文書ツリー)をオブジェクトに変換する役割を担います。

NotesDOMParser オブジェクトの Document プロパティにより取得できる NotesDOMDocumentNode が XML 全体を表し、文書ツリーのルートとなります。

   Dim ns As New NotesSession
   Dim dprs As NotesDOMParser
   Dim ddn As NotesDOMDocumentNode
        ・・・
   Set dprs = ns.CreateDOMParser()
   Set ddn = dprs.Document

今回は新規文書の作成ですので、DXL をゼロから作成することになります。よって、上記のシンプルなコードで OK です。

ちなみに ddn の値をデバッガで確認すると次の通り、中身は空っぽとなっています。


ノードの作成

ルートの準備ができたので、DXL の中身を追加します。最初は、文書を表す <document> ノードです。

DXL でノードを追加する方法は少し特殊です。

まず、ノードを作成するメソッドは、ルートである NotesDOMDocumentNode オブジェクトにしか存在しません。複数ある Create?????Node というメソッドで、それぞれタイプの違うノードを作成します。

これらメソッドを使って必要なノードを作成して、各ノードオブジェクトの AppendChild メソッドでそのノード配下のノードとしてセットします。

   Dim denCur As NotesDOMElementNode
   Dim denNew As NotesDOMElementNode
        ・・・
   Set denNew = ddn.CreateElementNode("document")
   Call denNew.SetAttribute("form", "DXL_SbS")
   Set denCur = ddn.AppendChild(denNew)

あえて例えるなら、一旦文書を作成しておいて、あとから返答文書にセットするイメージでしょうか?ノーツ屋としてわからなくもないですが、ちょっと回りくどいですね...

なお、<document> ノードは、フォーム名を form 属性で指定します。SetAttribute メソッドを使用してセットしています。属性はオブジェクトに直接追加できるようですね...


ここまで実行すると、ルートオブジェクトの ddn の NumberOfChildNodes が 1 となりノードの追加が成功したことがわかります。また、DocumentElement や FirstChild などの子ノードにアクセスするためのプロパティにも値がセットされています。


フィールドの作成

文書を表す <document> ノードが作成できたので、次は、フィールドである <item> ノードを追加します。

ノードの追加なので、<document> ノードと同様の手順で作成します。注意すべきは、親となるノードが <document> となるだけです。

   Set denCur = ddn.AppendChild(denNew)

   Set denNew = ddn.CreateElementNode("item")
   Call denNew.SetAttribute("name", "Title")
   Set denCur = denCur.AppendChild(denNew)

AppendChild メソッドの戻り値は、追加したノード自身となります。追加した <document> ノードは denCur 変数で受けていますので、これにフィールドを追加することになります。

このように、DOM クラスでノードを追加するには、親となるノードと追加するノードの2つを操作することになります。変数名を工夫するなど、見やすくしておかないと、あとで見た時に理解に時間がかかりそうです。


フィールドの中身の作成

フィールドを表す DXL は、値の型を表す <text> ノードがあり、そのタグの間に値がべた書きされています。この値を表す部分は、DOM クラスでは、NotesDOMTextNode となります。

具体的には次のように <text> ノードを作成し、それに対して、テキストノードを追加します。テキストノードは NotesDOMTextNode オブジェクトとなるので、別の変数 dtnNew で受け取っています。

   Dim dtnNew As NotesDOMTextNode
        ・・・
   Set denCur = denCur.AppendChild(denNew)


   Set denNew = ddn.CreateElementNode("text")
   Set denCur = denCur.AppendChild(denNew)

   Set dtnNew = ddn.CreateTextNode("#2 DOM クラスで DXL 文字列を作成")
   Call denCur.AppendChild(dtnNew)


DXL を文字列で取得

DXL の構造(DOM 文書ツリー)は、NotesDOMParser が管理していました。ここから、完成した DXL をテキスト文字列で抽出する必要があります。

   Dim nst As NotesStream
        ・・・
   Set nst = ns.CreateStream()
   Call dprs.SetOutput(nst)
   Call dprs.Serialize()

   xGetDXL = nst.ReadText()

まず、SetOutput メソッドを使用して、NotesDOMParser クラスの出力先を指定します。今回は出力先に NotesStream クラスのオブジェクトを使用しました。

NotesStream クラスは、テキストデータやバイナリデータを効率よく扱うためのクラスです。今回は、NotesDOMParser の出力を NotesStream で受け取り、ReadText メソッドで DXL 全体を文字列として取得しています。

なお、NotesDOMParser クラスの Serialize メソッドは、DOM 文書ツリーを検索し XML を出力オブジェクトにストリーミングします。この命令を実行することにより、NotesStream クラスのオブジェクト nst に値がセットされます。


まとめ

xGetDXL 関数の全体を掲載します。

文書を作成して、フィールドを1つ作成するだけですが、なかなか長いコードとなります。NotesDocument クラスなど LotusScript 標準のクラスで実現できることは、わざわざDXL で実施する必要はなさそうですね...

Function xGetDXL() As String
   Dim ns As New NotesSession
   Dim dprs As NotesDOMParser
   Dim ddn As NotesDOMDocumentNode
   Dim denCur As NotesDOMElementNode
   Dim denNew As NotesDOMElementNode
   Dim dtnNew As NotesDOMTextNode

   ' DXL 作成準備
   Set dprs = ns.CreateDOMParser()
   Set ddn = dprs.Document
   
   '<document>ノード作成
   Set denNew = ddn.CreateElementNode("document")
   Call denNew.SetAttribute("form", "DXL_SbS")
   Set denCur = ddn.AppendChild(denNew)

   '<item>ノード作成
   Set denNew = ddn.CreateElementNode("item")
   Call denNew.SetAttribute("name", "Title")
   Set denCur = denCur.AppendChild(denNew)

   '<text>ノード作成
   Set denNew = ddn.CreateElementNode("text")
   Set denCur = denCur.AppendChild(denNew)

   '<text>ノードに値を作成
   Set dtnNew = ddn.CreateTextNode("#1 DXLをDOMクラスから作成")
   Call denCur.AppendChild(dtnNew)

   '作成した DXL をテキストで抽出
   Dim nst As NotesStream
   Set nst = ns.CreateStream()
   Call dprs.SetOutput(nst)
   Call dprs.Serialize()

   xGetDXL = nst.ReadText()
End Function


前回 DXL Step-by-Step 次回

2023/09/22

DXL Step-by-Step:#1)文書の新規作成

『DXL Step-by-Step』シリーズでは、DXL 活用の調査・検証で、実現できたこと、発見したことなどをとりとめなく紹介します。


はじめに

DXL(Domino XML Language)は、ドミノ内の文書や設計を XML アクセスできる機能です。DXL の基本的な情報については、『連載:DXL ことはじめ』にまとめております。はじめての方はこちらを先に参照ください。

連載:DXL ことはじめ』の 第1回 にも書きましたが、DXL を使用すると LotusScript 標準のノーツクラスだけでは操作できないことが実現できます。

例えば、これまでの検証で次のようなことが実現できたと紹介しました。

◇ リッチテキストに対する操作

  • 画像をインラインイメージで添付
  • 表を動的に作成し、罫線や背景色などを設定
  • ボタンなどアクションをコードとともに動的に生成

◇ 設計に対する操作

  • イメージリソースを登録/変更/削除
  • エージェントの実行時間設定など詳細な情報の取得

これまであきらめていた機能が実現できる可能性がありますよね。この連載の中で、順にご紹介していく予定ですのでお楽しみに!


なお、DXL に関しては、現在も調査を進めている段階です。手探りで記述しコードをご紹介することになります。必要に応じて訂正や効率的なコードを改めて記載することになるかもしれません。その点はご容赦ください。


通常の文書作成

第 1 回の今回は、文書の新規作成です。

LotusScript のエージェントで、普通に文書を新規作成するには、次のようなコードになります。

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

   Set ndb = ns.CurrentDatabase
   Set nd = ndb.CreateDocument()
   nd.Form = "DXL_SbS"
   nd.Title = "#1 LotusScript から普通に文書を作成"

   Call nd.Save(True, False)
End Sub

データベースに対して、CreateDocument メソッドで文書を作成し、フォーム名を含むフィールドに値をセット、最後に Save メソッドで保存しています。


DXL の文書作成

これを DXL で作成するように書き直すと、次のようになります。

Sub Initialize
   Dim sDXL As String

   sDXL = xGetDXL() 'DXL文字列を作成
   Call xCreateNewDoc(sDXL)
End Sub

Function xGetDXL() As String
   Dim s As String

   'DXL文字列を作成
   s = "<document form='DXL_SbS'>"
   s = s & "<item name='Title'>"
   s = s & "<text>"
   s = s & "#1 DXL文字列から文書を作成"
   s = s & "</text>
   s = s & "</item>"
   s = s & "</document>"
   
   xGetDXL = s
End Function

Function xCreateNewDoc(ByVal vsDXL As String) As Boolean
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim dimp As NotesDXLImporter
   
   Set ndb = ns.CurrentDatabase
   
   Set dimp = ns.CreateDXLImporter()
   Call dimp.Import(vsDXL, ndb)
End Function

ポイントは、文書を作成する DXL と文書を保存する方法の2点です。それぞれ関数化しており、メインルーチンからコールするようにしてます。

実行すると以下のように Form と Title フィールドがセットされた文書が作成されます。


DXL の作成

DXL の作成を行っているのは xGetDXL 関数です。

今回は、単純に DXL をコード内に直接定数として記述したリテラル文字列を連結させて作成しています。作成される文字列は以下の通りです(見やすくなるよう改行とインデントしています)。

<document form='DXL_SbS'>
   <item name='Title'>
      <text>
         #1 DXL文字列から文書を作成
      </text>
   </item>
</document>

まず、文書を表す document ノードを作成します。DXL ではフォーム名は、フィールドではなく、document ノードの form という属性で表します。

フィールドは、document ノード配下に item ノードを作成し、フィールド名は name という属性で指定します。フィールドの値は、フィールドの型を表すノード、今回は文字列なので text ノードを作成します。実際の値は、text タグの中にセットします。

既存文書を DXL に変換すると様々なノードや属性が設定されています。ですが、新規作成の場合は、必要最低限の DXL だけで正常に登録できます。


文書の作成

文書の作成は、xCreateNewDoc 関数で実施しています。

DXL を使って更新(今回は文書の作成)を行うには、NotesDXLImporter クラスを使用します。NotesSession クラスの CreateDXLImporter メソッドで、オブジェクトを取得して利用します。

Import メソッドが、DXL をデータベースに反映するメソッドです。引数は、反映するDXL と反映先となるデータベースとなります。どこに(データベース)何を(DXL)反映するのか一目瞭然でわかりやすいですね。

この Import メソッドを実行すると DXL がドミノオブジェクトに変換されデータベースに反映されます。今回の場合では、文書が作成されるということになります。


なお、Import メソッド実行時に DXL に問題があると、エラーが発生します。よって、DXL の生成を失敗したからと言って、データベースを壊すようなことはないようです。

DXL Step-by-Step 次回

2023/09/21

リスト値を@関数でアクセス

ノーツでは、Excel シートのように複数の項目(列)を複数行入力するような画面をリスト値で表現することがよくあります。

このような構造に対して、@関数でアクセスする方法をまとめます。


まず、次のようなフォームがあり、交通機関(Type)と金額(Cost)のフィールドはリスト値を持つ設定だったとします(区切り文字は改行)。

このフォームを使用して作成した以下の文書をサンプルデータとして使用します。


特定の要素の金額を取得

例えば、”阪神” の金額を取得するには次のように記述します。

xSchType := "阪神";
xIndex := @Member(xSchType; Type);
xCost := @If(xIndex = 0; 0; Cost[xIndex]);
@Prompt([Ok]; ""; xSchType + " = " + @Text(xCost))

まず、検索文字列を xSchType にセットし、それが交通機関に含まれるか検索します。検索は、@Member で行い、見つかった場合はその要素番号を返します(1 ~)。見つからない場合は 0 を返します。

金額の取得は、[ ] の演算子を使用して、フィールド Cost を配列としてアクセスし、必要な要素の値を取得しています。

実行すると次のようにメッセージが表示され、正しく金額が取得できることがわかります。

なお、@Member で指定した交通機関が見つからない場合は、金額を 0 としています。

また、取得できる要素番号は 1 がスタートです。これを 1-based indexing と呼びます。ちなみに、LotusScript のデフォルトは、0-based indexing です。


ループを使用したアクセス

続いては、ループ処理を記述できる @For という関数を使用して、すべての要素にアクセスする方法です。

ヘルプによると構文は、次の通りです。

@For( initialize ; condition ; increment ; statement ; ... )

引数は、1つ目がループ変数の初期条件、2つ目が継続条件式(True の場合ループ処理を継続)、3つ目がループ変数の変更式、それ以降がループ処理の本体で、最大 252 個の処理が記述できます。

例がないとわかりにくいので、サンプルコードを示します。この式はリスト値の値を順にメッセージボックスで表示します。

xMax := @Elements(Type);
@For(n := 1; n <= xMax; n := n + 1;
   xMsg := Type[n] + " = " + @Text(Cost[n]);
   @Prompt([Ok]; ""; xMsg)
)

変数 n がループ変数で、n は 1 から始まり、リスト値の要素数まで、1つずつ加算しなら、ループを処理します。実行する処理は、インデントした部分の2行で、メッセージボックスに表示する文字列を生成し、それを@Prompt で表示しています。

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

2023/09/19

ビューで行の高さを超えたことを表示

リスト値を表示するビューでは、区切り文字に改行を指定して、値を整然と表示させることがよくあります。

ただ、このビューの設定、リスト値の数が多くて行の高さを超えたかどうかが判断できません。例えば、上記の1件目のデータは次の通り8要素でした。ビューだけを見ると5要素だと誤解されかねません。


このような場合に利用できる列式を作成しました。汎用的に作成したので、パラメータをセットするだけで、様々なシーンで利用できます。

REM {パラメータセット};
xMaxRow := 5;
xListVal := Type;
xUnit := "件";

REM {以下は共通部分(変更しないで下さい)};
xCount := @Elements(xListVal);
@If(xCount <= xMaxRow;
   xListVal;
   @Subset(xListVal; xMaxRow-1):
      (" … (計 " + @Text(xCount) + " " + xUnit + ")")
)

指定できるパラメータは次の3つです。これらの変数に値を与えるだけで利用できます。

変数 用途
xMaxRow 数値 ビューのプロパティで指定した行の高さ
xListVal 文字列リスト 列に表示するリスト値
xUnit 文字列 リスト値の単位


実行すると次のように表示されます。xMaxRow を超える場合は、最後の要素を件数表示に置き換えて表示します。

2023/09/17

Notes - Excel 連携:#19)グラフ関連オブジェクトまとめ

第 11 回から前回の第 18 回まで、グラフのオブジェクトである Chart オブジェクトの操作について解説してきました。今回はそのまとめとして、これまでの登場したオブジェクトの関係とその操作について整理ります。


グラフの作成方法

まず、シート内に存在するグラフは、Worksheet オブジェクト内の Shapes プロパティから取得するオブジェクトに含まれます。Shapes オブジェクトは、グラフ(Chart)だけでなく、画像(Picture)やテキストボックス(Textbox)などの様々なオブジェクトの集合となります。

グラフの追加は、Shapes オブジェクトの AddChart2 メソッドで作成しました。AddChart2 を実行すると Shape オブジェクトを返します。この Shape オブジェクトには、グラフ(Chart)オブジェクトが含まれ、Chart プロパティ経由でアクセスするという流れでした。


Chart オブジェクト内の構造

まず、Chart オブジェクトには、グラフの種類を決定する ChartType プロパティが存在し、グラフとして表示するデータを指定する SetSourceData メソッドを紹介しました。

指定方法は Range オブジェクトを使用します。Range オブジェクトは柔軟な仕様ですので、単一の範囲を指定することはもちろん、複数の範囲、連続した列、複数の連続しない列など様々指定方法ができました。

グラフ内のオブジェクトとしては、グラフのタイトルである ChartTitle オブジェクトと、軸の設定である Axes オブジェクトについて紹介しました。

グラフのタイトルのフォントを変更するには、ChartTitle オブジェクトからいくつものオブジェクトを掘り進んで設定する必要がありました。

ここまでを関連図で書くと次のようになります。


オブジェクト構造の補足

上記関連図では、連載記事内で触れたプロパティとメソッドのみ表示しています。それぞれのオブジェクトには、ほかにもたくさんのプロパティやメソッドが存在します。このような関係を理解したうえで、Microsft Learn を読み進めると混乱しなくて済むかと思います。


例えば、本編の連載では触れませんでしたが、軸に表示する文字のフォントを変更することができます。軸に表示する文字(の集合)のオブジェクトは TickLabels でしたが、Format というプロパティを持っています。このプロパティの型は ChartFormat で、グラフタイトルオブジェクトの Format プロパティと同じです。

Excel VBA のオブジェクト構造は、それぞれのオブジェクトが十分に独立化していて、様々な場所で再利用できるよう設計されています。そのため、階層が深く、入り組んだ関係に見えることもあります。しかし、そのオブジェクトの役割しっかり確認すると、理解しやすくなります。


前回 Notes - Excel 連携