カスタム ファインダーの作成と使用
MATLAB Report Generator レポート生成 API は、指定されたオブジェクトをデータ コンテナーから検索し、結果をレポート可能な形式で返すファインダーの作成をサポートしています。ファインダーを使用すると、レポート ジェネレーターで検索ロジックをレポート ロジックから分離できます。また、ファインダーにより検索ロジックの再利用が促進されるため、レポート ジェネレーターの開発がスピードアップします。この例では、ファインダーを開発および使用してレポートを生成する方法を示します。
ファインダーの定義
ファインダーを作成するには、ファインダーのプロパティと動作を定義する MATLAB クラスを作成する必要があります。続くセクションでは、ファインダー クラスを作成するために必要な手順について説明します。説明では、例として GrantFinder
という名前のクラスを使用します。クラスのコードは、このスクリプトに付属するファイル GrantFinder.m
にあります。GrantFinder
クラスは、全米人文科学基金 (NEH) によって交付された助成金の検索や書式設定ができるファインダーを定義します。
スケルトン クラス定義ファイルの作成
ファインダーのスケルトン クラス定義を作成するには、"MATLAB エディター" ("ライブ エディター" ではない) を使用します。たとえば、次のようになります。
ファインダーの基底クラスの指定
レポート API の mlreportgen.finder.Finder
クラスをファインダーの基底クラスとして指定します。
この基底クラスは、ファインダーに共通のプロパティを定義します。以下が含まれます。
Container
: ファインダーによって検索されるコンテナーを参照するために使用されるプロパティ。たとえば、GrantFinder
はこのプロパティを使用して、作成する助成金データベースへの参照を格納します。Properties
: 検索の条件を満たすオブジェクトのプロパティ値を指定するためにファインダー クライアントが使用するプロパティ。たとえば、このプロパティにより、GrantFinder クライアントが助成金のプロパティを指定し、NEH 助成金データベースの検索の結果として条件を満たす助成金を返すようにすることができます。
mlreportgen.finder.Finder
クラスでは、ファインダー クラス定義で定義する必要があるその他のプロパティとメソッドを指定します。このようにして、ファインダーがレポート API で確実に機能するようにします。
ファインダー コンストラクターの定義
ファインダーのインスタンスを作成する関数を定義します。たとえば、次のようになります。
GrantFinder
コンストラクターは、MATLAB 関数 parseFile
を使用して、ディスクから助成金の XML ファイルを読み取り、MAXP DOM ドキュメントに変換します。次に、DOM ドキュメントを mlreportgen.finder.Finder
コンストラクターに渡します。これにより、MAXP DOM ドキュメントがファインダーの Container
プロパティの値として設定されます。NEH データベースを MAXP DOM ドキュメントとして保存することで、ファインダーが MATLAB のネイティブ API を使用してデータベースを検索できるようにします。
コンストラクターは、助成金データベースの検索に使用される変数を初期化する関数 reset
も呼び出します。GrantFinder クラスは、この関数を定義します。同様に、使用するクラスでも関数 reset が定義されていなければなりません。関数 reset
は、クライアントがファインダーを使用してコンテナーの複数の検索を確実に実行できるようにします。詳細については、reset
メソッドの定義を参照してください。
find
メソッドの定義
ユーザー指定の制約を満たすオブジェクトをファインダー コンテナーから検索するメソッドを定義します。find メソッドは、検出オブジェクトを含む結果オブジェクトの配列を返さなければなりません。結果オブジェクトは、基本型 mlreportgen.finder.Result
のオブジェクトです。検索結果を結果オブジェクトとして返すことで、ファインダーのユーザーは結果をレポートまたはレポートの章に追加できます。詳細については、"ファインダーの結果の定義" を参照してください。検索結果を MATLAB 配列として返すことで、for ループを使用して検索結果を処理できます。たとえば、次のようになります。
GrantFinder
の find
メソッドは、find
メソッドの定義を示しています。
この find メソッドは、GrantFinder
クラスが定義する検索ユーティリティ関数 ("検索ユーティリティ メソッドの定義" を参照) である getElements
を使用して、ファインダーの Properties
プロパティによって指定されたプロパティ値の制約を満たす助成金を助成金データベースから検索します。関数 getElements
は、検索結果に Elements
という名前の内部プロパティを設定します。結果は matlab.io.xml.dom.Element
(matlab.io.xml.dom.Element
) のリストになります。
次に、find
メソッドは、この要素を GrantResult
型の結果オブジェクトの配列に変換します。GrantResult
コンストラクターを使用して、助成金データを含む matlab.io.xml.dom.Element
オブジェクトから交付結果オブジェクトを作成します。
hasNext
メソッドおよび next
メソッドの定義
ファインダー クラスの定義では、hasNext
メソッドと next
メソッドを定義しなければなりません。最初の呼び出しで、hasNext
メソッドは検索結果のキューを作成し、キューが空でない場合に true を返さなければなりません。後続の呼び出しにおいて、hasNext
メソッドは、キューが空の場合に true
を返し、それ以外の場合に false
を返さなければなりません。next
メソッドは、最初の呼び出しで最初の結果をキューに返さなければなりません。次の呼び出しでは次の結果を返し、以降同様に、キューが空になるまで行います。
これらのメソッドは、ファインダーのクライアントが MATLAB の while
ループを使用してファインダーのコンテナーを検索できるようにすることを目的としています。たとえば、次のようになります。
GrantFinder
クラスは hasNext
メソッドを示しています。
このメソッドは、ファインダーの IsIterating
プロパティが示すところに従って、検索キューが既に作成されているかどうかを最初に確認します。キューが既に存在し、かつ空でない場合、このメソッドは true
を返します。キューが存在し、かつ空の場合、このメソッドは false
を返します。キューがまだ存在しない場合 (つまり、これがメソッドの最初の呼び出しである場合)、hasNext
メソッドは次のように結果キューを作成します。まず、内部の getElements
メソッドを使用して、ファインダーの Properties
プロパティで指定された検索基準を満たす交付情報を取得します。getElements
メソッドは、ElementCount
という名前の内部ファインダー プロパティを、検出結果の数に設定します。ElementCount
が 0 より大きい場合、hasNext
メソッドは、NextElementIndex
という名前の内部プロパティを 1 に設定します。ファインダーの next
メソッドは、このプロパティを使用して検索キューの状態、つまりキュー内の次の項目を保存します。最後に、キューが最初から空でない場合、ファインダーは true
を返します。それ以外の場合は false
を返します。
GrantFinder
の next
メソッドは、hasNext
メソッドによって作成されたキューで動作します。
検索ユーティリティ メソッドの定義
ファインダーの find
メソッドと hasNext
メソッドは、検索制約を満たすオブジェクトをファインダーのコンテナーから検索しなければなりません。両方のメソッドを使用できる検索ユーティリティを定義することを検討する必要があります。たとえば、GrantFinder
の hasNext
メソッドと next
メソッドはどちらも、検索を getElements
という名前の内部ユーティリティに委任します。getElements
メソッドは、XPath
という名前の XML ドキュメント検索 API に検索を委任します (XPath Tutorial を参照)。
InvalidPropertyNames
プロパティの作成
ファインダーは、検索の制約に使用できないオブジェクト プロパティを指定する InvalidPropertyNames
という名前のプロパティを定義しなければなりません。mlreportgen.finder.Finder
基底クラスは、このプロパティを使用して、ファインダーの Properties
プロパティで指定されたユーザー指定の検索プロパティが有効であることを確認します。そうでない場合、基底クラスはエラーをスローします。つまり、クライアントがファインダーの Properties
プロパティを無効なプロパティに設定した場合、基底クラスはエラーをスローします。このようにして、レポート API の基底ファインダーは、ファインダーのプロパティの有効性チェックを処理します。
ファインダーが任意の検索オブジェクト プロパティを検索制約として使用できるようにする場合は、InvalidPropertyNames
プロパティを空に設定する必要があります。たとえば、GrantFinder
が任意の助成金プロパティを処理できるとします。この場合、このプロパティを次のように空に設定します。
reset
メソッドの定義
すべての検索ごとにファインダーを作成しなくてもよいように、ファインダーは複数の検索をサポートしなければなりません。このため、レポート API のファインダー基底クラスは、ファインダー クラスに、ファインダーの検索ロジックで使用される変数をリセットする reset
メソッドを定義するように強制します。たとえば、次のようになります。
ファインダーの結果の定義
適切な定義が存在しない場合は、クラスを作成して、ファインダーによって返される結果オブジェクトを定義しなければなりません。このセクションでは、ファインダー結果オブジェクトを定義する方法を示します。例として GrantResult
という名前のクラスを使用します。GrantResult
クラスは、"ファインダーの定義" セクションで例として使用されている GrantFinder
クラスによって返される結果を定義します。このスクリプトに付属する GrantResult.m
ファイルには、GrantResult
クラスのコードが含まれています。ファインダーの結果の定義には、以下のタスクが伴います。
結果基底クラスの指定
結果クラスの基底クラスとして mlreportgen.finder.Result
を定義します。たとえば、次のようになります。
Object
プロパティの定義
結果オブジェクトのクライアントが結果オブジェクトに含まれる検出オブジェクトにアクセスするために使用できる、Object
という名前のプロパティを定義します。ファインダーの Object
プロパティの SetAccess
値として protected
を指定します。これにより、確実にこの結果だけが、そこに含まれる検出オブジェクトを指定できるようにすることができます。
結果コンストラクターは、検出オブジェクトをその Object
プロパティの値として設定しなければなりません。結果コンストラクターは、基底クラスのコンストラクターを使用してこのタスクを実行できます。たとえば、次のようになります。
検出オブジェクト プロパティの公開
結果の Object
プロパティにより、クライアントは検出オブジェクト、つまりそのプロパティにアクセスできます。ただし、プロパティにアクセスするには、追加のコードや専門知識が必要になる場合があります。検出オブジェクトのプロパティの一部またはすべてを、結果オブジェクトのプロパティとして公開する場合があります。たとえば、GrantResult
クラスは、次のような助成金のプロパティのサブセットを公開します。
これにより、助成金ファインダーの結果オブジェクトのクライアントがこれらのプロパティ自体を抽出する必要がなくなります。結果のコンストラクターは、公開するプロパティの値を抽出し、その抽出された値を、対応する結果のプロパティに設定する必要があります。たとえば、次のようになります。
GrantResult
が、いくつかの助成金プロパティを 1 つの公開プロパティに結合していることに注意してください。たとえば、助成金の InstCity
、InstState
、InstPostalCode
、および InstCountry
のプロパティを、Location
という名前の単一の結果プロパティにして公開します。
この例では、コンストラクターは内部メソッドを使用して、助成金オブジェクトから助成金プロパティを抽出します。これは MAXP DOM Element
オブジェクトです。たとえば、次のようになります。
getReporter
メソッドの定義
結果オブジェクトに含まれる検出オブジェクトについて報告するレポーター オブジェクトを返すように、結果オブジェクトの getReporter メソッドを定義しなければなりません。このメソッドは、ファインダーのクライアントが、結果を Report
オブジェクト、Section
オブジェクト、または Chapter
オブジェクトに追加するだけで、検索操作の結果についてレポートを作成できるようにします。例:
レポートまたは章の append
メソッドは、結果オブジェクトを、結果に含まれるデータを書式設定するレポーターを返す getReporter
メソッドをもつものとして扱います。そのため、結果オブジェクトがレポートまたは章に追加された場合、append
メソッドは、結果の getReporter
メソッドを呼び出して結果レポーターを取得し、結果レポーターをレポートまたはレポーターに追加します。それにより、結果データが書式設定され、レポートに含められます。
GrantResult
クラス定義は、レポート API の mlreportgen.report.BaseTable
レポーターのカスタマイズ バージョンを返す getReporter
メソッドを定義します。BaseTable
レポーターは、番号付きタイトルがあるテーブルを生成します。GrantResult
クラスは BaseTable
レポーターをカスタマイズして、助成金プロパティのテーブルを生成します。たとえば、次のようになります。
次のコードは、GrantResult
クラスが BaseReporter
をカスタマイズして、番号付きの助成金プロパティ テーブルを生成する方法を示しています。
ファインダーの使用
このスクリプトは、ファインダーを使用してレポートを生成する方法を示します。このスクリプトは、「ファインダーの定義」セクションで使用された GrantFinder の例を使用して、選択された州の機関に対する 2010 年の NEH 助成金交付についての PDF レポートを生成します。スクリプトは以下のタスクを実行します。
Report Generator API のインポート
MATLAB Report Generator のレポート API に含まれるクラスをインポートします。クラスをインポートすることで、スクリプトが非修飾名 (短縮名など) を使用してクラスを参照できるようになります。
import mlreportgen.report.* import mlreportgen.dom.*
レポート コンテナーの作成
レポート API の mlreportgen.report.Report
クラスを使用して、レポート用の PDF コンテナーを作成します。スクリプトがレポート API をインポートすることによって、非修飾名でクラスを参照できることに注意してください。
rpt = Report("grant","pdf");
レポートのタイトル ページの作成
レポート API の TitlePage
クラスを使用して、レポートにタイトル ページを追加します。
append(rpt,TitlePage( ... "Title","NEH Grants", ... "Subtitle","By State for 2010", ... "Image","neh_logo.jpg", ... "Author","John Doe" ... ));
レポートの目次の作成
レポート API の TableOfContents
クラスを使用して、目次を追加します。
append(rpt,TableOfContents);
レポート データの検索
構造体の配列を使用して、このレポートに含める州を指定します。各構造体には、特定の州に対する次のデータが格納されます。
Name
: 州の名前PostalCode
: 州の郵便番号Grants
: 州の機関に対する助成金交付。このフィールドは最初は空ですNGrants
: この州の機関に対する助成金の交付数 (最初は空)
states = struct( ... "Name",{"California","Massachusetts","New York"}, ... "PostalCode",{"CA","MA","NY"}, ... "Grants",cell(1,3), ... "NGrants",cell(1,3) ... );
助成金ファインダーを使用して、州の構造体の Grants
フィールドと NGrants
フィールドに入力します。助成金ファインダーを作成します。
f = GrantFinder;
州の配列をループします。州ごとに、ファインダーの Properties
プロパティを使用して、州に交付された助成金の検索を制約します。以下の助成金プロパティを使用して、検索を制約します。
InstState
:助成金を受け取った機関が所在する州の郵便番号を指定します。YearAwarded
:助成金が交付された年を指定します。
n = numel(states); for i = 1:n f.Properties = [ {"InstState",states(i).PostalCode}, ... {"YearAwarded","2010"}]; states(i).Grants = find(f); states(i).NGrants = numel(states(i).Grants); end
助成金交付概要の章の作成
レポートの最初の章として助成金交付概要を作成します。助成金交付概要の章には、タイトルと助成金交付概要テーブルが含まれます。テーブルの各行には、2010 年に州の機関に対して交付された助成金の総交付数と総額がリストされます。州は、交付数の降順でテーブルに表示されます。各州は、交付された助成金の詳細を説明する章にハイパーリンクされます。
概要の章コンテナーの作成
章コンテナーを作成することから始めます。
ch = Chapter("Title","Grant Summary");
助成金交付概要テーブルのコンテンツの作成
テーブル ヘッダーのコンテンツを格納する cell 配列を作成します。
header = {'State','Grants Awarded','Amount Awarded'};
テーブル本体のコンテンツを格納する cell 配列を事前に割り当てます。cell 配列には R 行 3 列の行と列があります。ここで、R は州の数、3 は各州について報告される概要の項目数です。
body = cell(numel(states), 3);
MATLAB 関数 sort
を使用して、助成金の交付数で州の配列を並べ替えます。関数 sort
は、州の配列に対するインデックスの配列である ind
を返します。配列 ind
の最初のインデックスは交付数が最も多い州のインデックスで、2 番目は交付数が 2 番目に多い州のインデックスといった具合になります。
[~, ind] = sort([states.NGrants],"descend");
助成金交付数で州をループ処理し、各州の概要情報を埋めていきます。現在の州に対応する cell 配列行へのインデックスとして、変数 rowIdx
を使用します。
rowIdx = 0;
次の行は、助成金の受領順に states
配列を並べ替えた後、並べ替えられた states
配列内の各構造体をループの反復ごとに変数 state
に割り当てる for
ループを作成します。
for state = states(ind)
現在の州に対応する cell 配列の行を指すように行インデックスを更新します。
rowIdx = rowIdx+1;
スクリプトは、州のテーブルの最初のエントリとして、その州の助成金詳細の章へのハイパーリンクを入力します。たとえば、次のようになります。
続く行で、DOM InternalLink
コンストラクターを使用してハイパーリンクを作成します。InternalLink
コンストラクターは、リンク ターゲット ID とハイパーリンク テキストの 2 つの引数を取ります。このスクリプトは、現在の州の郵便番号をリンク ターゲット ID として使用し、州の名前をリンク テキストとして使用します。後で、スクリプトが助成金詳細の章を作成するときに、州の郵便番号を ID とするリンク ターゲットが章のタイトルに挿入されます。これでハイパーリンクの作成は完了です。
body(rowIdx, 1) = {InternalLink(state.PostalCode,state.Name)};
この州の助成金の総交付数を cell 配列行の 2 番目の項目に割り当てます。
body(rowIdx, 2) = {state.NGrants};
この州に対する総交付額を計算します。
totalAwarded = 0; for grant = state.Grants totalAwarded = totalAwarded + str2double(grant.AwardAmount); end
cur2str
メソッドを使用して、総額を金額として書式設定します。たとえば、次のようになります。
書式設定された結果を、この州の cell 配列の 3 番目 (最後) の項目として割り当てます。
formattedCurrency = sprintf('\x24%.2f',totalAwarded); body(rowIdx,3) = {regexprep(formattedCurrency,'\d{1,3}(?=(\d{3})+\>)','$&,')}; end
概要テーブルを作成するには、ヘッダーと本体の cell 配列を mlreportgen.dom.FormalTable
オブジェクトのコンストラクターに渡します。
table = FormalTable(header,body);
形式的テーブルは、ヘッダーと本体をもつテーブルです。FormalTable
コンストラクターは、2 つの引数 (テーブルのヘッダーのコンテンツを指定する cell 配列、およびその本体のコンテンツを指定する cell 配列) を取ります。コンストラクターによって、cell 配列のコンテンツは、テーブルを定義する DOM TableRow
および TableEntry
オブジェクトに変換されるため、必要なテーブル オブジェクトをスクリプトに作成させる必要がなくなります。
助成金交付概要テーブルの書式設定
この時点で、概要テーブルは次のようになります。
これは非常に読みにくいです。見出しは本体と同じ形式で、列も間隔が空いていません。
続く手順では、スクリプトはヘッダー テキストの書式設定を次のように調整します。
最初に、スクリプトは DOM TableColSpecGroup
オブジェクトを使用して、テーブル列の幅と配置を指定します。TableColSpecGroup
オブジェクトは、列のグループの書式を指定します。概要テーブルには列のグループが 1 つしかないため、スクリプトが作成する必要のある TableColSpecGroup
オブジェクトは 1 つだけです。
grp = TableColSpecGroup;
TableColSpecGroup
オブジェクトを使用すると、スクリプトでテーブルの列の既定のスタイルを指定できます。スクリプトは、列の既定の幅として 1.5in
を指定し、列の既定の配置として中央揃えを指定します。
grp.Style = {HAlign("center"),Width("1.5in")};
スクリプトは、TableColSpec
オブジェクトを使用して、最初の列の既定の列配置をオーバーライドします。
specs(1) = TableColSpec;
specs(1).Style = {HAlign("left")};
grp.ColSpecs = specs;
grp.Span = 3;
table.ColSpecGroups = grp;
メモ: スクリプトは、テーブル列ごとに 1 つずつ、最大 3 つの TableColSpec
オブジェクトを使用して、グループの列スタイルをオーバーライドできます。最初の TableColSpec
オブジェクトは最初の列に適用され、2 番目は 2 番目の列に適用されるといった具合になります。スクリプトは、最初の列の既定のスタイルのみをオーバーライドするため、グループに列仕様オブジェクトを 1 つだけ割り当てる必要があります。ただし、3 番目の列のみを変更する必要がある場合は、最初の 2 つの列仕様オブジェクトの Style
プロパティを空のままにし、3 つの列仕様オブジェクトを割り当てる必要があります。
既定のテーブル スタイルでは、テーブル エントリが密接しています。そのため、スクリプトは DOM InnerMargin
形式のオブジェクトを使用してエントリの上部にスペースを作成し、エントリを上の行のエントリから離します。InnerMargin
オブジェクトは、ドキュメント オブジェクトとそれを含むオブジェクトとの間、たとえば、テーブル エントリ内のテキストとテーブル エントリの境界線の間に空白 (内側の余白) を作成します。InnerMargin
コンストラクターは、オプションで 4 つの引数 (ドキュメント オブジェクトの左、右、上、下の内側の余白) を取ります。
スクリプトはこのコンストラクターを使用して、3 ポイントの上部内側余白の書式を作成します。次に、概要テーブルの本体セクション内のエントリのスタイルに対して、この書式を割り当てます。
table.Body.TableEntriesStyle = {InnerMargin("0pt","0pt","3pt","0pt")};
最後に、スクリプトはテーブル ヘッダーを次のように書式設定して、グレーの背景に太字の白いテキストで構成されるようにします。
table.Header.row(1).Style = {Bold,Color("white"),BackgroundColor("gray")};
レポートへの概要の章の追加
append(ch,table); append(rpt,ch);
助成金詳細の章の作成
州の構造体をループ処理します。
for state = states
州ごとに章を作成して、州の助成金詳細を保持します。章のタイトルにリンク ターゲットを挿入し、最初の章にある概要テーブルのハイパーリンクのターゲットとして機能するようにします。
ch = Chapter("Title",{LinkTarget(state.PostalCode),state.Name});
州の交付結果をループ処理します。
for grant = state.Grants
各交付結果について、その結果を章に追加します。
append(ch,grant);
交付結果は、選択された助成金プロパティのテーブルを作成するレポーターを返す getReporter
メソッドをもちます。章の append
メソッドは、結果のレポーターを取得して章に追加するように事前構成されています。したがって、章に交付情報を追加することは、章に結果プロパティ テーブルを追加するのと同じことになります。たとえば、次のようになります。
end append(rpt,ch); end
レポート オブジェクトを閉じる
レポート オブジェクトを閉じると、レポート オブジェクトが指定する PDF 出力ファイル (grant.pdf
) が生成されます。
close(rpt);
レポートの表示
rptview(rpt);
付録: NEH 助成金データベース
この例で使用されるデータベースのソースは、全米人文科学基金 (NEH) です。データベースには、2010 ~ 2019 年までの期間の NEH 助成金に関する情報が格納されています。XML 形式の約 6000 個の記録が格納されています。これは、NEH Grant Data で入手することができます。この例では、データベース XML ファイル NEH_Grants2010s.xml
のローカル コピーを使用します。
このデータベースは、1 つの Grants
要素で構成されています。これには Grant
要素のセットが含まれており、その各々に助成金データ要素のセットが含まれます。以下はデータベースからの抜粋で、その構造を示しています。
参考
mlreportgen.finder.Finder
| mlreportgen.report.Report
| mlreportgen.report.Chapter
| rptview