XML Datasources

An XML data source uses an XML file to provide the data to print. The XML structure is not flat like a table, there are not rows and columns, it is more similar to a tree, where we can have several levels of data. For this reason it is necessary to use an XPath query to identify which nodes of the XML document must be considered as records. The following XML is simple enough to easily explain how to do it:

<addressbook>
    <person>
        <name>ETHAN</name>
        <phone>+1 (415) 111-1111</phone>
    </person>
  <person>
        <name>CALEB</name>
        <phone>+1 (415) 222-2222</phone>
    </person>
    <person>
        <name>WILLIAM</name>
        <phone>+1 (415) 333-3333</phone>
    </person>
</addressbook>

In the report we want to list all the persons in the address book, showing for each person the name and the phone number. Each record can be identified with the XML node labeled "person". The selection of these specific xml tags is done with an XPath query like this:

/addressbook/person

XPath is a powerful query language to select data in an XML file. In this case the expression will select all the nodes of type "person" children of "addressbook". The result from the data source prospective will be a set of three records (since the occurrences of the tag person child of addressbook are three).

The XPath query can be defined inside the report, like we did with the SQL query in other tutorials. In this case JasperReports will execute the XPath query to select the nodes from the XML document provided using the XML data source. The advantage of keeping the XPath query inside the report is that we can use parameters to make the query dynamic.

Another option is to specify the XPath expression when the JRXmlDataSource is instanced, in this case the report query will remain blank and the XPath query provided at data source level will be used to process the XML file before pass the data to the report engine.

The following picture shows the XML data source configuration dialog.


Figure 1

When configuring an XML data source is possible to specify in which mode the data source must operate. Other information include the path of the XML file to use and a Date and Number patterns that can be used to convert a text value in a more appropriate object (like a date or a number).

Now that we defined which nodes must be considered as records using the XPath query to select all the person nodes, we have to define some report fields mapping XML values (tags or attributes) to fields. This is again done using XPath queries to select this time values and not a set of nodes like we did before. The queries are relative to the "record node" (the node person in our case). We can map for instance the tag name by using the expression "name" and call that report field Person_name. "name" is a child of person, and the XPath expression "name" simply select the value of the tag "name" (assuming that the current node has a child called "name"). Please note that we are not using the full path of the tag "name" (which would be /addressbook/person/name) since the name node we want to read is the one relative to the current person node we are processing.

Let's see everything in practice. Save the xml in a file called addressbook.xml, open the data sources dialog (by clicking the cylinder in the main tool bar) and click New. Select XML file data source a press Next. Set a name for the data source, the XML file (addressbook.xml) and select the option "Use the report XPath expression when filling the report".

Create a new blank report and open the Query Dialog by clicking the cylinder button on the designer tool bar (just on the right of the Preview button). Since we need to use XPath as query language, select XPath as Query Language (from the combo box on top of the window). This operation activates the XPath mapping tool (see figure 2).


Figure 2

Since the active connection is the XML data source that points to the addressbook.xml, iReport proposes the content of that file in the XML tree on the right. We can visually identify and set the "record" nodes: right click on a person node and select "set record node (generate xPath)". In this way iReport will generate the correct XPath query to select all the nodes of type person. To map the fields, right click on a node (i.e. name) and select "Add node as field". Add the fields name and phone.

Close the query dialog by clicking OK. In the report inspector we have now the selected fields and put them in the detail band (figure 3).


Figure 3

Execute the report. The result should be similar to the one in figure 4.


Figure 4

XML sub-dataset

Suppose our addressbook can contain more than a phone number for each person.

<addressbook>
    <lastupdate>2009-10-31</lastupdate>
    <person>
        <name>ETHAN</name>
        <phone type="mobile">+1 (415) 111-1111</phone>
        <phone type="home">+1 (415) 111-1112</phone>
        <phone type="work">+1 (415) 111-1113</phone>
        <phone type="fax">+1 (415) 111-1114</phone>
    </person>
  <person>
        <name>CALEB</name>
        <phone type="mobile">+1 (415) 222-2222</phone>
        <phone type="home">+1 (415) 222-2223</phone>
    </person>
    <person>
        <name>WILLIAM</name>
        <phone type="work">+1 (415) 333-3333</phone>
        <phone type="mobile">+1 (415) 333-3334</phone>
    </person>
</addressbook>

In order to display the list of numbers we need to create a sub-dataset starting from the person node. The main dataset will work on the person nodes, while the sub-dataset will process all the phone tags of a specific person node.

To display the data hold by the sub-dataset we can use a subreport (which would allow to have multiple levels, since a subreport can contain other subreports), or we can use a simple List element. In this tutorial we will use a simple list, but the concept can be easily applied to subreports.

A List elements allows to display a set of records coming from a data source. Remove the phone field and add to the detail a List component (dragging it from the palette). When a new List component is added to the report, iReport creates a sub-dataset to feed the List. We have to configure this sub-dataset to select all the nodes of type phone in the context of the person node. To do it, right click the sub-dataset node in the report inspector view (the node is labeled dataset1) and select Edit Query. The Query Dialog for the sub-dataset pops up.


Figure 5

Select XPath as query language. In the XML tree view right click a phone node and select "set record node". Now we are working like the record node would be phone. Please note that the generated xpath query to select the phone nodes will be not used, because we will pass a data source that includes that xpath expression. With the phone node as root, add the fields type and phone. Please note the xpath syntax used in the description of the fields to extract the tag values:

@type extracts the value of the attribute "type" of the current node
child::text() extracts the text value of the current node

Close the Query dialog by clicking the OK button and add inside the List element the fields from the dataset1 (the new fields objects are in the report inspector inside the subdataset node, drag them from here over the List element in the designer).
The layout should be similar to the one shown in figure 6.


Figure 6

Right click the List component and select Edit list datasource. This opens the Dataset Run dialog to specify how to fill the dataset1. Select "Use dataset expression" (which usually is selected by default) and set the following expression:

((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).subDataSource("/person/phone")

This line of code is very important. The syntax $P{} is used to reference a parameter object, in this case REPORT_DATA_SOURCE which is a built-in parameter that contains the data source used to fill the main dataset. The data source in this case is an instance of JRXmlDatasource which provides two methods to work with subdatasets: subDataSource() and dataSource(), both get as argument an xpath expression.
In our case, the subDataSource method is what we need to explore the XML tree under the node person. In particular, the data source will use as record node all the phone nodes children of person.
Close the dataset run dialog and run the report. The result should be similar to the one in figure 7.


Figure 7

Feedback