Category "Objects"


Templates are tools for creating text output reports. The beauty of templates is that they allow a separation of the code that generates/calculates information from the code that produces the output. The template code produces the output and is done in plain text in the type of output desired with a few directives that are used to insert the real information, check conditions, do loops, include other files, etc. This article will demonstrate more features than my original post about templates. The demo program mg_report_demo.pro (docs uses three templates to create its output: the main template, the header, and the footer.

Demo output

The files needed to use the template are the MGffTemplate class (docs) and the MGffTokenizer class (docs).

Using the template object is simply a matter of instantiating an MGffTemplate object that uses a template file, in this case report.tt; create a a structure of variables; and call the process method with the structure of variables. In MG_REPORT_DEMO this is done with:

template = obj_new('MGffTemplate', 'report.tt')
variables = { dir: myDir, $
              files: infoArray, $
              header: 'header.tt', $
              footer: 'footer.tt' $
            }
template->process, variables, 'report.html'
obj_destroy, template

The fields of the structure variables will be variables accessible in the template via directives. Directives are enclosed between squares brackets and the percent sign. For example, the myDir field can be included in the output by:

<h1>Results for [% dir %]</h1>

Also, arbitrary IDL expressions can put their results into the output as well:

Reported on [% systime() %]

There are also directives to include output from other files. The include directive simply copies the contents of the file into the output, no processing is done. So the following snippet in report.tt,

[% INCLUDE footer %]

puts the contents of footer.tt (since that is stored in the footer variable), into the output. So Output produced by MGffTemplate is copied directly and any directives in it would be ignored (just copied verbatim). To process the file as another template, passing the variables structure to it and processing its directives, use the include_template directive. Here,

<p>[% INCLUDE_TEMPLATE header %]</p>
<p>The header variable is set to header.tt which is:</p>
<h1>Results for [% dir %]</h1>
<p class="date">Reported on [% systime() %]</p>

The last directive demonstrated is the “foreach” directive which loops through an array. Any array can be used, but an array of structures is particularly useful. The files field of the variables structure is an array of structures of the form:

imageInfo = { filename: '', $
              channels: 0L, $
              dimensions: lonarr(2), $
              has_palette: 0L, $
              num_images: 0L, $
              image_index: 0L, $
              pixel_type: 0L, $
              type: '' $
            }

The following (partial snippet) code accesses the various fields of each element of the files array in turn:

<p>[% FOREACH f IN files %]</p>
<h2>[% f.filename %]</h2>
<table class="[% f.type %]">
  <tbody>
    <tr>
      <td>Number of bands</td>
      <td>[% f.channels %]</td>
    </tr>
    <tr>
      <td>Type</td>
      <td>[% strlowcase(f.type) %]</td>
    </tr>
  </tbody>
</table>
[% END %]

See report.tt for the full loop.

This example used HTML output, but that is not required. Any text can be output, i.e. XML, TeX, etc.

A class similar to MGffTemplate is used in IDLdoc to produce all its output. When I changed from a bunch of print statements scattered over various methods of a class to templates where all the output is in a template file, it was a <em>lot</em> easier to focus on the content of the output. If you need to generate text-based reports (HTML, XML, LaTeX, DocBook, etc.) from IDL, I would suggest using this class.

Template class

The files needed to use the template are the MGffTemplate class (docs) and the MGffTokenizer class (docs).

Let’s take a look at a simple example. In this example, mg_template_example.pro (docs) will use the template file image-file.tt to produce this output (source code of output). The code in mg_template_example queries an image file and gets a structure, info.

filename = filepath('people.jpg', subdir=['examples', 'data'])
result = query_image(filename, info)

Then it simply creates a template from the template file, passes the info structure and the name of output file to the process method, and frees the template object.

otemplate = obj_new('MGffTemplate', 'image-file.tt')
otemplate->process, info, 'image.html'
obj_destroy, otemplate

This is easy, the real work for doing output is now in the template file. The template file is just a text file, most of which will be copied verbatim into the output. But there are directives enclosed in [% and %] that pass commands to the template object. Output in the directives will be processed in some way (depending on the directive) and then included in the output. The simplest directive simply inserts a variable into the output. For example,

Number of bands[% channels %]

Here channels was a field of the info structure passed into the process method. Also, any IDL expression that returns a string (or can be converted to a string) can be used, like

Dimensions[% strjoin(strtrim(dimensions, 2), ', ') %]

where dimensions was a field of the info structure.

There are other directives for FOR loops, IF statements, and several ways to include other files. More on these features later; I hoping to create a more complicated example that uses some of these features soon.

A couple weeks ago, I wrote a demo program to view JPEG 2000 images as a “regular” widget program. Now I want to rewrite the same program as an “object widget,” in other words write methods of a class instead of normal functions and procedures. You need to already understand the basics of object-oriented and widget programming in IDL to follow along with this example. The following files are needed for this program: mgtilejp2__define.pro (doc), mgobjectwidget__define.pro (doc), mg_object_event_handler.pro , and mg_object_cleanup.pro.

JPEG 2000 tile viewer as an object widget

This new program will have exactly the same functionality as the old program. So what are the advantages of writing the program as an object? Of course it’s a matter of preference and some will already have a preference to use or avoid objects. Here are my thoughts about the advantages of this approach:

  1. Cleaner: state/pstate doesn’t need to be passed around.
  2. Better encapsulation when multiple programs are interacting with each other and passing messages to each other. In other words, one program doesn’t need to know internal details about the other program in order to pass a message to it.

New architecture

There are several techniques to make “object widgets.” I found the following technique simple yet still has the advantages of object-oriented programming. The steps to making a simple object widget:

  1. The member variables of the object hold what used to be in the state or pstate.
  2. The init method is the widget creation part of the program. One important item: place self in the UVALUE of the top-level base. This will make our scheme for event handling work.
  3. XMANAGER can’t call a method for an event handler or cleanup routine, so we will trick it. Instead write a simple two line event handler which pulls out the UVALUE of the top-level base (which is our object widget reference) and calls the real event handler method of the object. This will mean that our object widget will only have a single event handler that handles all events (but possibly dispatches events to other routines). The same must be done for the cleanup routine.

Once you’ve done this, it’s fairly easy to modify the structure a bit for your own purposes.

Code changes

Here’s what I had to do to make MG_TILEJP2 into an object widget:

  1. Change the names of the routines from mg_tilejp2_refresh to mgtilejp2::refresh. mgtilejp2 becomes mgtilejp2::init. Make sure to change it to a function and return 1.
  2. Add MGTILEJP2__DEFINE that simply names the class, inherits from MGObjectWidget, and creates the member variables with the same name and type as the fields of the old state variable.
  3. Convert creating pstate and putting it into the tlb’s UVALUE into putting self into the tlb’s UVALUE. Get rid of
widget_control, event.top, get_uvalue=pstate

in the event handler.

  1. Fix up passing around pstate since it’s no longer needed. Fix (*pstate).owindow to self.owindow.
  2. Added a cleanupWidgets method, fixed up cleanup method, and MG_TILEJP2_EVENT to mgtilejp2::handleEvents.

See previous post about tiling JPEG 2000 images with IDLgrImage.

UPDATED 10/25/13: Updated links of library source code.

One area that is sorely missing in IDL’s library is more flexible collections. Sure, arrays are extremely powerful in IDL and we should try to use them whenever possible. But sometimes arrays are just not the right tool for the job. There are two main cases which arrays don’t handle well:

  1. Arrays can’t have zero elements.
  2. Adding an element to an array by concatenation repeatedly is extremely inefficient.

MGArrayList solves both of these problems. This is one thing in my library that I use on nearly every project.

I have a class on RSI’s User-Contributed Library called Array_List which has most of the same functionality, but I wanted MGArrayList to have exactly the same interface as IDL_Container. So MGArrayList should do everything IDL_Container does, but for all types instead of just objects.

Source code and docs for MGArrayList, its iterator, and parent classes. I intend to implement other collection classes such as hash tables, sets, and trees over the next few weeks.

« newer posts