DLPS Classes

Subclassing in TextClass -- An Example

DLXS middleware is written in Object-Oriented Perl. Base classes objects have default member data and generally applicable methods. Collection specific behavior is implemented by writing subclasses of the base class. Many look and feel issues can be addressed by changing collection specific XSL changes. However, different or additional searches or entirely new functionality will require Perl code modification. This is accomplished through subclassing.

There are many useful examples of subclassing in the DLXSROOT/cgi/t/text/TextClass subdirectory. These examples can be extended to all the classes of middleware. As an example, consider how DLXSROOT/cgi/t/text/TextClass/MachynTC.pm changes the number of characters returned in a KWIC snippet and changes the title for an item with a specitic ID.

The general idea (which was followed for MachynTC) is the following:

  1. Make a subclass of TextClass.pm. By convention we name TextClass subclasses based on the collection id. For the purposes of this discussion, if your collection id is machyn, the subclass module would be called MachynTC.pm. This file should be in the DLXSROOT/cgi/t/text/TextClass subdirectory.
  2. The subclass module needs to have certain Perl code early on that makes this file a subclass of TextClass.pm. See below.
  3. In the collmgr, enter TextClass/MachynTC in the subclassmodule field. This will tell the middleware to instantiate an TextClass object from the MachynTC subclass for this collection.

The easiest way to create a TextClass subclass module is to copy an existing subclass module. The simplest version is SampleTC.pm.

   package SampleTC;
       # enable strict under development
       if ( $ENV{'DLPS_DEV'} )
           require "strict.pm";
   use TextClass ();
   use DlpsUtils qw( :DEFAULT );
Remove the following line or change to "use" other subclass needed modules
   use Foo;

   # Subclass of TextClass.pm module
   use vars qw( @ISA );   
   @ISA = qw( TextClass );
   # ----------------------------------------------------------------------
   # NAME      : new (Defaults to BaseClass::new
   # PURPOSE   : create new SampleTC object
   # CALLED BY : main
   # CALLS     : SampleTC->_initialize
   # INPUT     : see TextClass::_initialize
   # RETURNS   : 
   # NOTES     :
   # ----------------------------------------------------------------------
   # ----------------------------------------------------------------------
   # NAME      : _initialize
   # PURPOSE   : create structure for TextClass object
   # CALLED BY : new
   # CALLS     :
   # INPUT     : see new
   # RETURNS   :
   # NOTES     :
   # ----------------------------------------------------------------------
   sub _initialize
       my $self = shift;
       $self->SUPER::_initialize( @_ );
   ## ----------------------------------------------------------------------

After copying the file, you will need to change the package name to match the collection. In the example case,

package MachynTC;

You can replace use Foo; with any use ... statements you might need, that the base class (TextClass.pm) doesn't already use

The code that refers to the @ISA array is needed to tell Perl what base class this module is a subclass of. This does not need changing.

Now, any method that you create here that has the same name as a method in the base class (TextClass.pm) will be run instead of the method in the base class.

Any method you create here that does not exist in the base class can also be run by the middleware when dealing with this collection.

Of course, when writing methods, the first parameter passed in, implicitly, is the object itself. So, $self = shift; should always be the first line in any class's method.

Finally, at any time, you can call $self->SUPER::NameOfMethodInBaseClass( ). This will run the method that is defined in the base class. Often it is convenient to create a method by the same name as the base class's method, run the base class version of it (via SUPER::) and then add some more code. Or vice versa; that is, run some code and then call the base class method (via SUPER::).

The first example of method overriding in MachynTC.pm changes the number of characters returned from XPAT in a KWIC snippet. Note that it overrides the _initialize() method to change the value of some member data from its default in TextClass.pm to the value required in MachynTC. MachynTC::_initialize() calls the base class _initialize() method as SUPER::_initialize() BEFORE changing the psetoffset member data value so that the new value will be in effect.

     sub _initialize
         my $self = shift;

         $self->SUPER::_initialize( @_ );

         # --- custom --------------------------------------------
         # Increase the number of characters returned in the kwic 
         # so that the largest ADD element will be returned intact
         # (should the hit be in the middle of a kwic).
         # This is to allow correct special formatting of the ADD.
         # -------------------------------------------------------

         my $maxADDsize = 650;
         $self->{'psetoffset'} = $maxADDsize;
         $self->{'psetstring'} =
             $self->{'psetoffset'} .
                 qq{ shift.-} .
                     int(($self->{'psetoffset'}) / 2);

         # --- end custom ----------------------------------------


The next example of method overriding in MachynTC.pm changes the title in the XML returned by XPAT for one specific item. Note that the _HeaderFilter_XML here performs the title substitution before invoking the base class method to do the heavy lifting of processing the entire header XML. In this way, code duplication is avoided, making the code easier to maintain. If the some additional element in the header needs to be expressed in the otput, the code can be changed in the base class and MachynTC will benefit from the new code without additional effort.

     sub _HeaderFilter_XML
         my $self = shift;
         my ( $cgi, $dso, $headRef ) = @_;

         my $NEWTITLE = "British Library Cotton MS. Vitellius F. v.";

         if( $cgi->param('idno') eq  "5076866.0001.001"
             && $cgi->param('view') eq "image" )
             $$headRef =~ s,(]*>)[^<]*(),$1$NEWTITLE$2,gs;

         $self->SUPER::_HeaderFilter_XML( $cgi, $dso, $headRef );


Strictly speaking, it would be preferable to fix the data but this may not always be possible or easy. And this serves as a good example of the kind of changes subclassing can implement.

Remember that, for a collection to use subclassed code, you need to update the subclassmodule field for this collection's entry in the Collection Manager.