Chapter 9. Reporting and Printing

In versions of APEX prior to version 3.0, printing was often seen as one of the weakest areas. It has always been possible to use the print functionality of the browser to print the web page that is currently being viewed (for example, if it contains a report), and this sort of built-in browser functionality may be sufficient in many cases. However, if you require more complex printing or you wish to print only the report, rather than the entire content of the page, clearly you will need another solution.

APEX version 3.0 introduced printing functionality, a much-welcomed improvement (perhaps one of the key features that encouraged people to justify upgrading from an earlier version of APEX). However, the introduction of printing ability also introduces some new decisions that need to be made, and potentially some extra work in setting up a print server (also referred to as a report server, print engine, or report engine).

Choosing a Print Server

APEX 4.0 does not natively contain a printing engine. In other words, it is not APEX itself that generates a PDF for printing. Instead, APEX sends the data you wish to print to a separate system (the print server), which processes the data and produces the desired output, which is returned to APEX and then sent back to the user's browser. Figure 9-1 illustrates this process.

PDF files are generated by a server, and sent back to the client's web browser where they can be printed locally.

Figure 9.1. PDF files are generated by a server, and sent back to the client's web browser where they can be printed locally.

You may be thinking that it would be better if APEX itself contained everything it needs to produce the printed output. There are a few probable reasons why that is not the route the Oracle team took. First, building a print server that could run "inside" the database would be a big task (and a diversion from the core APEX development). Second, if the team did build a print server, it would likely be implemented partly in Java, which would make the solution unavailable to Oracle XE, which currently does not support Java inside the database. Third, Oracle already has an excellent, well-established report server called BI Publisher.

So, what the Oracle team has done is to allow you to point your APEX instance to a print server, and when a user chooses to print, the work is offloaded to that print server, rather than being handled internally by APEX.

However, very wisely, the Oracle team has not tied you into using Oracle BI Publisher. You can point at any print server, or rather, any print server that meets the requirements. Currently, you can use the following as a print server:

  • Oracle BI Publisher

  • Oracle Containers for Java EE (OC4J) with Apache FOP

  • Any other Extensible Stylesheet Language Formatting Objects (XSL-FO) processing engine

Following are some of the factors involved in choosing a print server:

Cost:

BI Publisher has a license cost. Apache FOP is free.

Ease of Use:

BI Publisher has some additional features that make designing reports much easier than a basic FOP solution.

Availability:

Do you already have one of the supported print servers configured and working?

For many individuals and small companies, BI Publisher is simply not an option due to the cost. Larger companies may already be using BI Publisher somewhere else in the organization, so they can use the existing BI Publisher report server.

You might look at the relative costs of BI Publisher and Apache FOP and assume that Apache FOP must be much more limited (more expensive is better, right?); however, that is very much untrue. BI Publisher, together with some of the plug-ins it provides, does make it incredible easy to visually design your reports. However, almost anything you can produce in BI Publisher can be produced using Apache FOP—it just might take more time and work. That additional effort adds up and represents a very real cost to your company. So, you'll need to find the sweet spot for your particular circumstances. Do you want to pay a lot of money for BI Publisher, but potentially save a lot of development costs when designing your reports? Or would you prefer to spend nothing (in theory) on the FOP solution, but potentially have higher development costs when designing your FOP-based reports?

Deciding where to run your print server is often a matter of policy. Since APEX runs in the database, there are no choices to make about where to run it. Many organizations, however, try to avoid running anything on the database hardware other than the Oracle binaries. For this reason, APEX architectures often include at least two computers. In most cases, a separate machine is used to host an Oracle HTTP server that acts as the HTTP listener for APEX. We often see the web-tier of the architecture being used to host the print servers along side of the HTTP server. Using the web layer of the architecture allows for enhancements like load balancing or introducing a firewall between the web and database layers. It is best to keep your print server close to the database server since there may be significant amounts of data passed between them.

Configuring APEX to Use a Print Server

If you open the Buglist report in the Buglist application and go to the Print Attributes section, as shown in Figure 9-2, you are warned that printing has not been enabled for the instance yet. Notice that you are able to change settings related to the print attributes (which are discussed in the "Configuring Some Simple Print Options" section a little later in the chapter); however, printing will not work.

The instance has not been configured for printing.

Figure 9.2. The instance has not been configured for printing.

Once the instance administrator configures the instance for printing, the warning message shown in Figure 9-2 will disappear. Printing can effectively be performed by any application running in that instance (although you have the choice of whether to allow printing from individual reports in applications).

To configure APEX to use a print server, go into the administration interface, as shown in Figure 9-3. From there, choose Instance Settings to go to the Instance Settings window, as shown in Figure 9-4.

Instance administration interface

Figure 9.3. Instance administration interface

Instance Settings window

Figure 9.4. Instance Settings window

The settings required to configure a print server are as follows:

Print Server:

Choose either Standard or Advanced. To use Advanced, you need to be using Oracle BI Publisher. Choose Standard for Apache FOP or another XSL-FO processing engine.

Print Server Protocol:

Choose either HTTP or HTTPS. Bear in mind that data is transferred between the APEX instances and the print server, so if it's important that no one should be able to see that raw data on that particular part of the network, use HTTPS so that the data is transferred in an encrypted manner (this is the same as the HTTPS protocol used on secure web sites). But also bear in mind that the resulting PDF document will display the data in clear text.

Print Server Host Address:

This is the hostname or IP address of where the print server is running (essentially, the address of the machine where the print server is installed).

Print Server Port:

This is the port number on which the print server is running. Typically, for a default installation of BI Publisher, this is 9704. For Apache FOP, it is often 8080. However, this value can be anything, so make sure you find out from the person responsible for the print server on which port it is running.

Print Server Script:

Think of this as the URL to which APEX sends the data to enable the print server to process the data and produce a report. Typically, for BI Publisher, this will be /xmlpserver/convert. For Apache FOP, it could be /cocoon/fop_post or something similar. Again, check with the person responsible for the print server to obtain the correct value to use.

That is all you need to do from the APEX side of things to enable printing from the entire APEX instance. These settings enable you to generate a printed report via the print server of your choice.

Note

When the APEX instance administrator enters the values for the print server, there is no attempt to validate these settings. The only way you can test whether the settings are correct is to actually try to print something.

Printing Reports

We now have our print server installed and running, and the APEX instance configured to use the print server (see Figure 9-4). For this discussion, we'll use BI Publisher, but the basic procedures and options apply to Apache FOP as well.

You can connect to BI Publisher using a browser, entering a URL similar to http://dbvm:9704/xmlpserver. You'll see an administration login window, as shown in Figure 9-5.

Note

If you do not have BI Publisher installed in your environment, there is a wealth of information on the Oracle Technology Network at http://www.oracle.com/technetwork/middleware/bi-publisher/overview/index.html

The BI Publisher login window

Figure 9.5. The BI Publisher login window

This is where you would log in to BI Publisher to use all its functionality as a separate product. With BI Publisher, you can create ad hoc reports, recurring reports, and scheduled reports that are generated at specific times (for example, month-end reports). You can schedule these reports to be e-mailed out to all department heads, or perhaps uploaded to a web server so that the latest sales figures are always available without having to manually create the reports. Here, we are dealing with only the APEX integration side of things.

From the APEX perspective, you don't actually need to log in to the BI Publisher web interface, as most printing tasks can be done from the APEX side. Parameters and settings are passed across to BI Publisher when the report is printed.

Enabling Printing for a Report

Let's take a simple example: we want to enable printing for the Buglist report on page 1 of the Buglist application. Figure 9-6 shows how easy it is to enable printing for that report. All we need to do is open that report, go to the Print Attributes section, and set Enable Report Printing to Yes, as shown in Figure 9-6. The only other change we made was to give a slightly more meaningful label for the link (changing it from "Print" to "Print Bugs").

Enabling printing for the Buglist report

Figure 9.6. Enabling printing for the Buglist report

The settings in the Print Attributes section work as follows:

Enable Report Printing:

Set it to Yes to enable printing from the report, or No to disable printing.

Link Label:

The on-screen label used for the link to print the report.

Response Header:

You can customize the response header sent back to the user's browser to enable items like the content disposition to be defined.

View File As:

Allows you to specify whether the output should be opened directly in the browser window (Inline) or downloaded as an attachment (Attachment), which can then be opened automatically by whichever application is associated with that type of file.

File Name:

Rather than using the generated file name based on the region name, you can specify a particular file name here.

Output Format:

Allows you to produce output in different formats (we will look at this in more detail soon, in the section about setting basic print options).

Item:

Allows you to specify the output type via an Item rather than specifying it in the Output Format setting.

Report Layout:

Allows you to specify a particular report layout for the output.

Print Server Overwrite:

Allows you to configure a different print server for this particular report, rather than using the default one defined for the entire instance.

Print URL:

Allows you set a URL to use in a link or from a button to generate the report, rather than using the default link.

If you now run the application and view the Buglist report, you see the new Print Bugs link, as shown in Figure 9-7.

Print link is enabled on the report.

Figure 9.7. Print link is enabled on the report.

If we examine the URL that the Print Bugs link goes to, we find the following:

http://dbvm:7780/pls/apex/f?p=103:1:970917396197354:
FLOW_XMLP_OUTPUT_R4238061396701896_en-us

Notice the FLOW_XMLP_OUTPUT_R4238061396701896_en-us value, which is passed as the request value. This enables APEX to handle the request correctly, passing the data from the on-screen report through to the print server (BI Publisher in this example) to create the PDF.

If you click the Print Bugs link, after a short delay, the PDF for the report should open in your browser, as shown in Figure 9-8.

Default PDF output for the Buglist report (using BI Publisher)

Figure 9.8. Default PDF output for the Buglist report (using BI Publisher)

Once you have the PDF open in your browser, you can print the report by using your browser's print function, which will print to your local printer.

As you can see, the PDF in Figure 9-8 isn't exactly ideal in terms of layout or formatting. However, considering how little work we needed to do to get this PDF produced from our application, it is still very impressive. If you already have BI Publisher installed, configured, and working in your environment, and your APEX administrator has already configured the APEX instance for printing, then all you (as a developer) need to do is to switch on the Enable Report Printing option for the report, and that's it!

Troubleshooting Print Problems

One of the common problems people have with printing is that when they click the print link, the PDF fails to open in the browser. You might see an error something like the one shown in Figure 9-9.

PDF fails to open.

Figure 9.9. PDF fails to open.

In this example, we have deliberately stopped the print server. However, notice that as far as the end user is concerned, there is little explanation of why the PDF could not be opened.

A PDF can fail to be produced for many reasons: the print server not running, network and/or firewall issues, data-related problems, and so on. One technique that can be useful to try to diagnose this problem is, instead of trying to open the PDF automatically (and getting the error), to try saving the PDF to your disk first, and then opening the resulting file in a text editor. Look through the file to see if it sheds any light on the cause of the printing problem.

For example, rerunning the previous example but this time saving the file and then opening it in an editor, we can see the output shown in Listing 9-1.

Example 9-1. Viewing the Contents of the Problem PDF File

<pre>report error:

ORA-20001: The printing engine could not be reached because either the URL
specified is incorrect or a proxy URL needs to be specified.</pre>

The reason the PDF could not be opened is now fairly obvious. The file is not a valid PDF file and instead contains an error message (from APEX) that the print engine could not be reached (since we stopped the print engine). However, some of the errors might not be quite so obvious. For example, we have occasionally had issues with unescaped characters in the data.

Also, let your print server administrator know about the problem. The administrator might be able to search through the print server log files, which can show if APEX and the print server are actually managing to communicate in the first place.

If you are using an 11gR1 or R2 database, you must run the SQL in Listing 9-2 as SYS ins order to grant the appropriate privileges to local network resources:

Example 9-2. Granting Privileges to Local Network Resources to Enable Printing in 11G Databases

DECLARE
  ACL_PATH VARCHAR2(4000);
  ACL_ID  RAW(16);
BEGIN
  -- Look for the ACL currently assigned to 'localhost' and give APEX_040000
  -- the "connect" privilege if APEX_040000 does not have the privilege yet.
  SELECT ACL INTO ACL_PATH FROM DBA_NETWORK_ACLS
   WHERE HOST = 'localhost' AND LOWER_PORT IS NULL AND UPPER_PORT IS NULL;

  -- Before checking the privilege, ensure that the ACL is valid
  -- (for example, does not contain stale references to dropped users).
  -- If it does, the following exception will be raised:
  --
  -- ORA-44416: Invalid ACL: Unresolved principal 'APEX_040000'
  -- ORA-06512: at "XDB.DBMS_XDBZ", line ...
  --

  SELECT SYS_OP_R2O(extractValue(P.RES, '/Resource/XMLRef')) INTO ACL_ID
    FROM XDB.XDB$ACL A, PATH_VIEW P
   WHERE extractValue(P.RES, '/Resource/XMLRef') = REF(A) AND
         EQUALS_PATH(P.RES, ACL_PATH) = 1;

  DBMS_XDBZ.ValidateACL(ACL_ID);
   IF DBMS_NETWORK_ACL_ADMIN.CHECK_PRIVILEGE(ACL_PATH, 'APEX_040000',
     'connect') IS NULL THEN
      DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(ACL_PATH,
     'APEX_040000', TRUE, 'connect'),
  END IF;

EXCEPTION
  -- When no ACL has been assigned to 'localhost'.
  WHEN NO_DATA_FOUND THEN
  DBMS_NETWORK_ACL_ADMIN.CREATE_ACL('local-access-users.xml',
    'ACL that lets power users to connect to everywhere',
    'APEX_040000', TRUE, 'connect'),
  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL('local-access-users.xml','localhost'),
END;
/
COMMIT;

You should also consider re-starting the BI Publisher server after making these changes in the database.

Configuring Some Simple Print Options

So far, we have managed to output a PDF version of the Buglist report using the default settings. Let's take a look at the other options available for the output format, page format, and report columns.

Choosing a Report Output Format

From the Print Attributes section of the report, you can choose to output the report in PDF, Word, Excel, HTML, or XML format, as shown in Figure 9-10.

Available output formats

Figure 9.10. Available output formats

So rather than outputting in PDF format, the user might want to output the report in a format that can be easily used in Microsoft Word, for example. However, if we change the Output Format setting to Word, then it is set to output in Word for every user. We want the application to be a bit smarter (and more user-friendly) than that, and let the user specify the output format.

To allow users to select the output format, we add a new select list to the report, which lists each of the different output formats and the value we need to pass across to the print server (BI Publisher in this example) to produce the output in that particular format. We can create a static LOV for the select list, as shown in Figure 9-11.

LOV for output formats

Figure 9.11. LOV for output formats

Notice the valid values for the return values in Figure 9-11 (PDF, RTF, XLS, HTML, and XML). Make sure you use these values; otherwise, you will not get the output in the desired format. Of course, you don't need to provide all of the output formats to the end users. You could just provide PDF and Word for example, or perhaps provide different formats to different users depending on their privileges.

We now need to change the Output Format setting to use the Derive From Item option, rather than one of the hard-coded options, and we specify the select list as the item from which to derive the output format. In this example, the select list is named P1_OUTPUT_FORMAT, as shown in Figure 9-12.

Allowing dynamic output format

Figure 9.12. Allowing dynamic output format

We have also modified the File Name attribute in Figure 9-12 to be BUGS. This is actually the same name as the region itself (which would be used as a default), but we want to make sure the output is always called BUGS, even if we rename the region.

Setting Page Attributes

You can also modify the settings for the page attributes of the output, such as the paper size, orientation (landscape or portrait), borders, and so on. For our Buglist report, it makes sense to print the output in landscape format because we expect the rows to be wide.

Another interesting feature is the ability to define a page header and page footer for the output, as shown in Figure 9-13.

Specifying a page header for the output

Figure 9.13. Specifying a page header for the output

A very nice feature of the Page Header and Page Footer options is that you can reference session state. For example, in Figure 9-13, we have used the value of the APP_USER session state to show the name of the user who produced the report. This could be very useful for auditing purposes. You could extend this simple example to reference a session state item that contains the current date and time, so that it is clear from the output when the report was produced (so that you can see whether the data is current).

Note

If the period following the APP_USER session state is omitted, the report will execute, but the output will be blank and no error message will be displayed. This detail is easy to miss and finding the solution to this issue can be maddening (based on personal experience) to say the least.

Selecting Columns

You can also define which columns should be included in the output. Why would you want to do this? Well, consider the case where you might want to display some sensitive information (such as customer bank account details) on the screen, but you don't want that information to be printed and taken off-site. Figure 9-14 shows the Report Columns section, where you can choose whether to include or exclude individual columns in the output. In this example, we have excluded the ID column from the output.

Including and excluding columns in the output

Figure 9.14. Including and excluding columns in the output

Notice that you can also change the width of each column in the output. The width is expressed as a percentage of the overall width. You can either specify the values for the widths yourself or use the Recalculate button to calculate the new values based on what you have entered.

Testing Print Settings

We have changed a number of settings in the report. Let's see the results. Figures 9-15 and 9-16 show the output in PDF and HTML format.

PDF output including header and custom column width

Figure 9.15. PDF output including header and custom column width

HTML output including header and custom column width

Figure 9.16. HTML output including header and custom column width

Notice how the HTML version isn't exactly the same as the PDF output. For example, the header isn't centered, and the width of the DESCRIPTION column doesn't seem to have honored the width percentage we specified. However, as a first attempt at producing the output in multiple formats, it's still impressive.

Creating Custom Report Layouts with BI Publisher

In the previous section, we used the default report layout for the output from our report. However, the real power of using BI Publisher with APEX is the ease with which you can create your own customized layouts for each report. Using the features of BI Publisher, you can create a number of different layouts for the report, and then specify which layout should be used.

Note that you can also use custom report layouts with Apache FOP. However, the way (and ease) in which those report layouts are created using the print servers is very different. Customizing report layouts with Apache FOP is discussed later in this chapter.

Installing the Client-Side Layout Tool

To create a new report layout, you use one of the best features of BI Publisher: the BI Publisher Desktop client-side tool for Microsoft Office. This separate client-side installation adds a plug-in to Microsoft Word. With this plug-in, you can design your report templates inside Word, just as you would design a regular Word document.

Figure 9-17 shows the plug-in installed into Word 2010. Notice the new Oracle BI Publisher menu option and the additional Data, Insert, Preview, Tools, and Help toolbar menus.

BI Publisher Desktop plug-in installed in Word

Figure 9.17. BI Publisher Desktop plug-in installed in Word

The BI Publisher Desktop can currently be downloaded from the BI Publisher section of the OTN web site at the following location:

http://www.oracle.com/technetwork/middleware/bi-publisher/downloads/index.html.

Once you have installed the BI Publisher Desktop tool (a very straightforward process of running the installation executable file, which walks you through a wizard), it should be integrated into Microsoft Word, as shown in Figure 9-17.

Creating a New Report Layout

As we mentioned earlier in the chapter, when the user chooses to print a report, APEX will send the data contained in that report to the print server, which is then responsible for generating the report. Whether you use BI Publisher or Apache FOP, the report data is sent in an XML format. Therefore, the report layout you use needs to operate on the data in an XML format.

Basically, the process of using your own report template breaks down into the following phases:

  • Making BI Publisher Desktop aware of the columns that are available to use in the data that will be sent to the report server

  • Designing the report layout in BI Publisher Desktop (really, in Word)

  • Importing the new report layout back into APEX

  • Using the new report layout for a particular report

Looking at our Buglist report (Figure 9-15), we can see that the standard rows/columns layout doesn't really make the report easy to read. It would be nice to be able to display the list of bugs in another way. Let's say that we want the details for each bug to be displayed in a more vertical format, like this

Reported By: ...

Status: ...
Priority:...
...

Creating a New Query

We begin to create our new report layout by first creating a new query. Go to Shared Components page, which has two options in the Reports section:

Report Queries:

Lets you define a query that can be used to produce a printable document. This option will also allow you to download the XML that the query would produce, which you can then use in the BI Publisher Desktop tool to manipulate the data.

Report Layouts:

Lets you upload a previously created report layout (without needing to define a particular query).

In this example, we want to create a report query based on the same query used in the Buglist report, so that we can get an XML representation of the data that is going to be sent to the print server. We need to use a query that gives us the same layout of data and includes the columns we want to use in the report layout. We also want to take into account whether the user has filtered the report in any way, since we want the printed report to reflect the same data that the on-screen report is displaying. So, we can create a new report query and use the same session state item.

Choose Report Queries to start the Create Report Query wizard, as shown in Figure 9-18 (depending on your version of APEX, the Create Report Query wizard may look slightly different). In Figure 9-18, we are basing the output format on an item (P1_OUTPUT), and we have included the P1_REPORT_SEARCH item session state. You can include as many session state items as you need here.

The next step in the wizard asks us to specify the query that is going to be used to generate the data. Listing 9-3 shows the query for the Buglist report (changed slightly to make it more readable—notice the ellipses in the subqueries).

Creating a new report query

Figure 9.18. Creating a new report query

Example 9-3. Buglist Report Query

SELECT

  bl.id,
  bl.bugid,
  bl.reported,
  bl.status,
  bl.priority,
  bl.description,
  bl.reported_by,
  bl.assigned_to,
  bl.cost,
  (select ur.email from user_repository ur
     where ur.username = bl.reported_by)
    as reported_email,
  (select initcap(ur2.forename) || ' ' || initcap(ur2.surname) from
     user_repository ur2 where ur2.username = bl.reported_by)
    as reported_full_name
FROM buglist bl
WHERE
(
 instr(upper(bl.status),upper(nvl(:P1_REPORT_SEARCH,bl.status))) > 0 or
 instr(upper(bl.priority),upper(nvl(:P1_REPORT_SEARCH,bl.priority))) > 0 or
 instr(upper(bl.description),upper(nvl(:P1_REPORT_SEARCH,bl.description))) > 0 or
 instr(upper(bl.reported_by),upper(nvl(:P1_REPORT_SEARCH,bl.reported_by))) > 0
)

Notice that we are using the value of the P1_REPORT_SEARCH page item in the query in the predicate.

The next screen in the wizard is shown in Figure 9-19. You can see that currently the report query contains a single query. You can add extra queries by clicking the Add Query button. This gives you the flexibility of creating a report that contains data from two unrelated queries.

Source query definition

Figure 9.19. Source query definition

At this point, you can choose to create the report query. However, for this example, before we do that, we need to download the XML data from the query, because we are going to use that XML output to design our custom report layout.

Downloading the XML Data or Schema

You can choose to download the XML either as plain XML data or as an XML schema. The difference between the two choices is that the XML data file, as the name implies, contains the real data that is generated from running the query, while the XML schema file creates an XML document that details the definition of the XML document itself, without the data. The XML data will typically be much larger than the XML schema; however, the XML data has the benefit of allowing us to test the report layout in BI Publisher Desktop, since we'll have some data to display, so we'll choose this option for the example.

Listing 9-4 shows the format of the XML data file, and Listing 9-5 shows the format of the XML schema file. We have not reproduced the files in full, since they are quite large, but it can be helpful to see the content of these files to get an idea of how they differ.

Example 9-4. XML Data File Content

<?xml version="1.0" encoding="UTF-8"?>
<DOCUMENT>
    <DATE>12/24/2010</DATE>
    <USER_NAME>TGF</USER_NAME>
    <APP_ID>101</APP_ID>
    <APP_NAME>Oracle APEX AppBuilder</APP_NAME>
    <TITLE>Bugs</TITLE>
    <P1_REPORT_SEARCH></P1_REPORT_SEARCH>
    <REGION ID="0">
        <ROWSET>
            <ROW>
                <ID>1</ID>
<BUGID>1</BUGID>
                <REPORTED>01/27/2006</REPORTED>
                <STATUS>Open</STATUS>
                <PRIORITY>High</PRIORITY>
... rest of content omitted

Example 9-5. XML Schema File Content

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="DOCUMENT">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="DATE"/>
        <xs:element ref="USER_NAME"/>
        <xs:element ref="APP_ID"/>
        <xs:element ref="APP_NAME"/>
        <xs:element ref="TITLE"/>
        <xs:element ref="P1_REPORT_SEARCH"/>
        <xs:element ref="REGION"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="DATE">
    <xs:simpleType>
      <xs:restriction base="xs:string"/>
    </xs:simpleType>
  </xs:element>
  <xs:element name="USER_NAME">
    <xs:simpleType>
      <xs:restriction base="xs:string"/>
    </xs:simpleType>
  </xs:element>
... rest of content omitted

Also notice in Listing 9-4 that the P1_REPORT_SEARCH session state item appears in the XML. This is why we needed to include this item when creating the report query. If we didn't, it would not be included in the data. (It could still be referenced from within the query inside APEX; it just would not be available in the report.)

After we download the XML data, we fire up Microsoft Word (which has the BI Publisher Desktop plug-in installed). We can now load the XML data into BI Publisher Desktop, as shown in Figure 9-20. After doing so, we'll get a message stating that the data has been loaded successfully.

Loading the XML data

Figure 9.20. Loading the XML data

Note

If you get an error at this stage, it is likely due to a Java version mismatch. Consult the BI Publisher Desktop installation requirements document to find out whether your system meets the requirements.

Designing the Report Layout

Now the fun begins, as we begin designing the report layout. We can use basic Word functionality and create a page header to give the report a heading of "Bug Report." We could also include the current date and time if required, but we'll keep this layout simple.

We want to include each row in the XML data (that is, each bug) in the report. The BI Publisher Desktop tool contains some nice wizards that walk you through choosing the fields and data you want to include. We recommend using the wizards as much as possible to generate the code automatically. You can view that code to see how it works. Then, if desired, you can create fields manually or make adjustments if the wizards don't do precisely what you want.

For this example, we'll use the Table Wizard, as shown in Figure 9-21, and select the Free Form format.

Creating a free-form report format

Figure 9.21. Creating a free-form report format

Next, we need to choose the grouping field, which refers to the repeating field in the data. The wizard gives us the following two choices (based on the data we loaded):

  • Document/Region

  • Document/Region/Rowset/Row

If you look back at the format of the XML data in Listing 9-4, you will see that each row is represented by the DOCUMENT/REGION/ROWSET/ROW element, so we select that as the grouping field.

Next, we need to select the fields that will be included in the report, as shown in Figure 9-22. And in the next step of the wizard, we can choose a field to sort the data by. In this case, it makes sense to sort the data by descending reported date.

Selecting the fields to display in the report

Figure 9.22. Selecting the fields to display in the report

Figure 9-23 shows the output of the wizard. You can see that all it does is insert form fields that represent the fields from the XML document. The Word document also includes a form field named F at the beginning and a form field named E at the end. These two form fields are there to allow the fields to be repeated for each record.

The Word document includes form fields

Figure 9.23. The Word document includes form fields

Testing the Report

Since we imported the XML data file into BI Publisher Desktop, we can now test the report from within the tool itself, without having to export the report layout into APEX. This is a quick way to prototype a report layout. We can even choose to preview the output in several different formats: PDF, HTML, Excel, and RTF (future versions of BI Publisher might support other formats).

Figure 9-24 shows the preview in PDF format. Admittedly, the layout isn't exactly pretty just yet, but it shows how easily we can control the positioning of the fields, as we now have a vertical style layout. Notice that the records run together with no spaces in between. As a quick exercise, go back to the Word document containing the template and add a blank line immediately before the "end ROW" field. When you preview the report again, you'll see that the records are separated by a blank line.

We could now make this report look a bit nicer by using standard Microsoft Word functionality. Figure 9-25 shows a new Word document containing a modified report layout. We created a table and positioned the XML elements for the columns (the form fields essentially) inside the table cells. We also assigned a contrasting table-shading color scheme to distinguish the definition of each individual bug.

If you previewed the report again, you would see a much more visually appealing report. However, let's continue and add a few other nice features before we finish with this report.

Previewing the report layout

Figure 9.24. Previewing the report layout

Creating a nicer layout for the report

Figure 9.25. Creating a nicer layout for the report

Showing the Search Criteria

Let's also include a line in the report that displays the search criteria that was used to generate the report. In the printed report, this will show whether the records were filtered. We can easily do this by adding an extra line at the top of the page that reads as follows:

Search Criteria: P1_REPORT_SEARCH

This would certainly meet the requirements; however, the line would be included in the report whether or not we actually provided a filter. So, if we simply ran the report and displayed all the records, we would have a line that read "Search Criteria:" at the top of the page. It would be nice to be able to suppress that line unless P1_REPORT_SEARCH actually contains a value, wouldn't it? Well, as you've guessed, we can do that!

To control whether the "Search Criteria" line is displayed, highlight it and choose the Insert Conditional Region menu option. This will allow us to specify a condition that is evaluated to determine whether to show that region, as shown in Figure 9-26.

Adding a conditional region to the report

Figure 9.26. Adding a conditional region to the report

Essentially, the condition states that the entire "Search Criteria" line should be displayed only if the P1_REPORT_SEARCH field is not empty. As shown in Figure 9-26, the result of using the conditional region is that a new form field named C (for Condition) is placed before the "Search Criteria" text, with a closing form field of EC (for End Condition) placed after the P1_REPORT_SEARCH form field.

Once you become comfortable with how the form fields work, you can begin to create them manually. We can examine the code behind the conditional region we just added by highlighting the entire line and choosing Tools Field Browser. This brings up a window that displays the form fields and the code behind each one, as in this example:

C                 <?if:P1_REPORT_SEARCH!=''?>

P1_REPORT_SEARCH  <?P1_REPORT_SEARCH?>
EC                <?end if?>

So, to create a conditional region, you could type the code that is behind the C form field, and use whatever logic you like in the condition. To display a particular XML element, for example, you could type the code <?ELEMENT?>.

Highlighting Priority Items

Another nice addition to the report would be to highlight any bugs that have a high priority. We can do this by using conditional formatting (as opposed to a conditional region). We select the PRIORITY form field and then choose Insert Conditional Format. This displays the Properties dialog box shown in Figure 9-27, which allows us to specify two conditions to allow the field to be formatted differently depending on the conditions.

Adding conditional formatting to the report

Figure 9.27. Adding conditional formatting to the report

If two conditions are not sufficient, you can use the Advanced tab to manually enter the code. Examining the code for this condition, we see the following (formatted here to make it easier to read; in the editor, it will most likely appear as one long line):

<?if:PRIORITY='High'?>

  <?attribute@incontext:color;'red'?>
<?end if?>
<?if:PRIORITY!='High'?>
  <?attribute@incontext:color;'green'?>
<?end if?>

Let's preview the report in BI Publisher Desktop. The results are as shown in Figure 9-28.

Previewing the new report

Figure 9.28. Previewing the new report

Along with the priority being displayed in a different color depending on the condition we specified (which may not be apparent in the figure), notice that the line that specifies the search criteria is not present. This is because the XML data file that we loaded does not contain a value for the P1_REPORT_SEARCH session state item, as shown here (and in Listing 9-4):

<?xml version="1.0" encoding="UTF-8"?>

<DOCUMENT>
    <DATE>09-JUL-08</DATE>
    <USER_NAME>ADMIN</USER_NAME>
    <APP_ID>103</APP_ID>
    <APP_NAME>APEX - Application Builder</APP_NAME>
    <TITLE>Bugs</TITLE>
    <P1_REPORT_SEARCH></P1_REPORT_SEARCH>
    <REGION ID="0">
        <ROWSET>
... rest of listing omitted

However, when a user runs the real report through APEX, any value that is typed into the P1_REPORT_SEARCH field will be passed through in the XML data to BI Publisher, and then the conditional region should display.

Saving the New Layout for Later Use

So now that we have created the report layout, how do we use it in APEX? With BI Publisher, all we need to do is to save the Word document that we created as an RTF file. Note that no other format will work (so be careful not to accept the default Save As type of Word document). Once you have saved the layout as an RTF file, you can upload it into APEX to be used as a report layout.

If you recall, we left APEX back at the Create Report Query wizard (Figure 9-19). Now we return to APEX and go to the next page in the wizard, where we can choose to upload the new RTF, as shown in Figure 9-29.

Uploading the RTF report layout

Figure 9.29. Uploading the RTF report layout

Notice that for the Report Layout Source option, we have selected "Create file based report layout." The default option is "Use generic report layout," which is the original tabular layout.

Once we've uploaded the RTF layout, we get the option to test the report (which you should do). We also are provided with a URL that can be used from a link or button within the application to generate the report. In this case, the URL is as follows:

f?p=&APP_ID.:0:&SESSION_ID.:PRINT_REPORT=Bugs

Notice how the value for the REQUEST parameter in the URL uses the name of the report as a parameter to the PRINT_REPORT command.

We can now go back to the original Buglist report in the Buglist application and change the Report Layout setting from Default Report Layout to our new Bug layout. You will see all your custom report layouts in the drop-down list in the Print Attributes section.

Now run the application and type something into the search field to filter the records displayed in the report. Then click the Print Bugs link. The PDF version will display the same records as the on-screen report. Figure 9-30 shows an example of the report produced when we entered "error" in the search field. You can see that because we supplied a value for the P1_REPORT_SEARCH page item, the "Search Criteria" line is included in the report.

Filtered PDF output

Figure 9.30. Filtered PDF output

This quick example has illustrated just how easily you can create very advanced and dynamic reports using BI Publisher. However, before we finish this section on using BI Publisher, let's add a couple of other nice features just to show how powerful BI Publisher is.

Adding Graphics and Charts

One common requirement for printed reports is to include some sort of company logo on the report. This is actually pretty trivial to implement with BI Publisher Desktop. We can just insert a static logo in the Word document using the regular Insert Picture option in Word. For example, we inserted a regular GIF file with a ladybug (or ladybird, as we call them in the United Kingdom) image, to represent a bug logo for the report.

We can also add another really nice feature, which is to include a chart in the report that is created dynamically using the data from the report. To do this, from the Word document, choose Insert Chart, and then choose the columns to use in the axes. As an example, we added a chart showing the number of bugs per priority, as shown in Figure 9-31.

Adding a chart to a report

Figure 9.31. Adding a chart to a report

After you've added an image and/or a chart to your report, you can resave the RTF template and upload it again into APEX. Unfortunately, there is currently no option to replace an existing report layout, so you will need to delete the old report layout and create a new one. A side effect of that re-creation step is that any reports that used the old report layout will be "orphaned" from it and will resort to using the default report layout instead. So, make sure you go back and modify each report that needs to use the new layout.

Running the application again and generating the report results in a nice chart (and custom logo) at the end of the report, after the repeating rows, as shown in Figure 9-32.

As you've seen, using BI Publisher with APEX makes it very easy to create incredibly useful output formats for your end users. You can create some visually appealing reports (much better than the simple ones we have created for this demonstration) that show off the high-fidelity reporting capabilities of APEX.

Including a chart and logo in the report

Figure 9.32. Including a chart and logo in the report

Generating Reports Through Apache FOP

The previous sections showed how easy it is to use BI Publisher as your print server. If you cannot use BI Publisher, your other option is Apache FOP or some other XSL-FO processing print server. As explained in the "Configuring APEX to Use a Report Server" section earlier in the chapter, to use Apache FOP, you must configure the APEX instance to use the FOP print server (choose Standard for the Print Server setting).

Installing Apache FOP

A couple of great resources walk you through setting up and configuring Apache Tomcat and Cocoon to enable PDF printing at no cost (other than your time, that is!).

  • The PDF Printing section of the APEX OTN site, at www.oracle.com/technology/products/database/application_express/html/configure_printing.html. This details how to install and configure the supported Apache FOP that is bundled with the APEX installation itself.

  • An entry in the blog of Carl Backstrom, one of the Oracle developers on the APEX team, at http://carlback.blogspot.com/2007/03/apex-cocoon-pdf-and-more.html. This demonstrates how to use another XSL-FO processing engine in a short video that walks you through the steps, and shows exactly which versions of the software you need to get up and running.

Here, we will briefly cover installing the supported Apache FOP engine supplied with APEX.

In the APEX installation file you downloaded (you still have a copy of that, right?), within the utilities/fop directory, there is a Java WAR file named fop.war:

[oracle@db fop]$ pwd

/home/oracle/apex/utilities/fop

[oracle@db fop]$ ls -al
total 5580
drwxr-xr-x 2 oracle oinstall    4096 Jun  6 21:34 .
drwxr-xr-x 4 oracle oinstall    4096 Jun  6 21:34 ..
-r--r--r-- 1 oracle oinstall 5691921 Jun  6  2007 fop.war

You can load this WAR file into the OC4J container, which means you can load it into a tool like Enterprise Manager or Application Server. In this example, we will load the WAR file into Enterprise Manager.

First, connect to Enterprise Manager using the URL http://dbvm:8888/em/. Obviously, replace the hostname (dbvm) and port number (8888) with those for your own environment.

Once you have logged in to the OC4J home page, click the Applications tab, choose the Deploy button, and then specify the location of the WAR file. You have the option of loading the WAR file from the local disk or from a location on the server (assuming you are not connected to the OC4J administration control from the server itself). Figure 9-33 shows the settings we used on our local OC4J server.

Loading the fop.war archive

Figure 9.33. Loading the fop.war archive

On the following page in the wizard, you need to specify an application name (such as fop). Also, ensure the Context Root setting is cleared.

After you have completed all the steps (which are covered in the PDF Printing section of the guide on the APEX OTN site), the fop.war file should be successfully deployed to your OC4J container.

Creating a New Layout Using XSL-FO

You can print reports with Apache FOP as described in the "Printing Reports" section earlier in this chapter. For our Buglist report, we need to change the report layout back to the standard default layout, as shown in Figure 9-34, because the RTF layout we created in BI Publisher will not work with Apache FOP.

Using the default report layout for Apache FOP

Figure 9.34. Using the default report layout for Apache FOP

When we print this report, we get a pretty basic layout, very similar to the first BI Publisher style layout shown earlier, as shown in Figure 9-35.

The default report layout generated with Apache FOP

Figure 9.35. The default report layout generated with Apache FOP

To understand how this report is being generated, we need to delve into the world of XSL-FO. Whereas with BI Publisher, you can design the report layout using Microsoft Word and the BI Publisher Desktop plug-in, with FOP, you need to design an XSL-FO template, which is used to generate the output. Essentially, the XSL-FO is an XSL document that is used to transform the XML data into the desired output format. It is the FOP engine that performs this transformation.

As you'll see, much more hands-on work is necessary to modify reports using XSL-FO as compared with using BI Publisher. The complexity of the XSL-FO is both a positive and a negative—you can alter any aspect of the output, but to do that, you need to learn the syntax and how XSL-FO works.

Using the Create Report Layout wizard, we'll create a new report layout for Apache FOP. Because we are using the standard print server, rather than the advanced (BI Publisher), we cannot choose the RTF format. Instead, we must choose the XSL-FO format.

As shown in Figure 9-36, the two choices for the layout type are Generic Columns, which means you refer to the columns by number, or Named Columns, which lets you reference the columns by name, similar to using the form fields in BI Publisher. Choosing Named Columns gives you more control; you'll know exactly what will be displayed where on the report. If you use Generic Columns, it's likely that, at some point, you'll end up having one column's data appear where you expected to see another column's data. (It's a similar situation to doing select * from table rather than listing the actual columns you want, as you can never guarantee the order they'll come out in unless you specifically list the order.)

Creating an Apache FOP report layout

Figure 9.36. Creating an Apache FOP report layout

For this example, we'll choose Generic Columns, simply because that lets us see a sample of the XSL-FO code, as shown in Figure 9-37.

Generic column XSL-FO code

Figure 9.37. Generic column XSL-FO code

Understanding the XSL-FO Code

As we mentioned earlier, the layout code is actually an XSL document. It is broken into the following sections:

  • The main code section contains the core code, which references each of the following sections.

  • The report column heading section contains the code to display a heading for each column.

  • The report column section contains the code to display each column.

  • The report column width section is used to specify the width of columns.

Tip

A great reference source for XSL-FO is available at www.w3schools.com/xslfo/default.asp.

In the main code section, we see code like the following:

<fo:table-header>

  <fo:table-row>
    #PRN_TEMPLATE_HEADER_ROW#
  </fo:table-row>
</fo:table-header>
<fo:table-body>
  <xsl:for-each select=".//ROW">
    <fo:table-row>
      #PRN_TEMPLATE_BODY_ROW#
    </fo:table-row>
  </xsl:for-each>
</fo:table-body>

This section is responsible for outputting the headers and detail for the records (in other words, this small section is responsible for the vast majority of what we see in the outputted PDF). Much of the rest of the main code is responsible for how the page is formatted and laid out (as opposed to displaying the actual data). Listing 9-6 shows some of that formatting and layout information.

Example 9-6. Some XSL-FO Formatting and Layout Information

<xsl:attribute-set name="header-font">

  <xsl:attribute name="height">
    #HEADER_FONT_SIZE#pt
  </xsl:attribute>
  <xsl:attribute name="font-family">
    #HEADER_FONT_FAMILY#
  </xsl:attribute>
  <xsl:attribute name="white-space-collapse">
false
  </xsl:attribute>
  <xsl:attribute name="font-size">
          #HEADER_FONT_SIZE#pt
  </xsl:attribute>
  <xsl:attribute name="font-weight">
          #HEADER_FONT_WEIGHT#
  </xsl:attribute>
</xsl:attribute-set>

Even if you're not familiar with XSL-FO (or with XSL), it should be reasonably obvious that the section of code in Listing 9-6 is responsible for the font settings for the header. Notice the references to things that look like APEX substitution strings, such as #HEADER_FONT_SIZE#. These values are actually passed across as the values you set in the Report Attributes section for your report.

Now let's take a look at the section responsible for the column headings, shown in Listing 9-7.

Example 9-7. XSL-FO Section for Column Headings

<fo:table-cell xsl:use-attribute-sets="cell header-color border">

  <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
    <fo:inline xsl:use-attribute-sets="header-font">
            #COLUMN_HEADING#
          </fo:inline>
  </fo:block>
</fo:table-cell>

The code in Listing 9-7 is remarkably short. This brevity really is one of the powers of XSL: a short code fragment can be used multiple times for many different data elements. This block of code is actually called as a result of the #PRN_TEMPLATE_HEADER_ROW# substitution variable we saw in the main block of code, or more precisely, this section of code is embedded into the main block of code as it is represented by the #PRN_TEMPLATE_HEADER_ROW# variable.

Formatting Report Headings

Let's try something relatively simple. We'll change the report headings so they're in italic and also use a red font. The way that we change the font for the heading is to add a font-style attribute to the column heading section of the XSL-FO (Listing 9-7). For example, the following column heading section uses font-style to ask for red, italic font:

<fo:table-cell xsl:use-attribute-sets="cell header-color border">

  <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
    <fo:inline xsl:use-attribute-sets="header-font"
      font-style="italic" color="red">
            #COLUMN_HEADING#
          </fo:inline>
  </fo:block>
</fo:table-cell>

We added font-style="italic" color="red" to the fo:inline section of the code. You might notice that this is very similar to what you would do in HTML to modify the text.

If you ran the application again, after assigning the new layout to the report, you would see that the headers are indeed in italics and use a red font. However, adding a font style in this way is really not the "correct" way to change font attributes, as we are not using pure XSL-FO.

Notice that in the fo:inline section, we state that for any following text, we want to use the values from the header-font declaration:

<fo:table-cell xsl:use-attribute-sets="cell header-color border">

  <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
    <fo:inline xsl:use-attribute-sets="header-font"
      font-style="italic" color="red">
            #COLUMN_HEADING#
          </fo:inline>
  </fo:block>
</fo:table-cell>

What we should really do is include our new formatting (italic and red) in the definition for the header-font section, which you saw in Listing 9-5. We can now modify that section of code, as shown in Listing 9-8.

Example 9-8. Modifying the XSLFO to Change the Font Style and Color

<xsl:attribute-set name="header-font">

   <xsl:attribute name="height">
     #HEADER_FONT_SIZE#pt
   </xsl:attribute>
   <xsl:attribute name="font-family">
     #HEADER_FONT_FAMILY#
   </xsl:attribute>
   <xsl:attribute name="white-space-collapse">
          false
   </xsl:attribute>
   <xsl:attribute name="font-size">
          #HEADER_FONT_SIZE#pt
   </xsl:attribute>
   <xsl:attribute name="font-weight">
          #HEADER_FONT_WEIGHT#
   </xsl:attribute>
   <xsl:attribute name="font-style">
           italic
   </xsl:attribute>
   <xsl:attribute name="color">
           red
   </xsl:attribute>
</xsl:attribute-set>

Now if we run the report, we should again see the header font is in italics and the color of the header is red, as shown in Figure 9-38.

PDF showing XSL-FO changes

Figure 9.38. PDF showing XSL-FO changes

Notice that the headings are not nicely lined up, due to some of the headers being wider than the columns are defined to be. We could modify the heading alignment and column widths, although we could also specify those items through the Report Columns section in the Report Attributes, as described earlier in the chapter. In fact, we could have also very easily changed the header color through the Report Column Headings section in the Report Attributes. However, we could not specify that the header should be in italics using that method. Italics need to be set manually in the XSL-FO code.

Highlighting Priority Items

As another example, let's say we want to highlight bugs that are a high priority. Looking at the code for the column template, we see the following:

<fo:table-cell xsl:use-attribute-sets="cell border">

    <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
        <fo:inline xsl:use-attribute-sets="body-font">
            <xsl:value-of select=".//#COLUMN_HEADER_NAME#"/>
        </fo:inline>
    </fo:block>
</fo:table-cell>

We have two choices here: we can explicitly check that we're processing the PRIORITY column and then check to see if the value is 'High', or we can just check to see if the value is 'High' (regardless of which column it is in). Since we are using the generic column layout rather than using named columns, it is much trickier to check specifically for the PRIORITY column. So, we'll go with the approach of displaying any column value that matches 'High' in red.

The xsl:choose syntax essentially allows us to perform an if-then-else operation in XSL.

<xsl:choose>

  <xsl:when ...>
          ...
  </xsl:when>
  <xsl:otherwise>
          ...
  </xsl:otherwise>
</xsl:choose>

We will use this syntax to check if the value of the current column is 'High' (and display it in red); otherwise, we display it normally. Replace the contents of the Column Template with the following code:

<xsl:choose>
  <xsl:when test=".//#COLUMN_HEADER_NAME#='High'">
    <fo:table-cell xsl:use-attribute-sets="cell border">
      <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
        <fo:inline xsl:use-attribute-sets="body-font-red" font-style="italic">
          <xsl:value-of select=".//#COLUMN_HEADER_NAME#"/>
        </fo:inline>
      </fo:block>
    </fo:table-cell>
  </xsl:when>
  <xsl:otherwise>
    <fo:table-cell xsl:use-attribute-sets="cell border">
      <fo:block xsl:use-attribute-sets="text #TEXT_ALIGN#">
        <fo:inline xsl:use-attribute-sets="body-font">
          <xsl:value-of select=".//#COLUMN_HEADER_NAME#"/>
        </fo:inline>
      </fo:block>
    </fo:table-cell>
  </xsl:otherwise>
</xsl:choose>

We use the xsl:choose construct to check the value of the current column in the data. The first case is when the column value does equal 'High'. Notice the parts we have highlighted. The first is where we test (using the test keyword) the current value of the column and compare it to a string. We have used the same notation for referring to the column value that was used in the original code.

.//#COLUMN_HEADER_NAME#

This allows us to reference the current XML element in the XML document that contains the data from the report.

Also notice that we changed the fo:inline statement to use the body-font-red attribute set, rather than the original body-font. This is because we need to display the value differently in both cases. So we need to add another attribute set to the main code. To do this, we simply copied the body-font attribute-set and added a color attribute to make the font appear in red.

:

<xsl:attribute-set name="body-font">

  <xsl:attribute name="height">
    12.0pt
  </xsl:attribute>
  <xsl:attribute name="font-family">
    #BODY_FONT_FAMILY#
  </xsl:attribute>
  <xsl:attribute name="white-space-collapse">
    false
  </xsl:attribute>
  <xsl:attribute name="font-size">
    #BODY_FONT_SIZE#pt
  </xsl:attribute>
  <xsl:attribute name="font-weight">
#BODY_FONT_WEIGHT#
  </xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="body-font-red">
  <xsl:attribute name="height">
    12.0pt
  </xsl:attribute>
  <xsl:attribute name="font-family">
  #BODY_FONT_FAMILY#
  </xsl:attribute>
  <xsl:attribute name="white-space-collapse">
    false
  </xsl:attribute>
  <xsl:attribute name="font-size">
    #BODY_FONT_SIZE#pt
  </xsl:attribute>
  <xsl:attribute name="font-weight">
    #BODY_FONT_WEIGHT#
  </xsl:attribute>
  <xsl:attribute name="color">
    red
  </xsl:attribute>
.....................

So to summarize, the column template will check the value of the current column. If it is equal to the string 'High', it will use the new body-font-red attribute-set; otherwise (if it is not equal to 'High'), it will use the original body-font attribute-set.

If we now run the report, we should see that any bugs with a priority of High have the PR-IORITY column marked in red (we've also used italics to make it more noticeable in the figure), as shown in Figure 9-39.

Highlighting High priority in red and italics

Figure 9.39. Highlighting High priority in red and italics

Adding Graphics to a Report

In the BI Publisher example, we included a static logo in the PDF. Let's see how we can achieve that in the XSL-FO–based layout.

To include the static logo, we need to do the following two things:

  • Copy that image somewhere on the web server's file system so that it is accessible by the FOP engine.

  • Reference the image as an external resource in the XSL-FO

To reference the image, we need to use an XSL-FO construct called fo:external-graphic, which we can use in a way similar to the image tag in HTML. For example, we can reference the image like this

<fo:external-graphic width="50px" height="50px"

    src='url("http://dbvm:7780/i/bug/bug.gif")'>
</fo:external-graphic>

In this example, the URL to the image is http://dbvm:7780/i/bug/bug.gif. We have also specified the width and height of the image so that it is scaled nicely on the resulting PDF.

Now we look through the main code and locate the section where the footer region of the PDF is processed.

<fo:static-content flow-name="region-footer">

   <fo:block xsl:use-attribute-sets="text footer">
      <fo:inline xsl:use-attribute-sets="body-font page-number">
         <fo:page-number/>
      </fo:inline>
   </fo:block>
   <fo:block xsl:use-attribute-sets="text text_2 #PAGE_FOOTER_ALIGNMENT#">
      <fo:inline xsl:use-attribute-sets="page-footer">#PAGE_FOOTER#</fo:inline>
   </fo:block>
</fo:static-content>

Notice that currently the footer displays just the page number (as indicated by the fo:page-number reference. We can change this to include the new fo:external-graphic construct.

<fo:static-content flow-name="region-footer">

  <fo:block xsl:use-attribute-sets="text footer">
    <fo:inline xsl:use-attribute-sets="body-font page-number">
      <fo:external-graphic width="50px" height="50px"
        src='url("http://dbvm:7780/i/bug/bug.gif")'/>
      <fo:page-number/>
    </fo:inline>
  </fo:block>
  <fo:block xsl:use-attribute-sets="text text_2 #PAGE_FOOTER_ALIGNMENT#">
    <fo:inline xsl:use-attribute-sets="page-footer">#PAGE_FOOTER#</fo:inline>
  </fo:block>
</fo:static-content>

If we now run the report again, we should see the logo appear in the footer area, as shown in Figure 9-40.

Including a custom image with XSL-FO

Figure 9.40. Including a custom image with XSL-FO

Our XSL-FO report is a simple example, which can easily be extended. For example, instead of using text for the priority, we could reference an image, or perhaps reference images that have been stored in the database (and made accessible via a URL, as discussed in the previous chapter).

Summary

Oracle's BI Publisher is an excellent product that makes it easy to build report formats for use in printing from APEX. BI Publisher is an enterprise product. If you work in a large organization, you may already have access to BI Publisher. Take advantage of that access if you can.

Good as it is, BI Publisher can sometimes be too costly or too complex to deploy for smaller organizations. In that case, look at a solution such as Apache FOP, which is based on XSL-FO. An XSL-FO solution is also extremely powerful and provides a good alternative to using BI Publisher, but requires more work on your side.

One final word on XSL-FO: a number of commercial products allow you to visually design XSL-FO templates, a bit like the BI Publisher Desktop product. We have tried a number of these products with mixed success; some are much easier to work with than others. Also, bear in mind that these are not APEX-specific tools and do require a learning curve of their own. And, in some cases, the XSL-FO produced by the tools did not work (that is, did not produce a PDF) when used with the fop.war shipped with APEX. So, we encourage you to test these products before purchasing (which is always wise to do with any software product!).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset