|
 |
ソフトウェア > Lotus > Lotus Developer Domain > 製品別技術情報 > Lotus Notes/Domino >
LDD Today
LotusScript:Lotus Notes/Domino 6におけるXMLクラス
 |
 |
|
by Sally Blanning Dejan & David Dejan
レベル:中級者
対象:Lotus Notes/Domino 6
原文の掲載:2003年2月3日
IBM Lotus Notes/Domino 6では、XMLデータのエクスポート、インポート、および処理を行ういくつかの新規LotusScriptクラスを追加して、統合的にXMLをサポートします。この記事では、最初にこれらの新規クラスを詳しく説明し、次にDXL(Domino XML Language)フォーマットのNSFファイルから選択的にデータを書き出す場合や、LotusScriptでサポートされている2つのパーサー(DOMパーサーとSAXパーサー)を適用する場合に便利なサンプル・コードを紹介します。今後の記事では引き続き、XSL変換を適用してDXLを他のXMLダイアレクト、特にHTMLに変換する例や、XMLデータをNSFフォーマットに呼び出す例を紹介していきます。(この記事は、Notes/Domino 6における新規LotusScriptクラスおよびLotusScript言語の拡張に関する詳しい解説シリーズの2回目の記事です。最初の記事では、リッチ・テキスト要素を操作するLotusScriptの新規クラスについて説明しました。LDD Todayの記事「LotusScript:Notes/Domino 6のリッチ・テキスト・オブジェクト(英語)」を参照してください)
XMLの概要
XML(Extensible Markup Languageの略)は、他の方法では互換性のないアプリケーションとシステムとの間でデータを交換するための業界標準かつ業界の主力ソリューションとしてかなり急速に出現してきたものです。XMLはHTMLのようなマークアップ言語であり、文書のコンテンツにタグを付けます。しかし、HTMLのタグは固定されておりコンテンツの外観(書体、サイズ、重み、ページ上の配置)を記述しているのに対して、XMLのタグには制限がなく、要素(データ要素)の命名法やその関連といったコンテンツの構造を記述しています。実際、XMLは単なる言語ではなく、メタ言語(データ構造の記述に使用される専用タグ言語を作成し、文書化するための言語)です。
Dominoでは、DXLを初めてインプリメントしLotus XML Toolkitを提供したRelease 5からXMLをサポートしています。DXL(Domino XML Language)は、XMLフォーマットで記述されたDominoデータベース構造を表すために特別に設計されたXML言語です。DXLでは、各要素の構造(データ値、データ・タイプ、属性、その値)および各要素とデータベースの他の部分との関係を記述するタグ言語が提供されています。
データベースをDXLとしてエクスポートすることは、言うまでもなく最初のステップにすぎません。データをさらに他のアプリケーションに移行したい場合、データを受け取り側アプリケーションが理解できるXML言語に再フォーマットする(変換する)必要があります。この変換プロセスは、データのタグ・セット(および構造)を再構成するアプリケーションであるパーサーによって実行されます。
Release 5のXMLサポート用Lotus XML Toolkitには、2つのコマンド行ユーティリティー(DXLExportとDXLImport)およびXMLフォーマットのNSFファイルからのデータ取り出しを可能にするパーサーをインプリメントしたC++/Javaクラスが含まれています。Notes/Domino 6におけるXMLサポートの新機能は、XMLの全機能をネイティブにサポートする新規LotusScriptクラスです。LotusScriptもRelease5のLotus XML Toolkitに比べてより完全にインプリメントされているため、Designer全体をLotusScriptで開発し、デバッグすることができます。
この新しいサポート機能の核心は、NotesXMLProcessorという新しい基本クラスです。このクラスのプロパティーおよびメソッドは、複数のXML処理クラスに継承されています。したがって、NotesXMLProcessorを直接使用することはありません。代わりに、NotesSessionクラスの適切なCreateメソッドを使用して、派生したXMLクラスの1つからオブジェクトを作成します。
Domino Designer 6からそのホームページを開き、[Show me]プロンプトに続いて[Domino Objects for DXL Support]を選択すると、次のようなDXL用LotusScriptクラス図が表示されます。

これらのうち最も重要な7つのクラスはすべて、Notes/Domino 6で新しく追加されたクラスであり、次の3つの主要なカテゴリーに分類されます。
- DXL専用プロセッサー:NotesDXLExporterとNotesDXLImporter
- XMLプロセッサー:NotesDOMParser、NotesSAXParser、およびNotesXSLTransformer
- helperクラス:NotesNoteCollectionとNotesStream
DXLプロセッサー・クラス
NotesDXLExporterおよびNotesDXLImporterの機能は簡単明瞭です。NotesDXLExporterはDominoデータをDXL言語で記述されたXMLに変換し、NotesDXLImporterはDXL化されたXMLデータをDominoデータに変換します。
XMLプロセッサー・クラス
Dominoはその他の3つのプロセッサー・クラスによって、標準XML機能を実行します。
NotesDOMParser
XML Document Object Model(DOM)は、データをツリー構造で表現します。DOMツリー内のすべての設計要素(ノード)は属性を持ち、データ・オブジェクトを全体的に表すルート・ノードに対する親子関係でグループ化されます。DOMパーサーは、メモリー上にDOMツリーを構築し、DOMツリー内をノードからノードへと移動して構文解析します。Dominoは、上図に示すNotesDOMParserクラスおよびそのサブクラスをいくつか使用して、構文解析を実行します。
NotesSAXParser
SAX(「Simple API for XML」の頭字語)は、XMLオブジェクトを構文解析するもう1つの手法です。SAXパーサーは、メモリー上にDOMツリーを構築するのではなく、XMLデータをストリームとして読み出した後、アプリケーションが処理できるイベントとして生成します。SAXは、比較的少量のデータをXMLファイルから読み出して構文解析する場合や、XMLファイルが大きすぎるためメモリー上に構築するDOMツリーがDOMパーサーを実行中のコンピューターのキャパシティーとパフォーマンスに多大な負荷をかける可能性がある場合に適しています。SAXの比較的シンプルな手法は図にも示されていますが、同時にデータ変換力が欠如していることも分かります。
NotesXSLTransformer
ほとんどのデータ・エクスポート/インポート操作では、データをフィルター操作または変換する必要があります。XMLでは、XSL(XML Style Language)で記述された外部文書を利用して、このデータ変換を制御しています。XSL変換では、XML文書をあるタグ言語から別のタグ言語へ変換し、使用するエンティティーをフィルター操作して変換し、データ内の親/子/兄弟関係を再構成します。XSLスタイルシートは、整形式XML言語で記述されています。
Helperクラス
2つのHelperクラスは特にXML処理操作に対して有効ですが、他にも幅広く利用できます。
NotesStream
このシリーズの前回の記事「LotusScript:Notes/Domino 6のリッチ・テキスト・オブジェクト(英語)」を読んでいれば、NotesStreamクラスを理解できるでしょう。NotesStreamクラスは、Dominoとファイルまたはメモリー内ストアとの間で交換される、バイナリー・データまたは文字データのストリームを表します。
NotesNoteCollection
Dominoデータベースは、メモとして内部で知られている設計要素およびデータ要素から構成されています。エージェント、ビュー、イメージ・リソースおよびACLエントリーの場合、文書はメモです。 NotesNoteCollectionクラスのメソッドおよびプロパティーを使用すると、データベース内のすべてのメモまたはタイプで指定されたサブセット、または指定日以降に作成したメモを、設計メモなど単なる文書メモとして表現するオブジェクトを作成することができます。
パイプライン処理機能
パイプライン処理は、LotusScriptでXMLと連携するための鍵となる機能です。プロセッサー・オブジェクトとhelperオブジェクトを組立ラインのように一緒にフックして、片方の出力を次の入力にすることができます。途中の値をキャプチャーする必要がない場合、この手法によってステップを省略できます。たとえば、NotesNoteCollectionクラスを使用してDXLExporterによってエクスポートされるデータベース内の文書を選択し、NotesXSLTransformerを使用してDXLをHTMLに変換できます。
パイプライン処理が機能する理由は、行の最初のオブジェクトに対してProcessメソッドを呼び出す前に、XMLプロセッサーが入力と出力の識別を要求するためです。最もシンプルな方法でパイプラインをセットアップするには、すべてのプロセスに入力を指定しますが、出力は行の最後のプロセスにのみ指定します。
たとえXMLに精通していなくても、HTMLのようにデータがタグで囲まれているので、すぐにXMLに気付くでしょう。このタグには、値が割り当てられている属性を含めることができ、ネストすることも可能です。また、通常はインデントで示される階層構造を持っています。
しかし、タグ・タイプはHTMLとは異なります。HTMLのタグ数は限定されていますが、XMLのタグ数は無制限です。さらに、HTMLでは気軽にタグ付けができるのに対して(たとえば、パラグラフ終了タグに</p>を使用せずに開始タグに<p>を使用できます)、XMLでは必ず厳密に整形式にタグ付けする必要があります。つまり、すべての開始タグ(たとえば、<item>)は必ず終了タグ(</item>)と一致しなければなりません。(下記サンプル・コードを見れば分かるとおり、一部のタグは「/>」を最後に使用して自己終端しています)
XMLでは、データを記述するスキーマの中でタグを使用します。DXLは、Dominoデータベースを記述するこれらスキーマの1つです。<databaseinfo>や<aclentry>などのタグは、データベース構造の設計要素の記述子です。DXLスキーマには、NSFファイルの全設計要素(文書、フォーム、ビュー、エージョント、ACL、その他多数)の記述子が含まれています。DXLファイルは、すべて適切にタグ付けされ、プレーン・テキストで記述されたデータベースのコンテンツを表しています。次のサンプル・コードは、DXLファイルの冒頭部分です。
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE database SYSTEM 'xmlschemas/domino_6_0.dtd'>
<database xmlns='http://www.lotus.com/dxl' version='6.0'
replicaid='85256C7500771804' path='dxlhelloworld.nsf'
title='DXL Hello World'>
<databaseinfo dbid='85256C7500771804' odsversion='43'
diskspace='327680'
percentused='86.40625' numberofdocuments='1'>
<datamodified><datetime>
20021118T165757,31-05
</datetime></datamodified>
<designmodified><datetime>
20021118T165803,33-05
</datetime></designmodified>
</databaseinfo>
<acl maxinternetaccess='editor'>
<aclentry name='-Default-' default='true'
level='noaccess' readpublicdocs='true'
writepublicdocs='true'/>
<aclentry name='OtherDomainServers'
type='servergroup' level='noaccess'
readpublicdocs='false' writepublicdocs='false'/>
|
|
ターゲットのフォーマットが何であれ、エクスポート操作はすべてDXLによって開始され、最もシンプルな場合はDXLで終了します。上記は、データベースのコンテンツをDXLとしてエクスポートする非常にシンプルなエージェントです。これは、この記事のサンプル・コードのzipファイルに含まれているDXL Hello Worldというデータベースの一部で、LDD Sandobox(英語)からダウンロードできます。このデータベースには、1つのビュー、1つのフォーム、および1つのデータ・フィールドを持つ1つの文書が格納されています。DXL Hello Worldデータベースを開く前に、パス名C:\dxlで指定されたディレクトリーをハード・ディスクに作成します。その後データベースを開いて1つの文書を開き、[アクション]メニューから[1. Export DXL]を選択して、エージェントを実行します。
Export DXLエージェント
Domino Designerヘルプ・ファイルから少し変更されているエージェントはNotesStreamオブジェクトを使用して、NSFのコンテンツをデータベースにちなんで命名されたファイルdxlhelloworld_all.xmlに書き込みます。このファイルに拡張子.dxlを付けていれば、ファイルがDXLフォーマットであることが分かりやすくなり、さらに明確になったかもしれません。拡張子.xmlを選んだ理由は、Internet Explorer(IE)のXMLファイル・タイプとの自動関連付け機能およびXMLファイルを適切なフォーマットで表示するレンダリング機能を利用するためです。Windows Explorer上でこのファイルをダブルクリックすると、有効なXMLファイルであればIE上で開きます。(無効な場合は、エラー・メッセージおよびファイル内のエラー位置を指すポインターが表示されます。これは、XMLファイル作成時にエラーをデバッグする手助けとなります。)
Export DXLエージェントの最初の部分では、NotesStreamオブジェクトを作成してdxlhelloworld.nsfのコンテンツを表しています。
Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
Dim stream As NotesStream
Set stream = session.CreateStream
filename$ = "c:\dxl\" & Left(db.FileName, Len(db.FileName) - 4)
& "_all.dxl"
If Not stream.Open(filename$) Then
Messagebox "Cannot open " & filename$ & ". Check to make
sure this directory exists.",, "Error"
Exit Sub
End If
Call stream.Truncate
|
|
(NotesStream.Truncateメソッドは、出力ファイルが読み取り専用で書き込めない場合、エラーを生成します)
次に、入力と出力の2つのパラメーターを持つNotesDXLExporterオブジェクトが設定され、プロセスが呼び出されます。エクスポーター・プロセスを実際に実行する呼び出しは、エクスポーター・オブジェクトの仕様からは分離されていることに注意してください。これは、パイプライン処理が必要な場合に重要になります。また、処理前に、この他にも多くのセットアップが必要です。
Dim exporter As NotesDXLExporter
Set exporter = session.CreateDXLExporter(db, stream)
exporter.OutputDOCTYPE = False
Call exporter.Process
End Sub |
|
CreateDXLExporterメソッドに入力/出力パラメーターを設定しなかった場合、SetInputメソッドおよびSetOutputメソッドでも設定することができます。これは、エクスポート操作を繰り返す場合に便利な手法です。その例を、次に示します。
Dim exporter As NotesDXLExporter
Set exporter = session.CreateDXLExporter
Call exporter.SetInput(db)
Call exporter.SetOutput(stream)
exporter.OutputDOCTYPE = False
Call exporter.Process
End Sub |
|
exporter.OutputDOCTYPE=False行では、XMLに関するより複雑な問題を提起しています。この行がTrueに設定された場合(またはTrueがデフォルト値のため、省略された場合)、出力には次のような行が含まれるでしょう。
|
<!DOCTYPE database SYSTEM 'xmlschemas/domino_6_0.dtd'>
|
|
この行はDOCTYPE宣言と呼ばれ、XML文書の構造を定義するファイルをポイントします。この文書タイプ定義(DTD)は、文書すべての要素タイプ、子要素タイプ、それらの出現順序と出現回数、さらにすべてのHYPERLINK"http://xmlwriter.net/xml_guide/attlist_declaration.shtml"属性、エンティティー、および文書のその他の部分を宣言します。たとえばDomino DOCTYPE宣言では、データベース要素が文書によって作成されたXML DOMツリーのルート要素であり、他の宣言は同じSYSTEM上のファイルにパス名xmlschemas/domino_6_0.dtdで指定された文書として含まれていることを宣言しています。Notesディレクトリーを実際に見れば、Dataフォルダー、JVMフォルダー、およびMUIフォルダーと並んでXMLSchemasフォルダーがあるのが分かるでしょう。XML Schemasフォルダーの中にはdomino_6_0.dtd(147 KbのDTDファイル)があり、開いて読むことができます。
DOCTYPEについて
では、どうしてDOCTYPE宣言を記述する(または記述しない)のでしょうか?XML、XSL、DOM、およびSAXには多くの理解し難い点がありますが、それらはこの記事の範囲外なので、ここではその一部についてほんのわずかだけ説明します。XMLには、妥当なXMLと整形式XMLの2種類があるとだけ言っておきます。妥当なXMLにはDTDがあります。整形式のXMLにはDTDがありませんが厳密に構成されています。つまり、すべてのエンティティーが宣言され、適正に閉じられ、正確にネストされています。XMLの仕様では、DTDはオプションとして設定されています。整形式のXMLは自己文書化するためDTDを使用せずに処理することができますが、妥当なXMLではできません。Internet ExplorerなどDTDの使用方法を知らないアプリケーションは、DOCTYPE宣言で行き詰まってしまい、ファイルの処理ができなくなります。
Domino DXLファイルを使用するアプリケーションの一部はDOCTYPE宣言が存在していることに依存し、一部はDOCTYPE宣言が存在していないことに依存しているため、XMLの仕様ではDOCTYPE宣言をオプションに設定しています。NotesDXLExporterクラスでは、DOCTYPE宣言をOutputDOCTYPEプロパティーによってサポートしています。
今回は出力処理を何も行わないため、このExport DXLエージェントでは機能的な差は実際のところありません。しかし、出力をInternet Explorer上で表示したいので、Falseに設定しています。DXLを別のXMLプロセスに対してパイプライン処理していた場合でも、同様にFalseに設定する必要があったでしょう。一般に、始めのうちは出力のDOCTYPE宣言を省略する方がよいでしょう。
Export DXLエージェントの出力は、40 KBのファイルです。DOCTYPE宣言が含まれていないため、Internet Explorerから開くことができます。Internet Explorerから開くと、この記事の冒頭部分で例として使用したDXLコードと同じDXLコード(もちろんDOCTYPE宣言はありません)で始まる、見慣れたコードが表示されます。全体に目を通すと、NSFファイルのすべての要素が表示されていることが分かります。ACL、ログ・エントリー、エージェント、さらにデータベース・アイコンまで表示されています。
文書を1つしか持っていないデータベースの場合、40 KBのデータは必要以上の処理が要求されます。最終的にNSFファイルを再作成することが目的でない限り、設計メモ、ACLエントリーなどを実際にエクスポートする必要はないでしょう。不要な要素のより分けは、NotesNoteCollectionを使用して開始します。
NotesNoteCollectionクラスには、エクスポートに含めるべきメモ・タイプを切り替えるプロパティーが含まれています。約30のメモ・タイプは、SelectDocuments、SelectHelpAbout、SelectSubformsなどのプロパティーとして個別に指定できます。また、半ダースほどの手法によって、いくつかの関連タイプのメモすべてを組み込んだり除外したりできます。たとえばSelectAllCodeElementsメソッドは、すべてのエージェント、データベース・スクリプト、スクリプト・ライブラリー、データ接続、アウトライン、およびデータベース内の各種コード要素のワンストップ・ショッピングのようなものです。プロパティーおよびメソッドは、ブール引数のtrue(つまり、このタイプのメモはエクスポートされます)またはfalse(このタイプのメモはエクスポートされません)によって、オン/オフを切り替えます。さらに、CreateNoteCollectionメソッドのBooleanプロパティーは、マスター・スイッチのような役割を果たします。CreateNoteCollectionをfalseに設定して0から選択を開始するか、またはCreateNoteCollectionをtrueに設定してすべての値から開始できるようにし、各メモ・タイプのプロパティーを使用するか、または選択に合うように複数タイプを包含するメソッドの1つを使用します。たとえば以下のようにします。
Dim nc as NotesNoteCollection
Set nc = db.CreateNoteCollection(False)
Call nc.SelectAllDesignElements(True)
|
|
上記コードが設計要素のみを含んだNoteCollectionオブジェクトを作成するのに対して、下記コードは設計要素を除いたデータベースのメモすべてを含むNoteCollectionオブジェクトを作成します。
Dim nc as NotesNoteCollection
Set nc = db.CreateNoteCollection(True)
Call nc.SelectAllDesignElements(False)
|
|
これらのコンストラクター・プロパティーを設定すると、BuildCollectionメソッドを呼び出すことによってコレクション・オブジェクトが作成されます。これは、次のサンプル・コードで示されています。
Export Only Documentsエージェント
文書のみを含みデータベースの他の要素すべてを除外するNoteCollectionオブジェクトを作成するには、DXL Hello Worldデータベースの[2. Export Only Documents]エージェントを実行します。Export Only Documentsエージェントは、NotesNoteCollectionのSelectDocumentsプロパティーを使用します。
Dim nc As NotesNoteCollection
Set nc = db.CreateNoteCollection(False)
nc.SelectDocuments=True
Call nc.BuildCollection
|
|
NotesNoteCollectionオブジェクトは、DXLExporterの入力として指定されます。
Dim exporter As NotesDXLExporter
Set exporter = session.CreateDXLExporter(nc, stream)
exporter.OutputDOCTYPE = False
Call exporter.Process
|
|
このエージェントを実行した結果、dxlhelloworld_documents.xmlというXMLファイルが生成されます。このXMLファイルは、文書メモ(この場合は1つのメモのみ)、文書要素を含むファイルのルート要素(<database>)、および当然ながら文書要素に含まれる要素を表す要素のみから構成されているため、はるかに簡潔になっています。実際、ここで記載するのにも十分簡潔です。
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE database SYSTEM 'xmlschemas/domino_6_0.dtd'>
<database xmlns='http://www.lotus.com/dxl' version='6.0'
replicaid='85256C7500771804' path='dxlhelloworld.nsf'
title='DXL Hello World'>
<databaseinfo dbid='85256C7500771804' odsversion='43'
diskspace='327680' percentused='88.828125'
numberofdocuments='1'>
<datamodified>
<datetime>
20021118T165757,31-05
</datetime>
</datamodified>
<designmodified>
<datetime>
20021125T165404,83-05
</datetime>
</designmodified>
</databaseinfo>
<document form='Hello'>
<noteinfo noteid='8fa'
unid='7C962ABED6913FAD85256C750078A86F' sequence='1'>
<created>
<datetime>
20021118T165754,39-05
</datetime>
</created>
<modified>
<datetime>
20021118T165757,31-05
</datetime>
</modified>
<revised>
<datetime>
20021118T165757,30-05
</datetime>
</revised>
<lastaccessed>
<datetime>
20021118T165757,30-05
</datetime>
</lastaccessed>
<addedtofile>
lt;datetime>
20021118T165757,30-05
</datetime>
</addedtofile>
</noteinfo>
<updatedby>
<name>CN=David DeJean/O=DeJean</name>
</updatedby>
<item name='HelloData'>
<text>Hello World.</text>
</item>
</document>
</database>
|
|
この出力はきっと必要以上に冗長なものでしょう。しかし先に進むためには、要素を省略するだけでなく、DXLファイルの構造を編集する必要があります。そのためには、LotusScriptで新たに利用できるようになった3つのXML構文解析ツール(DOMパーサー、SAXパーサー、XSLトランスフォーマー)の1つを使用して、データを構文解析しなければなりません。この記事ではDOMパーサーおよびSAXパーサーについて解説し、XMLトランスフォーマーについてはこのシリーズの次回の記事に残しておきます。
NotesNoteCollectionクラスは、余り柔軟なプログラミング・ツールではありません。DXL出力に含めるメモと含めないメモを選択することはできますが、Domino DTDによって定義されるメモ自身の構造および内容は変更できません。DXLファイルには、Dominoデータベースに格納されているデータだけでなく、すべてのメタデータ(データの保存・変更時刻、保存者名、表示方法)も含まれます。
このメタデータは、膨大な量になる場合があります。前述の例では、データは「Hello World」データベースに蓄積されます。この例では、26項目にも上るメタデータ(データベース名と属性、データベース情報と属性、文書名と属性、項目名)が設定(または隠蔽)されています。また、7つの日付/時刻値、2つのデータ・タイプ、3つの固有ID、およびオリジナル・データベースが占有するディスク容量の解析も記述されています。
これらのほとんどはDomino以外では、いかなる用途の場合にも不要なデータです。さらに通常は、保持すべき要素の内部深くまで複数のレイヤーによってネストされています。たとえば、<databaseinfo>、<noteinfo>、および<updatedby>という要素はすべて、database要素に含まれています。これらの値は、データ・タイプIDとして機能する要素(datetime、text)の内部にネストされています。(オリジナルのXML仕様では、データ・タイプは認識されずすべてテキストとして扱われていました)
では、どうすればいいのでしょうか? DXLトランスポーターがメモ内の各要素を認識し、無視するかDXL出力ファイルに転送するかを決めることができれば、役立つかもしれません。しかしそのためには、より大きなトランスポーターが必要になるでしょう。さらに、この機能をすでに実現しているXMLプロセスがあります。それはパーサーと呼ばれ、DOMパーサーとSAXパーサーは、異なるデータ・モデルを使用します。
Export Only Data-SAXエージェント
Domino SAXパーサーは、次の13個のイベントを発生させることができます。それらは、SAX_Characters、SAX_EndDocument、SAX_EndElement、SAX_Error、SAX_FatalError, SAX_IgnorableWhitespace、SAX_NotationDecl、SAX_ProcessingInstruction、SAX_ResolveEntity、SAX_StartDocument、SAX_StartElement、SAX_UnparsedEntityDecl、およびSAX_Warningです。LotusScriptでは、各イベントを個別のサブルーチンにフックすることによって応答することができます。サンプル・エージェントを見るには、DXL Hello Worldデータベース内の[3. Export Only Data-SAX]エージェントを参照してください。このエージェントを[アクション]メニューから選択して実行すると、dxlhelloworld_sax.xmlというファイルがC:\dxlディレクトリー内に作成されます。
このSAXパーサー・エージェントのタスクは、メタデータを適正に評価して、DXL Hello World データベースの実データを含むXMLファイルを出力することであり、その他のことはできる限り何もしません。
コード開始部分は、Export Only Documentsスクリプトとよく似ています。文書のみからなるNotesNoteCollectionを作成し、そのコレクションをDXLとしてエクスポートします。次に、SAXパーサーに対してDXLをパイプライン処理します。このパイプライン処理は自動的に行われ、DXLエクスポーターに入力引数(nc)を指定しますが、出力引数は省略します。(パイプライン内に他のプロセッサー・オブジェクトが存在する場合は、同様に入力を指定しますが出力は省略します。)パイプライン処理の最終プロセスであるSAXパーサーは、入力(DXLエクスポーター)および出力(NotesStreamオブジェクト)の両方の引数を取ります。
Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
'Build a NoteCollection to limit the export file to documents
Dim nc As NotesNoteCollection
Set nc = db.CreateNoteCollection(False)
nc.SelectDocuments=True
Call nc.BuildCollection
'Create the DXL exporter
Dim exporter As NotesDXLExporter
Set exporter = session.CreateDXLExporter(nc)
exporter.OutputDOCTYPE = False
'Create the output file
Dim xml_out As NotesStream
Set xml_out=session.CreateStream
filename$ = "c:\dxl\" + Left(db.FileName, Len(db.FileName) - 4)
+ "_sax.xml"
If Not xml_out.Open(filename$) Then
Messagebox "Cannot open " + filename$ + ". Check to make
sure this directory exists.",, "Error"
Exit Sub
End If
Call xml_out.Truncate
'Create the SAX parser
Dim saxParser As NotesSAXParser
Set saxParser=session.CreateSAXParser(exporter, xml_out)
|
|
SAXパーサー・エージェントの残りの部分では、SAXパーサーの各イベントと対応するサブルーチン間の接続を定義します。
On Event SAX_Characters From saxParser Call SAXCharacters
On Event SAX_EndDocument From saxParser Call
SAXEndDocument
On Event SAX_EndElement From saxParser Call
SAXEndElement
On Event SAX_Error From saxParser Call SAXError
On Event SAX_FatalError From saxParser Call SAXFatalError
On Event SAX_IgnorableWhitespace From saxParser Call
SAXIgnorableWhitespace
On Event SAX_NotationDecl From saxParser Call
SAXNotationDecl
On Event SAX_ProcessingInstruction From saxParser Call
SAXProcessingInstruction
On Event SAX_StartDocument From saxParser Call
SAXStartDocument
On Event SAX_StartElement From saxParser Call
SAXStartElement
On Event SAX_UnparsedEntityDecl From saxParser Call
SAXUnparsedEntityDecl
On Event SAX_Warning From saxParser Call SAXWarning
exporter.Process
End Sub |
|
(プロセス開始行はexporter.Processであり、saxParser.Processではないことに注意してください。パイプライン処理を実行する場合は、すべてのプロセス・オブジェクトをセットアップし、その後パイプライン内の最初のオブジェクトを呼び出してプロセスを開始します)
イベントごとにハンドラー・サブルーチンを記述する必要はありません。このサンプル・エージェントは、SAXイベントごとにメッセージ・ボックスを表示するDomino Designerヘルプ・ファイルのエージェントを応用したものです。完全なリストでは、読者独自のコードの開始点が表示されています。無視できる空白やエンティティーは、気にしなくてもよいでしょう。(ただし、エラーおよび警告についてはおそらく知る必要があるでしょう)
これらの各イベント・ハンドラーはパラメーターによってデータを照会し、Outputメソッドを使用して必ず明示的に出力を生成します。たとえば文字データのイベント・ハンドラーは、NULLを無視し、その他の文字を文字列として出力ファイルに記述します。
Sub SAXCharacters (Source As Notessaxparser, Byval Characters As String,_Count As Long)
If Characters <> Chr(10) Then
Source.Output(Characters)
End If
End Sub
|
|
要求されている出力が何であれ、Export Only Data−SAXエージェントはイベント・ハンドラーの各フォーマットを使用してXMLファイルを構文解析し、新たなXMLファイルを出力します。SAXStartDocumentサブルーチンがトリガーされると、XML宣言および改行/CRを出力ファイルに記述します。
Sub SAXStartDocument (Source As Notessaxparser)
Source.Output({<?xml version='1.0' encoding='utf-8'?>} + Chr
(13)+Chr(10))
End Sub
|
|
SAXStartElementサブルーチンは、要素の開始およびその属性(ある場合)をフォーマットします。SAXStartElementサブルーチンには、特定の要素名をチェックし、その要素が見つかった場合は出力に要素を記述せずにサブルーチンを終了する3つのif文が含まれています。
Sub SAXStartElement (Source As Notessaxparser, Byval ElementName As String, Attributes As NotesSaxAttributeList)
If ElementName = "databaseinfo" Then
Exit Sub
End If
If ElementName = "noteinfo" Then
Exit Sub
End If
If ElementName = "updatedby" Then
Exit Sub
End If
Source.Output({<}+ ElementName)
|
|
属性は、NotesSAXAttributesListクラスを使用して処理されます。配列は、要素の属性すべての名前と値を持つパーサーによって自動的に作成されます。ループは各属性を順番に選択し、Source.Output()ステートメントは属性の名前、値、および書式制御文字を出力に記述します。すべての属性が処理されると、サブルーチンは要素の開始タグを「>」で閉じて、終了します。
Dim i As Integer
If Attributes.Length > 0 Then
Dim attrname As String
For i = 1 To Attributes.Length
attrname = Attributes.GetName(i)
Source.Output({ } + attrname+{="}
+Attributes.GetValue(attrname) + {"})
Next
End If
Source.Output({>})
End Sub
|
|
要素が値を持っている場合は、次に文字データとして表示され、SAXパーサーはSAX_Charactersイベントを発生させます。SAXCharactersサブルーチンは値の全内容が「Chr$(10)」ではないことをチェックして確認し、「Chr$(10)」より大きい場合は出力に記述します。
Sub SAXCharacters (Source As Notessaxparser, Byval Characters As String,_
Count As Long)
If Characters <> Chr(10) Then
Source.Output(Characters)
End If
End Sub
|
|
SAX_EndElementイベントは、SAXStartElementサブルーチンからドロップされた3つの要素名のチェックを持つSAXEndElementサブルーチンをトリガーします。要素名が他の名前である場合、SAX_EndElementイベントは定様式終了タグおよび改行/CRを出力に記述します。
Sub SAXEndElement (Source As Notessaxparser, Byval ElementName As String)
If ElementName = "databaseinfo" Then
Exit Sub
End If
If ElementName = "noteinfo" Then
Exit Sub
End If
If ElementName = "updatedby" Then
Exit Sub
End If
Source.Output(
</} + ElementName + {>} + Chr(13)+Chr(10))
End Sub
|
|
このエージェントの出力は、Export Only Documentsエージェントの出力とよく似ています。実際、少し似すぎています。以下は、Internet Explorerで表示用にフォーマットした出力です。
<?xml version="1.0" encoding="utf-8" ?>
HYPERLINK "C:\dxl\" - <database
xmlns="http://www.lotus.com/dxl"version="6.0"
replicaid="85256C7500771804" path="dxlhelloworld.nsf"
title="DXL Hello World">
HYPERLINK "C:\dxl\" - <datamodified>
<datetime>20021209T173711,55-05</datetime>
</datamodified>
HYPERLINK "C:\dxl\" - <designmodified>
<datetime>20021210T132250,26-05</datetime>
</designmodified>
HYPERLINK "C:\dxl\" - <document form="Hello">
HYPERLINK "C:\dxl\" - <created>
<datetime>20021204T092656,26-05</datetime>
</created>
HYPERLINK "C:\dxl\" - <modified>
<datetime>20021204T092658,78-05</datetime>
</modified>
HYPERLINK "C:\dxl\" - <revised>
<datetime>20021204T092658,77-05</datetime>
</revised>
HYPERLINK "C:\dxl\" - <lastaccessed>
<datetime>20021204T092658,77-05</datetime>
</lastaccessed>
HYPERLINK "C:\dxl\" - <addedtofile>
<datetime>20021204T092658,77-05</datetime>
</addedtofile>
<name>CN=David DeJean/O=DeJean</name>
HYPERLINK "C:\dxl\" - <item name="HelloData">
<text>Hello World.</text>
</item>
</document>
</database>
|
|
<databaseinfo>、<noteinfo>、<updatedby>という要素およびその属性すべてを適切に取り除くことはできましたが、これらのサブ要素はまだそのままです。<databaseinfo>の子要素である要素<datamodified>および<designmodified>は、ここでは<database>の子要素になっています。また、<noteinfo>の子要素であった5つの日付/時刻要素は、<document>の子要素になっています。さらに、以前は<modifiedby>の子要素であった<name>要素も、同様に<document>の子要素になっています。どうしたのでしょうか?
SAXパーサーは、指定したとおりのことを正確に実行し、命名した3つの要素から開始タグと終了タグを削除しました。これがSAXパーサーが実行できたすべてです。なせならSAXパーサーが実行方法を知っているのはこれだけだからです。子要素やサブ要素のことは分かりません。知っているのはイベントだけです。各種用途に対して、また各種XMLファイルに対しても、書き込みやすく実行しやすいだけで十分でしょう。しかし、複雑なDomino DTDではより強力なパーサーが必要です。幸いなことに、すでに入手してあります。
NotesDOMParserクラスは、DOMパーサーをLotusScriptでインプリメントします。このクラスを呼び出すコードは、当然ながら他のNotesXMLProcessorオブジェクトとよく似ています。しかし、DOMパーサーの機能はSAXパーサーとは大きく異なります。DOMパーサーが構築するDOMツリーはメモリー・オブジェクトであり、単なるイベント・ストリームではありません。したがって、DOMパーサーはDOMツリーを再帰的にトラバースします。SAXイベントによって表示されているデータが何であれ、プロセスは1回に限定されません。出力の制御は、DOMツリー内の他のノードの存在と内容に関連する可能性があります。
DOM用語では、DOMツリー内の各項目はノードと呼ばれ、独自のプロパティーを持ちます。NotesDOMNodeというLotusScriptクラスには、カレント・ノードのタイプ識別に使用するNodeTypeというプロパティーが提供されています。NotesDOMNodeの派生クラスは、要素ノード、属性ノード、テキスト・ノード、およびXML宣言ノードのような専用ノードなど、各種ノード・タイプを表します。これらの派生クラスによって、LotusScriptコードはノード・タイプの各インスタンスを処理します。SAXパーサー用に設定された各種イベント・ハンドラーの代わりに、DOMパーサーはDOMツリーのトラバース、見つけたノードのタイプ・チェック、および適切なクラスでプログラミングしたあらゆるアクションの処理を実行する1つのサブルーチンのみを必要とします。
Export Only Data−DOMエージェント
[4. Export Only Data−DOM]エージェントは、SAXパーサー・エージェントに設定したのと同じ課題に対処するため、つまりできる限り多くのデータをメタデータから分離するために記述されています。Export Only Data−DOMエージェントのコードを見ると、SAXパーサーのスクリプトと同じように開始しているのが分かります。文書のみからなるNotesNoteCollectionを作成し、そのコレクションをDXLとしてエクスポートします。次にDXLを、今回はDOMパーサーに対してパイプライン処理します。このパイプライン処理の方法は同じです。DXLエクスポーターに入力引数(nc)を指定しますが、出力引数は省略し、exporter.Precessがプロセスを開始します。DOMパーサーがエクスポーターから出力を取得してDOMツリーに変え、walkTreeというサブルーチンがDOMツリーをトラバースします。
Dim docNode As NotesDOMDocumentNode
Set docNode = domParser.Document
Call walkTree(docNode)
|
|
NotesDOMParserオブジェクトのDocumentプロパティーは、DOMツリーのルート・ノードを表します。walkTreeサブルーチンは、このルート・ノードからDOMツリーのトラバースを開始します。セットアップが終わると、walkTreeサブルーチンは最初にSelect Caseステートメントを実行します。Select Caseステートメントでは、ノードというオブジェクトはNotesDOMNodeのインスタンスです。後続のcase文中で大文字の値は、NodeTypeプロパティーの正規の値です。条件が一致すると、一致したcase文中のコードが実行されます。
Sub walkTree (node As NotesDOMNode)
...
If Not node.IsNull Then
Select Case node.NodeType
|
|
実際、すべてのDOMツリーはDocumentノードを持つため、DOMNODETYPE_DOCUMENT_NODEに直ちに一致し、サブルーチンは次の3つを実行します。最初は基本的な処理で、Documentの1番目の子ノードおよび子ノードの総数を取得して、DOMツリーの次のレベルをトラバースできるようにします。
Case DOMNODETYPE_DOCUMENT_NODE: 'It's the Document
node
'Get the number of children of Document
Set child = node.FirstChild 'Get first child node
Dim numChildNodes As Integer
numChildNodes = node.NumberOfChildNodes
|
|
次に、出力をXMLにフォーマットしているため、極めてシンプルですが本当に必要なXML宣言をNotesDOMXMLDeclNodeクラスのプロパティーを使用して出力に書き込みます。配置する位置は、必ずXML宣言を記述しなければならない、Documentの最初の子ノードのプロパティーです。
'Create an XML declaration for the output
Dim xNode As NotesDOMXMLDeclNode
Set xNode = node.FirstCild
domParser.Output({<?xml version="} + xNode.Version + {" ?>})
|
|
最後に、walkTreeサブルーチンは次のノードを取得して、再帰します。つまり、childというオブジェクト(ここではDocumentの2番目の子ノード)を引数として自身を呼び出します。これはwhile文の内部で実行され、繰り返す間に未処理の子ノード数が減少します。
'Call walkTree for the first child
While numChildNodes > 0
Set child = child.NextSibling 'Get next node
numChildNodes = numChildNodes - 1
Call walkTree(child)
Wend
|
|
このエージェントでは、他のcase文のうち2つのみが重要です。カレント・ノードが改行以外の値を持つテキスト・ノードの場合、値は出力に書き込まれます。(これらの文字を除去するのは、単に表面上のことです。これらの文字はDominoデータ内の他の場合は空のフィールドから出力され、出力がInternet Explorerでレンダーされた場合は、ストレイ・ボックス文字として表示されます。したがって、ここでは除去します。)
Case DOMNODETYPE_TEXT_NODE: 'It's a plain text node
If node.NodeValue <> Chr(10) Then
domParser.Output(node.NodeValue)
End If
|
|
コア・ノード・タイプは、要素メモです。DOMツリー内のほとんどのノードはこのタイプで、属性および値の両方を持つことができます。このcase文が一致すると、最初にすべきことはSAX Parser SAXStartElementサブルーチンで実行したことと同じです。削除すべき3つの要素名をチェックし、その要素が見つかった場合には、出力に要素を記述せずにサブルーチンを終了します。要素が3つの要素のうちの1つでない場合には、要素タブの最初の部分(「<」)および要素名を出力に記述します。
Case DOMNODETYPE_ELEMENT_NODE:
If node.NodeName = "databaseinfo" Then
Exit Sub
End If
If node.NodeName = "noteinfo" Then
Exit Sub
End IfIf node.NodeName = "updatedby" Then
Exit Sub
End If
domParser.Output({<} + node.NodeName)
|
|
次に属性をチェックして、要素が属性を持つ場合、適切なフォーマットで属性を出力に記述し、「>」を記述してタグを閉じます。
Dim numAttributes As Integer, numChildren As Integer
numAttributes = node.attributes.numberofentries
Set attrs = node.Attributes 'Get attributes
Dim i As Integer
For i = 1 To numAttributes 'Loop through attributes
Set a = attrs.GetItem(i)
domParser.Output({ }+a.NodeName + {="} +
a.NodeValue + {"})
Next
domParser.Output(">")
|
|
次に子ノードをチェックして、存在する場合は最初の子ノードを取得し、前回同様にwhile文を使用してwalkTreeから再度再帰します。
numChildren = node.NumberOfChildNodes
Set child = node.FirstChild 'Get child
While numChildren > 0
Call walkTree(child)
Set child = child.NextSibling 'Get next child
numChildren = numChildren - 1
Wend
|
|
最後に、カレント・ノードのすべての子を処理してwhile文からドロップアウトすると、要素の終了タグを記述します。
|
domParser.Output( {</} + node.nodeName + {>} + LF)
|
|
DOMツリー内のすべてのノードが処理されると、制御はメイン初期化サブルーチンのラベルresultsに戻ります。出力ストリームはクローズし、これですべて終了です。
results:
Call outputStream.Close
Exit Sub
errh:
outputStream.WriteText ("errh: "+Cstr(Err)+": "+Error+LF)
Resume results
End Sub
|
|
ラベルerrhも同様に、このコードの重要な部分です。エラーがどこかで発生している場合には、エラー番号とメッセージが出力ファイルとして記述され、パーサーが終了している可能性があります。XMLパーサーがエラーに弱い理由は、最も起こりやすいエラーが整形式でないデータを必要とするからです。整形式でないデータはXMLでないため、処理されません。たとえばこれを、HTMLパーサーであるWebブラウザーの場合と比較します。HTMLでは、たとえ完全に正確なコード表現でなくても画面上にレンダーすることが目的のため、Webブラウザーは極端にフォールト・トレラントです。一方、XMLは第一に整合性を取ります。
DOMパーサーの出力は、SAXパーサーで取得した出力に比べてはるかにコンパクトです。
<?xml version="1.0" ?>
<database path="dxlhelloworld.nsf" title="DXL Hello World"
xmlns="http://www.lotus.com/dxl"version="6.0"
replicaid="85256C7500771804">
<document form="Hello">
<item name="HelloData">
<text>Hello World.</text>
</item>
</document>
</database>
|
|
これは、整形式XMLです。このコードには、削除したい3つの要素タイプは含まれていません。さらに重要なことは、SAXパーサーが出力に渡した子要素(日付/時刻要素と要素)もすべて削除されています。その理由は再帰です。ノード処理を停止すると、そのすべての子ノードの処理も自動的に停止します。これに対してSAXパーサーでは、親ノードがどのように処理されているかによらず、これらの子要素すべてに対してイベントを生成します。さらに多くのコードを記述していればSAXパーサーの出力を改善できたかもしれません。しかしその場合、削除する要素名をチェックするために、さらに10個のif文を追加する必要があったでしょう。DOMパーサーは、筆者の目的によりかなったものでした。別の状況では、そうではないかもしれません。パーサーを別のパーサーと比較して、またはXSLトランスフォーマーと比較して選択する時期について、確かで迅速なルールはありません。XSLトランスフォーマーについては、このシリーズの次回の記事で詳しく解説します。
この記事では、XMLおよびDOMパーサーとSAXパーサーについて簡単に説明しましたが、DominoのXMLサポートについては他にもたくさん説明することがあります。ここで紹介したサンプル・コードによって、データをDXLとしてエクスポート/インポートすることを開始でき、LotusScriptでXMLを扱う方法が分かるはずです。このシリーズの次回の記事では、DXLをHTMLや他のXML言語など、その他のフォーマットに変換するExtensible Style Sheets(XSL)について説明します。

著者について
Sally Blanning DeJeanとDavid DeJeanは、Lotus NotesとDomino製品の誕生以来それを仕事の対象としており、それら製品についての執筆活動を行ってきました。彼らはまた、Notesに関する正に最初の本『Lotus Notes at Work』の共著者でもあります。CLPプリンシパルであるSallyは、この他にもNotesに関する本を執筆しており、Notes/Domino専任の開発者です。CLPであるDavidはいくつものコンピューター関係の出版物の編集者兼ライターであり、Notes、インターネット・アプリケーション、テクニカル/マーケティング通信技術の開発会社DeJean & Clemensの共同経営者でもあります。
|
 |
|
|
|