主要内容

本页采用了机器翻译。点击此处可查看英文原文。

创建并使用自定义查找器

MATLAB Report Generator 报告生成 API 支持创建查找器,在数据容器中搜索指定对象并以可报告的形式返回结果。查找器允许您在报告生成器中将搜索逻辑与报告逻辑分开。查找器还促进了搜索逻辑的重用,从而加快了报告生成器的开发。此示例显示如何开发和使用查找器来生成报告。

定义查找器

创建查找器需要创建一个 MATLAB 类来定义查找器的属性和行为。以下部分解释了创建查找器类所需的步骤。解释使用名为 GrantFinder 的类作为示例。该类的代码位于随此脚本附带的文件 GrantFinder.m 中。GrantFinder 类定义了一个查找器,可以查找和格式化由美国国家人文基金会 (NEH) 颁发的资助。

创建骨架类定义文件

使用 MATLAB 编辑器(而非实时编辑器)为您的查找器创建一个骨架类定义,例如

GrantFinder.png

指定查找器基类

将报告 API 的 mlreportgen.finder.Finder 类指定为您的查找器的基类。

finder2.png

此基类定义了查找器所共有的属性,包括

  • Container:用于引用查找器要搜索的容器的属性。例如,GrantFinder 使用此属性来存储对其创建的授权数据库的引用。

  • Properties:查找器客户端使用的属性,用于指定对象必须具备的属性值才能满足搜索。例如,此属性允许 GrantFinder 客户端指定授权属性,授权必须具备该属性才能作为搜索 NEH 授权数据库的结果返回。

mlreportgen.finder.Finder 类指定了您的查找器类定义必须定义的其他属性和方法。这可确保您的查找器能够与报告 API 配合使用。

定义查找器构造函数

定义一个创建查找器实例的函数,例如,

finderConstructor.png

GrantFinder 构造函数使用 MATLAB parseFile 函数从磁盘读取授予 XML 文件并将其转换为 MAXP DOM 文档。然后,它将 DOM 文档传递给 mlreportgen.finder.Finder 构造函数,该构造函数将 MAXP DOM 文档设置为查找器的 Container 属性的值。将 NEH 数据库存储为 MAXP DOM 文档允许查找器使用 MATLAB 的本机 API 来搜索数据库。

构造函数还调用 reset 函数来初始化用于搜索授权数据库的变量。GrantFinder 类定义了该函数。类似地,您的类必须定义一个重置函数。reset 函数确保客户端可以使用您的查找器对其容器进行多次搜索。请参阅定义 reset 方法 以了解更多信息。

定义一个 find 方法

定义一个方法来在查找器容器中搜索满足用户指定约束的对象。Find 方法必须返回包含其找到的对象的结果对象数组。结果对象是基类型 mlreportgen.finder.Result 的对象。将查找结果作为结果对象返回,允许查找器的用户将结果添加到报告或报告章节中。请参阅定义查找器结果以了解更多信息。将查找结果作为 MATLAB 数组返回,您可以使用 for 循环来处理搜索结果,例如

defineFindMethod1.png

GrantFinderfind 方法说明了 find 方法的定义。

find method.png

此查找方法使用 getElementsGrantFinder 类定义的搜索工具函数,请参阅定义搜索实用工具方法)在授权数据库中搜索符合查找器的 Properties 属性所指定的属性值约束的授权。getElements 函数将名为 Elements 的内部属性设置为其搜索结果。结果是 matlab.io.xml.dom.Elementmatlab.io.xml.dom.Element)的列表。

然后,find 方法将此元素转换为 GrantResult 类型的结果对象数组。它使用 GrantResult 构造函数从包含授权数据的 matlab.io.xml.dom.Element 对象创建授权结果对象。

定义 hasNextnext 方法

您的查找器类定义必须定义 hasNextnext 方法。第一次调用时,您的 hasNext 方法必须创建一个搜索结果队列,如果队列不为空,则返回 true。在后续调用中,如果队列为空,则 hasNext 方法必须返回 true,否则返回 false。您的 next 方法必须在第一次调用时返回队列中的第一个结果,在下一次调用时返回下一个结果,依此类推,直到队列为空。

这些方法旨在允许查找器的客户端使用 MATLAB while 循环来搜索查找器的容器,例如,

hasNextMethod.png

GrantFinder 类说明了 hasNext 方法。

此方法首先检查是否已经创建了搜索队列,如查找器的 IsIterating 属性所示。如果队列已经存在且不为空,此方法返回 true。如果队列存在且为空,此方法返回 false。如果队列尚不存在(即,这是该方法的第一次调用),则 hasNext 方法将创建一个结果队列,如下所示。首先,它使用其内部的 getElements 方法来获取符合查找器的 Properties 属性指定的搜索条件的授权。getElements 方法将名为 ElementCount 的内部查找器属性设置为找到的结果数。如果 ElementCount 大于零,则 hasNext 方法将名为 NextElementIndex 的内部属性设置为 1。查找器的 next 方法使用此属性来保存搜索队列的状态,即队列中的下一个项目。最后,如果队列最初不为空,则查找器返回 true;否则返回 false

GrantFindernext 方法对 hasNext 方法创建的队列进行操作。

next method.png

定义搜索实用工具方法

您的查找器的 findhasNext 方法必须在查找器的容器中搜索满足搜索约束的对象。您应该考虑定义一个两种方法都可以使用的搜索实用工具。例如,GrantFinder hasNextnext 方法都将搜索委托给名为 getElements 的内部实用工具。getElements 方法反过来将搜索委托给名为 XPath 的 XML 文档搜索 API(请参阅 XPath 教程)。

search util.png

创建一个 InvalidPropertyNames 属性

您的查找器必须定义一个名为 InvalidPropertyNames 的属性,该属性指定不能用于约束搜索的对象属性。mlreportgen.finder.Finder 基类使用此属性来验证查找器的 Properties 属性所指定的用户指定的搜索属性是否有效。如果不是,基类将抛出错误。换句话说,如果客户端将查找器的 Properties 属性设置为无效属性,则基类将引发错误。通过这种方式,报告 API 的基本查找器会为您的查找器处理属性有效性检查。

如果您的查找器可以使用任何搜索对象属性作为搜索约束,则它应该将 InvalidPropertyNames 属性设置为空。例如,GrantFinder 可以处理任何授予属性。因此它将此属性设置为空:

定义一个 reset 方法

查找器必须能够支持多种搜索,以避免每次搜索都需要创建一个查找器。因此,报告 API 的基本查找器类强制您的查找器类定义一个 reset 方法,该方法重置查找器的搜索逻辑使用的变量,例如,

reset.png

定义查找器结果

如果不存在合适的定义,则必须创建一个类来定义查找器返回的结果对象。本节展示如何定义查找器结果对象。它使用名为 GrantResult 的类作为示例。GrantResult 类定义了在定义查找器部 分中用作示例的 GrantFinder 类返回的结果。此脚本附带的 GrantResult.m 文件包含 GrantResult 类的代码。定义查找器结果需要执行以下任务。

指定结果基类

mlreportgen.finder.Result 定义为结果类的基类,例如,

result class.png

定义 Object 属性

定义一个名为 Object 的属性,结果对象的客户端可以使用它来访问结果对象包含的找到的对象。将 protected 指定为查找器的 SetAccess 属性的 Object 值。这确保只有您的结果可以指定其包含的找到的对象。

ObjectProperty.png

您的结果构造函数必须将找到的对象设置为其 Object 属性的值。结果构造函数可以使用基类构造函数来执行此任务,例如,

GrantResult.png

显示找到的对象属性

结果的 Object 属性允许客户端访问找到的对象及其属性。但是,访问这些属性可能需要额外的代码或专业知识。您可能希望将找到的对象的部分或全部属性作为结果对象的属性公开。例如,GrantResult 类公开了授权属性的以下子集。

Properties.png

这使得授权查找器结果对象的客户端无需自己提取这些属性。结果的构造函数应提取要公开的属性的值,并将相应的结果属性设置为提取的值,例如,

grantResultFull.png

请注意,GrantResult 将一些授予属性组合成一个单一的公开属性。例如,它将授权的 InstCityInstStateInstPostalCodeInstCountry 属性公开到名为 Location 的单个结果属性中。

在此示例中,构造函数使用内部方法从授权对象(即 MAXP DOM Element 对象)中提取授权属性,例如,

getGrantProperty.png

定义一个 getReporter 方法

您必须为结果对象定义一个 getReporter 方法,该方法返回一个报告器对象,该报告器对象报告结果对象所包含的找到的对象。此方法允许查找器的客户端通过将结果添加到 ReportSectionChapter 对象来报告查找操作的结果。例如,

defineFindMethod1.png

报告或章节的 append 方法知道结果对象必须具有 getReporter 方法,该方法返回格式化结果包含的数据的报告器。所以如果您将结果对象添加到报告或章节中,append 方法会调用结果的 getReporter 方法来获取结果报告器,并将结果报告器添加到报告或报告器中,从而对结果数据进行格式化并包含在报告中。

GrantResult 类定义定义了一个 getReporter 方法,该方法返回报告 API 的 mlreportgen.report.BaseTable 报告器的自定义版本。BaseTable 报告器生成一个带有编号标题的表。GrantResult 类自定义了 BaseTable 报告器来生成授权属性表,例如,

以下代码显示了 GrantResult 类如何自定义 BaseReporter 以生成编号的授予属性表:

getreporter1.png

getReporter2.png

getReporter3.png

使用查找器

此脚本显示如何使用查找器生成报告。该脚本使用“定义查找器”部分中使用的 GrantFinder 示例来生成有关 2010 年 NEH 向选定州的机构提供的资助的 PDF 报告。该脚本执行以下任务。

导入报告生成器 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) ...
    );

使用授权查找器填充状态结构的 GrantsNGrants 字段。创建资助查找器。

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 年该州机构获得的赠款总数和奖励总金额。表中各州按赠款数量的降序排列。每个州都通过超链接指向详细列出该州所获补助金的章节。

chapter.png

创建摘要章节容器

首先创建章节容器。

ch = Chapter("Title","Grant Summary");

创建拨款摘要表的内容

创建一个包含表头内容的元胞数组。

header = {'State','Grants Awarded','Amount Awarded'};

预先分配一个元胞数组来包含表体内容。元胞数组有 Rx3 行和列,其中 R 是状态数,3 是每个状态报告的摘要项数。

body = cell(numel(states), 3);

使用 MATLAB sort 函数,根据授予的授权数量对状态数组进行排序。sort 函数返回 ind,即状态数组的索引数组。ind 数组的第一个索引是授予最多的州的索引,第二个索引是授予数量第二多的州的索引,等等。

[~, ind] = sort([states.NGrants],"descend");

按授予数量循环遍历各个州,填写每个州的摘要信息。使用变量 rowIdx 作为与当前状态相对应的元胞数组行的索引。

rowIdx = 0;

下面一行按照收到的授权顺序重新排列 states 数组,并创建一个 for 循环,该循环在循环的每次迭代中将已排序的 states 数组中的每个结构体分配给变量 state

for state = states(ind)

更新行索引以指向与当前状态相对应的元胞数组行。

    rowIdx = rowIdx+1;

该脚本将州拨款详情章节的超链接作为该州表中的第一个条目,例如,

下面一行使用 DOM InternalLink 构造函数来创建超链接。InternalLink 构造函数接受两个参量:链接目标 ID 和超链接的文本。该脚本使用当前州的邮政编码作为链接目标 ID,并使用州的名称作为链接文本。稍后,当脚本创建授权详细信息章节时,它会在章节标题中插入一个链接目标,其 ID 是该州的邮政编码。这样就完成了超链接的创建。

    body(rowIdx, 1) = {InternalLink(state.PostalCode,state.Name)};

将此州的授权总数分配给其元胞数组行中的第二项。

    body(rowIdx, 2) = {state.NGrants};

计算授予该州的总金额。

    totalAwarded = 0;
    for grant = state.Grants
        totalAwarded = totalAwarded + str2double(grant.AwardAmount);
    end

使用 cur2str 方法将总金额格式化为美元金额,例如,

并将格式化的结果分配为此状态的元胞数组中的第三项和最后一项。

    formattedCurrency = sprintf('\x24%.2f',totalAwarded);
    body(rowIdx,3) = ...
        {regexprep(formattedCurrency,'\d{1,3}(?=(\d{3})+\>)','$&,')};
end

要创建摘要表,请将标题和正文元胞数组传递给 mlreportgen.dom.FormalTable 对象的构造函数。

table = FormalTable(header,body);

正式表是具有表头和正文的表。FormalTable 构造函数接受两个参量:一个指定表头内容的元胞数组和一个指定表体内容的元胞数组。构造函数将元胞数组内容转换为定义表的 DOM TableRowTableEntry 对象,从而使脚本无需自行创建必要的表对象。

设置拨款摘要表的格式

此时,摘要表如下所示:

unformatted.png

这不太可读。标题与正文格式相同,且各列之间没有间距。

在以下步骤中,脚本将调整标题文本格式,使其如下所示:

foramtted table.png

首先,脚本使用 DOM TableColSpecGroup 对象指定表列的宽度和对齐方式。TableColSpecGroup 对象指定一组列的格式。摘要表只有一组列,因此脚本只需要创建一个 TableColSpecGroup 对象。

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;

注意该脚本可以使用多达三个 TableColSpec 对象(每个表列一个),以覆盖组列样式。第一个 TableColSpec 对象应用于第一列,第二个对象应用于第二列,等等。该脚本只需要为组分配一个列规范对象,因为它只会覆盖第一列的默认样式。但是,如果只需要更改第三列,则必须分配三个列规范对象,而将前两个列规范对象的 Style 属性留空。

默认表样式挤满了表条目。因此,脚本使用 DOM InnerMargin 格式对象在条目上方创建一些空间,以将它们与上方行中的条目分开。InnerMargin 对象在文档对象和包含它的对象之间创建空间(内边距),例如,在表条目中的文本和表条目的边框之间。InnerMargin 构造函数可选地接受四个参量,即文档对象的左、右、上、下内边距。

该脚本使用此构造函数创建 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 资助的信息。它包含大约 6000 条 XML 格式的记录。可在 NEH Grant Data 中获取。此示例使用数据库 XML 文件的本地副本 NEH_Grants2010s.xml

该数据库由一个 Grants 元素组成,该元素包含一组 Grant 元素,每个元素包含一组授予数据元素。以下是数据库的摘录,说明了其结构体:

另请参阅

| | |

主题