Main Content

Create and Use a Custom Finder

The MATLAB Report Generator report generation API supports creation of finders that search data containers for specified objects and return the results in reportable form. Finders allow you to separate search logic from report logic in your report generators. Finders also promote reuse of search logic, thereby speeding development of report generators. This example shows how to develop and use a finder to generate a report.

Define a Finder

Creating a finder entails creating a MATLAB class that defines the finder's properties and behavior. The following sections explain the steps needed to create a finder class. The explanation uses a class named GrantFinder as an example. The code for the class resides in a file, GrantFinder.m, that accompanies this script. The GrantFinder class defines a finder capable of finding and formatting grants awarded by the U.S. National Endowment for the Humanities (NEH).

Create a Skeleton Class Definition File

Use the MATLAB Editor (not the Live Editor) to create a skeleton class definition for your finder, for example

classdef GrantFinder
end

Specify Finder Base Class

Specify the Report API's mlreportgen.finder.Finder class as the base class for your finder.

classdef GrantFinder < mlreportgen.finder.Finder
end

This base class defines properties that are common to finders, including

  • Container: a property used to reference the container to be searched by the finder. For example, the GrantFinder use this property to store a reference to a grant database that it creates.

  • Properties: a property used by finder clients to specify property values that an object must have to satisfy a search. For example, this property allows a GrantFinder client to specify the grant properties that a grant must have to be returned as a result of a search of the NEH grant database.

The mlreportgen.finder.Finder class specifies other properties and methods that your finder class definition must define. This ensures that your finder works with the Report API.

Define a Finder Constructor

Define a function that creates an instance of your finder.

function this = GrantFinder()
    % GrantFinder Create an instance of a grant finder

    % Import the MATLAB® API for XML Processing (MAXP)
    import matlab.io.xml.dom.*

    % Create a parser and call the parseFile method to convert the
    % grant XML file to a matlab.io.xml.dom.Document object.
    parser = matlab.io.xml.dom.Parser;

    % Assume that the grant XML file is in the current directory.
    documentDoc = parseFile(parser,"NEH_Grants2010s.xml");

    this@mlreportgen.finder.Finder(documentDoc);

    % Initialize the finder
    reset(this);

end

The GrantFinder constructor uses the MATLAB parseFile function to read the grant XML file from disk and convert it to a MAXP DOM document. It then passes the DOM document to the mlreportgen.finder.Finder constructor, which sets the MAXP DOM document as the value of the finder's Container property. Storing the NEH database as a MAXP DOM document allows the finder to use MATLAB's native API to search the database.

The constructor also calls a reset function that initializes variables used to search the grant database. The GrantFinder class defines this function. Similarly, your class must define a reset function. The reset function ensures that a client can use your finder to conduct multiple searches of its container. See Define a reset Method for more information.

Define a find Method

Define a method to search the finder container for objects that meet user-specified constraints. The find method must return an array of result objects that contain the objects that it finds. Result objects are objects of base type mlreportgen.finder.Result. Returning the find results as result objects allows a user of your finder to add the results to a report or report chapter. See Define a Finder Result for more information. Returning the find results as a MATLAB array allows you to use for loop to process search results, for example,

import mlreportgen.report.*

rpt = Report("myReport","pdf");
finder = MyFinder(container);
for result = find(finder)
    append(rpt,result);
end
close(rpt);

The GrantFinder's find method illustrates definition of a find method.

function results = find(this)
    %find Search the grant database and return the results
    %   results = find(grantFinder) searches the NEH grant
    %   database that resides in grantFinder, using the optional
    %   constraints specified by grantFinder's Properties
    %   property. This method returns the results as an array of
    %   result objects of type GrantResult.
    %
    %   See also GrantFinder.Properties,GrantResult
    results = [];

    % Get a list of the XML objects that represent the grants
    % that meet this finder's search criteria.
    getElements(this);

    % Convert the XML objects to an array of GrantResult nodes.
    % Return the results.
    for i = 1:this.ElementCount
        node = this.Elements(i);
        results = [results GrantResult(node)]; %#ok<AGROW>
    end
end

This find method uses getElements, a search utility function that the GrantFinder class defines (see Define a Search Utility Method) to search the grant database for grants that meet property value constraints specified by the finder's Properties property. The getElements function sets an internal property named Elements to the result of its search. The result is a list of matlab.io.xml.dom.Element (matlab.io.xml.dom.Element).

The find method then converts this element to an array of result objects of type GrantResult. It uses the GrantResult constructor to create a grant result object from the matlab.io.xml.dom.Element object that contains the grant data.

Define hasNext and next Methods

Your finder class definition must define hasNext and next methods. On its first invocation, your hasNext method must create a queue of search results and return true if the queue is not empty. On subsequent invocations the hasNext method must return true if the queue is empty, false otherwise. Your next method must return the first result in the queue on its first invocation, the next result, on its next invocation, and so on, until the queue is empty.

These methods are intended to allow a client of your finder to use a MATLAB while loop to search your finder's container, for example,

import mlreportget.report.*

rpt = Report("myreport","pdf");
finder = MyFinder(container);
while hasNext(finder)
    append(rpt,next(finder));
end
close(rpt);

The GrantFinder class illustrates a hasNext method.

function tf = hasNext(this)
    %hasNext Return true if the grant queue is not empty
    %   tf = hasNext(grantFinder) creates a search result queue
    %   the first time it is called. The queue contains grants that
    %   reside in grantFinder and that match the search criteria
    %   specified by grantFinder's Properties property. This method
    %   returns true if the grant queue is not empty.
    %
    %   See also GrantFinder, GrantFinder.next, GrantResult

    if this.IsIterating
        if this.NextElementIndex <= this.ElementCount
            tf = true;
        else
            tf = false;
        end
    else
        getElements(this);
        if this.ElementCount > 0
            this.NextElementIndex = 1;
            this.IsIterating = true;
            tf = true;
        else
            tf = false;
        end
    end
end

This method first checks whether it has already created a search queue as indicated by the finder's IsIterating property. If the queue already exists and is not empty, this method returns true. If the queue exists and is empty, this method returns false. If the queue does not yet exist (i.e., this is the method's first invocation), the hasNext method creates a result queue as follows. First, it uses its internal getElements method to get the grants that meet the search criteria specified by the finder's Properties property. The getElements method sets an internal finder property named ElementCount to the number of results found. If ElementCount is greater than zero, the hasNext method sets an internal property named NextElementIndex to 1. The finder's next method uses this property to save the state of the search queue, that is the next item in the queue. Finally, if the queue is not initially empty, the finder returns true; otherwise, false.

The GrantFinder's next method operates on the queue created by the hasNext method.

function result = next(this)
    %next Returns the next item in the grant queue
    %   result = next(grantFinder) returns the first item in the
    %   grant queue the first time it is invoked. It returns the
    %   next item in the queue on subsequent invocations. The
    %   next result is an object of GrantResult type.
    %
    %   Note: invoke hasNext to create the grant queue.
    %
    %   See GrantFinder, GrantFinder.hasNext, GrantResult

    % this.IsIterating is set by hasNext to indicate that
    % it has created a search queue.
    if this.IsIterating
        % this.NextElementIndex and this.ElementCount are
        % initialized by hasNext.
        if this.NextElementIndex <= this.ElementCount

            % Convert the next grant XML element to a GrantResult
            % object
            result = GrantResult(...
                this.Elements.item(this.NextElementIndex-1));

            % Update the next node index
            this.NextElementIndex = this.NextElementIndex+1;

        else
            % This condition can occur if the client invokes
            % next without first invoking hasNext to determine
            % whether any more grants exist.
            error("No more grants exist")
        end
    else
        % This condition can occur if a client invokes this method
        % without first invoking hasNext to create the search
        % result queue.

        % Reset the queue variables.
        reset(this);

        % Initialize the queue.
        if hasNext(this)
            % Returns the first item in the queue.
            result = next(this);
        else
            % This condition can occur if no grants meet the
            % client's search criteria.
            error("No grants exist")
        end
    end
end

Define a Search Utility Method

Your finder's find and hasNext methods must search your finder's container for objects that satisfy search constraints. You should consider defining a search utility that both methods can use. For example, the GrantFinder hasNext and next methods both delegate searching to an internal utility named getElements. The getElements method in turn delegates searching to an XML document search API named XPath (see XPath Tutorial).

function getElements(this)
    %getElements Get grant elements that match search criteria
    %   This is an internal method used to find grant elements
    %   and returns an array of search result object of
    %   type matlab.io.xml.dom.Element. It sets this finder's
    %   "Elements" property to the results of the search.

    % Import the XPath API
    import matlab.io.xml.xpath.*

    if isempty(this.Properties)
        % No search criteria. Set this.Elements to all grants in
        % the data base.

        this.Elements = evaluate(Evaluator,"/Grants/Grant",...
            this.Container,EvalResultType.NodeSet);
    else
        % Use MAXP DOM's XPath API to find grants that match the
        % search criteria specified by this finder's Properties
        % property. For information on this API, see
        % xpath package link
        % Create an XPath expression of the form
        %
        %  "//Grant[(p1 = 'v1') and (p2 = 'v2') and (p3 = 'v3')...]"
        %
        % where p1,v1,p2,v2... are the property-value pairs
        % specified by this finder's Properties property.
        %
        % The XPath expression finds all Grant elements that
        % match the specified properties and evaluate the XPath
        % expression. Set the finder's Elements property to the
        % results of the search.

        expr = compileExpression(Evaluator,...
            sprintf('/Grants/Grant[%s]',makePredicate(this)));

        this.Elements = evaluate(Evaluator,expr,...
            this.Container,EvalResultType.NodeSet);
    end
    this.ElementCount = length(this.Elements);
end

Create an InvalidPropertyNames Property

Your finder must define a property named InvalidPropertyNames that specifies object properties that cannot be used to constrain a search. The mlreportgen.finder.Finder base class uses this property to verify that user-specified search properties specified by your finder's Properties property are valid. If not, the base class throws an error. In other words, if a client sets your finder's Properties property to invalid properties, the base class throws an error. In this way, the Report API's base finder handles property validity checking for your finder.

If your finder can use any search object property as a search constraint, it should set the InvalidPropertyNames property empty. For example, the GrantFinder can handle any grant property. It therefore sets this property empty:

properties (Constant, Hidden)

    % InvalidPropertyNames
    %   Every finder must implement this property. It lists properties
    %   of search objects that cannot be used to constain a search.
    %   You can use any grant finder to constrain a grant search. This
    %   property is therefore empty.
    InvalidPropertyNames = {}
end

Define a reset Method

A finder must be able to support multiple searches to avoid the need to create a finder for every search. For this reason, the Report API's base finder class forces your finder class to define a reset method that resets variables used by your finder's search logic, for example,

function reset(this)
    %reset Resets the finder's search queue
    %   reset(grantFinder) initializes the grantFinder's search
    %   queue variables.
    %
    %   Note: this method is declared in mlreportgen.find.Finder.
    %   Subclasses must define it.

    this.Elements = [];
    this.IsIterating = false;
    this.ElementCount = 0;
    this.NextElementIndex = 0;
end

Define a Finder Result

If a suitable definition does not exist, you must create a class to define the result objects returned by your finder. This section shows how to define a finder result object. It uses a class named GrantResult as an example. The GrantResult class defines results returned by the GrantFinder class used as an example in the Define a Finder section. The GrantResult.m file that accompanies this script contains the code for the GrantResult class. Defining a finder result entails the following tasks.

Specify the Result Base Class

Define mlreportgen.finder.Result as the base class for your result class, for example,

classdef GrantResult < mlreportgen.finder.Result
    %GrantResult Result of an NEH grant search
    %   This class is used by GrantFinder objects to return the results
    %   of searching an NEH grant database.

Define Object Property

Define a property named Object that clients of your result object can use to access the found object that your result object contains. Specify protected as the SetAccess value of your finder's Object property. This ensures that only your result can specify the found object that it contains.

properties (SetAccess = protected)

    %Object Grant object
    %   The value of this object is a matlab.io.xml.dom.Element object
    %   that represents the grant, this result object reports. You can
    %   use the matlab.io.xml.dom.Element object to get grant data not
    %   available through this result object's other properties.
    Object
end

Your result constructor must set the found object as the value of its Object property. Your result constructor can use the base class constructor to perform this task, for example,

function this = GrantResult(grant)
    %GrantResult Construct a grant object
    %   result = GrantResult(grantObj) creates a result object from
    %   grant, a matlab.io.xml.dom.Element object containing a
    %   grant's properties.The grant argument must be of type
    %   matlab.io.xml.dom.Element.
    this@mlreportgen.finder.Result(grant);

Expose Found Object Properties

Your result's Object property allows a client to access the found object and therefore its properties. However, accessing the properties can require extra code or specialized knowledge. You may want to expose some or all of the found object's properties as properties of the result object. For example, the GrantResult class exposes the following subset of a grant's properties.

properties

    %Title Title of this grant
    %   Value is the content of a matlab.io.xml.dom.Element object of
    %   type ProjectTitle
    Title string = ""

    %Institution Institution awarded this grant
    %   Value is the content of a matlab.io.xml.dom.Element object of
    %   type Institution
    Institution string = ""

    %Location Location of institution awared grant
    %   Value is a character array formed from matlab.io.xml.dom.Element
    %   object of type InstCity, InstState, InstPostalCode, and
    %   InstCountry elements, for example,
    %   "West Barnstable, MA 02668-1599 USA".
    Location string = ""

    %AwardAmount Amount of grant
    %   Value is the content of a matlab.io.xml.dom.Element object of
    %   type AwardAmount
    AwardAmount string = ""

    %YearAwarded Year grant was awarded
    %   Value is the content of a matlab.io.xml.dom.Element object of
    %   type YearAwarded
    YearAwarded string = ""

    %ToSupport Purpose of grant
    %   Value is the content of a MAXP grant's ToSupport element
    ToSupport string = ""

    %Paricipant Name and title of grant manager
    %   Value is a string array formed from the content of the
    %   matlab.io.xml.dom.Element grant's Grant/Participant/Firstname,\
    %   Lastname, and ParticipantTypeID elements, for example,
    %
    %       "John Doe, Project Director"
    Participant string = ""

    %Tag User-defined result property
    Tag
end

This saves the client of the grant finder result object from having to extract these properties itself. Your result's constructor should extract the values of the properties to be exposed and set the corresponding result properties to the extracted values, for example,

function this = GrantResult(grant)
    %GrantResult Construct a grant object
    %   result = GrantResult(grantObj) creates a result object from
    %   grant, a matlab.io.xml.dom.Element object containing a
    %   grant's properties.The grant argument must be of type
    %   matlab.io.xml.dom.Element.
    this@mlreportgen.finder.Result(grant);

    this.Title = getGrantProperty(this,"ProjectTitle");
    this.Institution = getGrantProperty(this,"Institution");
    this.Location = sprintf('%s, %s %s %s', ...
        getGrantProperty(this,"InstCity"), ...
        getGrantProperty(this,"InstState"), ...
        getGrantProperty(this,"InstPostalCode"), ...
        getGrantProperty(this,"InstCountry") ...
        );
    this.AwardAmount = getGrantProperty(this,"AwardOutright");
    this.YearAwarded = getGrantProperty(this,"YearAwarded");
    this.ToSupport = getGrantProperty(this,"ToSupport");
    this.Participant = sprintf("%s %s, %s", ...
        getParticipantProperty(this,"Firstname"), ...
        getParticipantProperty(this,"Lastname"), ....
        getParticipantProperty(this,"ParticipantTypeID") ...
        );

end

Note that GrantResult combines some of the grant properties into a single exposed property. For example, it exposes a grant's InstCity, InstState, InstPostalCode, and InstCountry properties into a single result property named Location.

In this example, the constructor uses internal methods to extract the grant properties from the grant object, which is a MAXP DOM Element object, for example,

function propValue = getGrantProperty(this,propName)
    %getGrantProperty Get the value of a grant property
    %   propValue = getGrantProperty(thisResult, propName)
    %   returns the value of the grant property specified by
    %   propName as a string.

    % Use the getElementsByTagName method of class
    % matab.io.xml.dom.Element to get the element propName from
    % this.Object, whose value is a matab.io.xml.dom.Element
    % containing the grant data.
    nl = this.Object.getElementsByTagName(propName);

    % Assume that the grant element contains only one element named
    % propName.
    elem = nl.item(0);

    % Use the getTextContent method of matab.io.xml.dom.Element to
    % get the value of the propName element, which is the value of
    % the grant's propName property. The getTextContent method
    % returns the text content of this element. Convert the text
    % content as a MATLAB string array.
    propValue = string(elem.getTextContent);

end

Define a getReporter Method

You must define a getReporter method for your result object that returns a reporter object that reports on the found object that the result object contains. This method allows a client of your finder to report on a result of a find operation simply by adding the result to a Report, Section, or Chapter object. For example,

import mlreportgen.report.*

rpt = report("myReport","pdf");
finder = MyFinder(container);
for result = find(finder)
    append(rpt,result);
end
close(rpt);

A report or chapter's append method knows that a result object must have a getReporter method that returns a reporter that formats the data the result contains. So if you add a result object to a report or chapter, the append method invokes the result's getReporter method to get the result reporter and adds the result reporter to the report or reporter, causing the result data to be formatted and included in the report.

The GrantResult class definition defines a getReporter method that returns a customized version of the Report API's mlreportgen.report.BaseTable reporter. The BaseTable reporter generates a table with a numbered title. The GrantResult class customizes the BaseTable reporter to generate a table of grant properties, for example,

The following code shows how the GrantResult class customizes the BaseReporter to generate a numbered grant properties table:

function reporter = getReporter(this)
    %getReporter Get a reporter for this grant
    %   reporter = getReporter(thisGrantResult) returns a
    %   reporter of type mlreportgen.report.BaseTable that
    %   reports on selected properties of the grant that this
    %   result contains.
    %
    %   Note: the Report API's append method invokes the
    %   getReporter method of result objects and adds the
    %   resulting reporter to a report or a chapter. This allows
    %   you to add this include a grant in a report by adding
    %   grant results to chapter or report objects.
    %
    %   If you want to customize the appearance of a grant in
    %   the report, you can call this method to get the
    %   reporter, customize the reporter, and then add the
    %   reporter to a chapter or report.

    % Import the Report API and DOM API classes. We import the
    % DOM API classes because we want to customize the title of
    % the table used to report a grant result.
    import mlreportgen.report.*
    import mlreportgen.dom.*

    % Create an instance of a BaseTable reporter.
    reporter = BaseTable;

    % Get the reporter used to create the table's title.
    titleReporter = reporter.getTitleReporter;

    % The title of a base table consists of a prefix, followed
    % by a sequence number, followed by title text. By default,
    % the prefix is "Table". Let's change the prefix to "Grant".
    titleReporter.NumberPrefix = "Grant ";

    % Set the table title text to the title of this grant.
    titleReporter.Content = this.Title;

    % Set the base table title to the title reporter. This
    % causes the table reporter to use the title reporter to
    % generate our customized table title.
    reporter.Title = titleReporter;

    % Create a cell array containing the grant data we want to
    % report. We will use this cell array to create a DOM table
    % containing the grant data.
    info = {
        "Institution",this.Institution; ...
        "Location",this.Location; ...
        "Year Awarded",this.YearAwarded; ...
        "Award Amount",this.AwardAmount; ...
        "To Support",this.ToSupport; ...
        };

    if hasParticipant(this)
        info = [info; {"Participant",this.Participant}];
    end

    % Create a DOM table containing the grant data.
    table = Table(info);

    % The first column of our table contains the names of the
    % grant properties being reported. Let's make this column
    % wide enough to accommodate the longest property name. Let's
    % also use a bold font to render the property names, thereby
    % visually distinguishing them from the property values in the
    % adjacent column.

    % Use the DOM API's column spec group and column spec objects
    % to specify the format of the property name columm. Create
    % a group object to contain the specs for the individual
    % columns.
    grps(1) = TableColSpecGroup;

    % Create a column spec object to specify the format of the
    % first, i.e., property name, column of the table.
    spec = TableColSpec;

    % Specify the width and font formats of the first column.
    spec.Style = {Width("1.3in"), Bold};

    % Create a col specs array and add the first column spec to
    % the spec array.
    specs(1) = spec;

    % Create an object to specify the width of the second, i.e.,
    % property value, column of the table.
    spec = TableColSpec;

    % Specify the width of the column.
    spec.Style = {Width("4.5in")};

    % Add the col spec to the col spec array.
    specs(2) = spec;

    % Create a col spec group array and add the col specs array to
    % the groups array.
    grps(1).ColSpecs = specs;

    % Add the groups array to the table.
    table.ColSpecGroups = grps;

    % Set the grant properties table to be the content of the
    % table reporter. This causes the reporter to include the
    % group properties table along with our customized table title
    % in a report when the reporter is added to the report.
    reporter.Content = table;
end

Use a Finder

This script shows how to use a finder to generate a report. This script uses the GrantFinder example used in the Define a Finder section to generate a PDF report on NEH grants to institutions in selected states for the year of 2010. The script performs the following tasks.

Import the Report Generator API

Import the classes included in the MATLAB Report Generator's Report API. Importing the classes allows the script to use unqualified (i.e., abbreviated) names to refer to the classes.

import mlreportgen.report.*
import mlreportgen.dom.*

Create a Report Container

Create a PDF container for the report, using the Report API's mlreportgen.report.Report class. Note that because the script imports the Report API, it can refer to the class by its unqualified name.

rpt = Report("grant","pdf");

Create the Report Title Page

Add a title page to the report, using the Report API's TitlePage class.

append(rpt,TitlePage( ...
    "Title","NEH Grants", ...
    "Subtitle","By State for 2010", ...
    "Image","neh_logo.jpg", ...
    "Author","John Doe" ...
    ));

Create the Report Table of Contents

Add a table of contents, using the Report API's TableOfContents class.

append(rpt,TableOfContents);

Find the Report Data

Use an array of structures to specify the states to be included in this report. Each structure contains the data for a specific state:

  • Name: name of the state

  • PostalCode: the state's postal (zip) code

  • Grants: grants made to institutions in the state. This field is initially empty

  • NGrants: number of grants made to institutions in this state (initially empty)

states = struct( ...
    "Name",{"California","Massachusetts","New York"}, ...
    "PostalCode",{"CA","MA","NY"}, ...
    "Grants",cell(1,3), ...
    "NGrants",cell(1,3) ...
    );

Use a grant finder to populate the Grants and NGrants fields of the state structures. Create the grant finder.

f = GrantFinder;

Loop through the state array. For each state, use the finder's Properties property to constrain the search for grants awarded to the state. Use these grant properties to constrain the search:

  • InstState: Specifies the postal code of the state in which the institution that received the grant is located.

  • YearAwarded: Specifies the year in which the grant was awarded.

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

Create the Grant Summary Chapter

Create a grant summary as the first chapter of the report. The grant summary chapter contains a title and a grant summary table. Each row of the table lists the total number of grants and total amount of money awarded to institutions in the state for the year of 2010. States appear in the table in descending order of number of grants. Each state is hyperlinked to the chapter that details the grants awarded to it.

Table excerpt showing three states listed in the first column with interactive blue hyperlinks, followed by numerical columns indicating counts and corresponding monetary totals.

Create the Summary Chapter Container

Start by creating a chapter container.

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

Create the Contents of the Grant Summary Table

Create a cell array containing the contents of the table header.

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

Preallocate a cell array to contain the table body contents. The cell array has Rx3 rows and columns where R is the number of states and 3 is the number of summary items reported for each state.

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

Sort the states array by the number of grants awarded to them, using the MATLAB sort function. The sort function returns ind, an array of indices to the states array. The first index of the ind array is the index of the state with the most grants, the second, with the second most number of grants, etc.

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

Loop through the states by number of grants, filling in the summary information for each state. Use a variable, rowIdx, as an index to the cell array row corresponding to the current state.

rowIdx = 0;

The following line rearranges the states array in order of grants received and creates a for loop that assigns each structure in the sorted states array to the variable state on each iteration of the loop.

for state = states(ind)

Update the row index to point to the cell array row corresponding to the current state.

    rowIdx = rowIdx+1;

The script enters a hyperlink to the grant details chapter for the state as the first entry in the table for the state, for example,

California

The following line uses a DOM InternalLink constructor to create the hyperlink. The InternalLink constructor takes two arguments, a link target id and the text of the hyperlink. The script uses the current state's postal code as the link target id and the state's name as the link text. Later on, when the script creates the grant details chapter, it inserts a link target in the chapter title whose id is the state's postal code. This completes creation of the hyperlink.

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

Assign the total number of grants for this state to the second item in its cell array row.

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

Compute the total amount awarded to this state.

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

Use the cur2str method to format the total amount as a dollar amount, for example,

$27,413,312.75

and assign the formatted result as the third and final item in the cell array for this state.

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

To create the summary table, pass the header and body cell arrays to the constructor of a mlreportgen.dom.FormalTable object.

table = FormalTable(header,body);

A formal table is a table that has a header and a body. The FormalTable constructor takes two arguments: a cell array that specifies the contents of the table's header and a cell array that specifies the contents of its body. The constructor converts the cell array contents to DOM TableRow and TableEntry objects that define the table, saving the script from having to create the necessary table objects itself.

Format the Grant Summary Table

At this point, the summary table looks like this:

Portion of a table with three rows, each showing a linked state name in blue, followed by a column of grant counts and a column of corresponding dollar amounts.

This is not very readable. The heading has the same format as the body and the columns are not spaced apart.

In the following steps, the script adjusts the header text formatting to look like this:

Portion of a table with a grey header row, three rows, each showing a linked state name in blue, followed by a column of grant counts and a column of corresponding dollar amounts.

First the script specifies the width and alignment of the table columns, using a DOM TableColSpecGroup object. A TableColSpecGroup object specifies the format of a group of columns. The summary table has only one group of columns so the script needs to create only one TableColSpecGroup object.

grp = TableColSpecGroup;

The TableColSpecGroup object lets the script specify the default style of the table's columns. The script specifies 1.5in as the default width of the columns and center alignment as the default column alignment.

grp.Style = {HAlign("center"),Width("1.5in")};

The script uses a TableColSpec object to override the default column alignment for the first column.

specs(1) = TableColSpec;
specs(1).Style = {HAlign("left")};
grp.ColSpecs = specs;
grp.Span = 3;
table.ColSpecGroups = grp;

Note The script could use as many as three TableColSpec objects, one for each table column, to override the group column styles. The first TableColSpec object applies to the first column, the second to the second column, etc. The script needs to assign only one column spec object to the group because it is overriding the default style only for the first column. However, if it needed to change only the third column, it would have to assign three column spec objects, leaving the Style property of the first two column spec objects empty.

The default table style crowds the table entries. So the script uses a DOM InnerMargin format object to create some space above the entries to separate them from the entries in the row above them. An InnerMargin object creates space (inner margin) between a document object and the object that contains it, for example, between the text in a table entry and the table entry's borders. The InnerMargin constructor optionally takes four arguments, the left, right, top, bottom inner margins of a document object.

The script use this constructor to create a top inner margin format of 3 points. It then assigns this format to the style of the entries in the summary table's body section.

table.Body.TableEntriesStyle = {InnerMargin("0pt","0pt","3pt","0pt")};

Finally the script format the table header to consist of bold, white text on a gray background:

table.Header.row(1).Style = ...
    {Bold,Color("white"),BackgroundColor("gray")};

Gray table header row displaying three column labels, with the first for state names, the second for numbers of grants, and the third for awarded amounts.

Add the Summary Chapter to the Report

append(ch,table);

append(rpt,ch);

Create the Grant Details Chapters

Loop through the state structures.

for state = states

For each state create a chapter to hold the state's grant details. Insert a link target into the chapter title to serve as a target for the hyperlink in the summary table in the first chapter.

    ch = Chapter("Title",{LinkTarget(state.PostalCode),state.Name});

Loop through the grant results for the state.

    for grant = state.Grants

For each grant result, add the result to the chapter.

        append(ch,grant);

A grant result has a getReporter method that returns a reporter that creates a table of selected grant properties. The chapter append method is preconfigured to get a result's reporter and add it the chapter. Thus, adding a grant to a chapter is tantamount to adding the result property table to the chapter, for example,

Table with labeled rows listing the institution, location, year awarded, award amount, and a detailed description of what the funding supports.

    end
    append(rpt,ch);
end

Close the Report Object

Closing the report object generates the PDF output file (grant.pdf) that the report object specifies.

close(rpt);

Display the report

rptview(rpt);

Appendix: The NEH Grant Database

The source of the database used in this example is the National Endowment for the Humanities (NEH). The database contains information on NEH grants for the period 2010-2019. It contains about 6000 records in XML format. It is available at NEH Grant Data. This example uses a local copy of the database XML file, NEH_Grants2010s.xml.

The database consists of a Grants element that contains a set of Grant elements each of which contains a set of grant data elements. The following is an extract from the database that illustrates its structure:

<Grant AppNumber="TD-50102-10">
    <ApplicantType>O</ApplicantType>
    <Institution>Center for Independent Documentary, Inc.</Institution>
    <OrganizationType>Four-Year College</OrganizationType>
    <InstCity>Boston</InstCity>
    <InstState>MA</InstState>
    <InstPostalCode>02135-1032</InstPostalCode>
    <InstCountry>USA</InstCountry>
    <CongressionalDistrict>7</CongressionalDistrict>
    <Latitude>42.36309</Latitude>
    <Longitude>-71.14034</Longitude>
    <CouncilDate>2009-07-01</CouncilDate>
    <YearAwarded>2010</YearAwarded>
    <ProjectTitle>Mysticism and Monotheism</ProjectTitle>
    <Program>America's Media Makers: Development Grants</Program>
    <Division>Public Programs</Division>
    <ApprovedOutright>65000.0000</ApprovedOutright>
    <ApprovedMatching>0.0000</ApprovedMatching>
    <AwardOutright>65000.0000</AwardOutright>
    <AwardMatching>0.0000</AwardMatching>
    <OriginalAmount>65000.0000</OriginalAmount>
    <SupplementAmount>0.0000</SupplementAmount>
    <BeginGrant>2010-02-01</BeginGrant>
    <EndGrant>2010-08-31</EndGrant>
    <ProjectDesc>None</ProjectDesc>
    <ToSupport>Scripting for a two-hour television broadcast and companion website on mystical practice and its contemporary role 
    in Judaism, Christianity, and Islam.</ToSupport>
    <PrimaryDiscipline>Comparative Religion</PrimaryDiscipline>
    <SupplementCount>0</SupplementCount>
    <ParticipantCount>1</ParticipantCount>
    <Participant>
        <Firstname>Christine</Firstname>
        <Lastname>Herbes-Sommers</Lastname>
        <Institution>Center for Independent Documentary, Inc.</Institution>
        <City>Boston</City>
        <State>MA</State>
        <Country>USA</Country>
        <ParticipantTypeID>Project Director</ParticipantTypeID>
        <ValidFr>2009-02-03</ValidFr>
    </Participant>
    <DisciplineCount>1</DisciplineCount>
    <Discipline>
        <Name>Comparative Religion</Name>
    </Discipline>
</Grant>

See Also

| | |

Topics