The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

xpptagtut - How to create custom tags with Apache::XPP

DESCRIPTION

Apache::XPP is a great tool for embedding Perl in your web pages, partly because it allows you to extend it's built-in tag parser with custom XPP tags of your own. If the built-in tags don't meet all of your needs, you can create new tags by subclassing Apache::XPP and Apache::XPP::PreParse.

CREATING CUSTOM TAGS

There are two types of tags in XPP, just as there are in HTML. In XPP, they're called single tags and double tags. Single tags consist of one element, and do not have an end tag. Double tags consist of both a starting and ending element, with content inbetween. Note that there is no way to create a tag with an "optional" ending element (like <P> in HTML).

XPP tags can accept any number of option="value"-style options, but they cannot accept single word options.

For this tutorial, we're going to create two custom tags, one of each type. The <SHIFT> tag is a single tag, it is used to shift an element off of an array. The <REPLACE> tag is a double tag, it displays the text appearing between it's starting and ending elements, replacing a given pattern with a specified string.

We'll call our subclasses My::XPP and My::XPP::PreParse.

Subclassing Apache::XPP

Only one method in this class needs to be overridden, which is the one that XPP uses to determine what PreParse class to use. This method is called preparseclass, and it returns the name of the PreParse class you are going to implement (we'll get to that).

Here's the complete Apache::XPP subclass:

  package My::XPP;

  use strict;
  use Apache::XPP;

  use vars qw( @ISA );
  @ISA = qw( Apache::XPP );

  sub preparseclass {
      return "My::XPP::PreParse";
  }

  1;

That's it. All the functionality of this class is still handled by Apache::XPP, you've instructed it to use a different PreParse class. Of course, creating that PreParse class is a little more complicated.

Subclassing Apache::XPP::PreParse

Subclassing Apache::XPP::PreParse to create a custom tags involves two basic steps. The first thing you have to do it create a class variable called @parsers that contains the names of all the parser methods you're creating to handle your custom tags. Each tag will have exactly one parser method associated with it. The second step is to implement those parser methods for your custom tags.

The beginning of our Apache::XPP::PreParse subclass looks like this:

  package My::XPP::PreParse;

  use strict;
  use Apache::XPP::PreParse;

  use vars qw( @ISA @parsers );
  @ISA = qw( Apache::XPP::PreParse );

  @parsers = (
      @Apache::XPP::PreParse::parsers,
      qw( parser_xppshift parser_xppreplace )
        );

Once we get the usual formalities of package declaration and inheritance out of the way, we create the @parsers array by filling it with the contents of the parent class's parsers array, then adding our own custom parsers.

In this example, we've added two names to the list. These are the names of the custom parser methods we're going to implement. Now we need to write the code for these methods.

Implementing a single tag parser

Our single tag is called <SHIFT>, it has the following form:

  <SHIFT as="$scalarname" array="@arrayname">

In the above example, the first element is shifted off of @arrayname and assigned to the lexically-scoped variable $scalarname. The array option can be omitted, in which case @_ is used.

Your parser method should accept two arguments. The first is the PreParse object itself, the second is the text you're parsing. To implement a single tag parser, you need to pass this text along to an XPP::PreParse method called stag (as in single tag), along with some other data. Your parser is called as an object method, and calls to stag are made on that object.

Here's the beginning of our parser method for <SHIFT>:

  sub parser_shift {
    my $self = shift;
    my $text = shift;
    $self->stag( $text,

Simple enough, but we need to pass some more information to stag. First, we need to specify what text our tag starts with, including the opening angle bracket. This one is fairly simple:

    '<SHIFT',

The last argument is a single arrayref, which in turn contains at least one nested arrayref. Think of each nested arrayref as a sort of case statment. Every nested arrayref will contain 3 values, except for the last one (the default case), which will contain only two.

For the non-default cases, the first value is the name of an option that must be present for that case to be used. The next value is a printf-style format string with a number of string placeholders (%s). This string should evaluate to valid XPP or HTML content. The last value is an arrayref that contains the names of the options whose values should be substituted in the format string.

For our <SHIFT> tag, we'll use two nested arrayrefs. For the first one, we must pass the string 'array' as the first value, followed by a format string that contains two placeholders, followed by the options whose values are to be substituted:

    [ 'array', '<?xpp my %s = shift(%s); ?>', [ 'as', 'array' ] ],

If there was no array option passed to the tag, the next line, the default in this case, will be processed instead. The next line doesn't include an option name, since it's the default behavior:

    [ '<?xpp my %s = shift; ?>', [ 'as' ] ],

The resulting XPP code shifts off the default array (implicitly @_, although you could specify it explictly if you wanted) and assigns it to the variable named by the as option.

For completeness, here's the complete parser method for the <SHIFT> tag:

  sub parser_shift {
    my $self = shift;
    my $text = shift;
    $self->stag( $text, '<SHIFT',
      [
        [ 'array', '<?xpp my %s = shift(%s); ?>', [ 'as', 'array' ] ],
        [ '<?xpp my %s = shift; ?>', [ 'as' ] ],
      ]
    );
  }

This method goes in your PreParse subclass, My::XPP::PreParse in this case. Now on to the double tag...

Implementing a double tag parser

Our double tag is called <REPLACE>. It has the following form:

  <REPLACE pattern="not(?= happy)" string="so">
    I am very happy!
    I am not happy!
    I am not sad!
  </REPLACE>

In the above example, the contents of the tag would be changed to read:

    I am very happy!
    I am so happy!
    I am not sad!

As you can probably already see, this will be implemented as a simple pattern match/replacement.

Double tags are actually more straightforward to implement than single tags. Once again, your parser method will get only two parameters passed to it, the PreParse object and the text to be parsed:

  sub parser_replace {
    my $self = shift;
    my $text = shift;

Since this is a double tag, we'll be using the dtag (as in double tag) method for the actual parsing. dtag accepts three arguments, the text to be parsed (passed along directly by your method), the name of the tag, and a subroutine reference to process the content between your start and end elements.

First, let's create the subroutine ref:

  my $subref = sub {
      my $option = shift;
      my $data = shift;
      my $pattern = $option->{'pattern'};
      my $string  = $option->{'string'};
      $data =~ s/$pattern/$string/g;
      return $data;
    };

Two values are passed to this subref: a hashref of the options parsed into key => value pairs, and the data contained between the start and end elements of the tag. The subref modifies this data however you like, and returns it.

Next, you pass all this info to dtag:

  $self->dtag( $text, "REPLACE", $subref );

Note that the second argument, the name of the tag, does not include a leading angle bracket like it does for single tags.

For completeness, here's the complete parser method:

  sub parser_replace {
    my $self = shift;
    my $text = shift;
    my $subref = sub {
        my $params = shift;
        my $data = shift;
        my $pattern = $params->{'pattern'};
        my $string  = $params->{'string'};
        $data =~ s/$pattern/$string/g;
                return $data;
      };
    $self->dtag( $text, "REPLACE", $subref );
  }     

Note: Double tags support 1 level of nesting. This shouldn't affect how you build your custom tags, but you should note that the inner tag will always be parsed first.

USING YOUR CUSTOM XPP TAGS

Once you've created your custom tags, how do you use them?

The Apache::XPP documentation already covers the subject of configuring Apache to use Apache::XPP. To use your custom tag, you must replace the line in your conf file that sets the XPP handler:

  PerlHandler Apache::XPP

to this:

  PerlHandler My::XPP

adjusting, of course, for whatever name you gave your Apache::XPP subclass. Restart your httpd, and from that point on things should function as normal, except you'll have new custom XPP tags at your disposal!

SEE ALSO

Apache::XPP::PreParse

AUTHORS

Zack Hobson <zack@cnation.com> based on an original document by Doug Wiemer <dougw@cnation.com>.