On Wed, 17 Dec 2003, Andy Wardley wrote:

> Simon Wilcox wrote:
> > I've written a Plugin which provides an easy means to work with a sitemap 
> > described by an xml file. 
> 
> Me too.  Several times, each time slightly different :-)

:-)

> Can you post the code and/or a description of the API first?

OK. Here it is. Be gentle.

> My most recent half-assed implementation (attached) doesn't both with 
> this abstraction and uses a quick XML::Simple hack in an all-in-one 
> plugin to do the job.  But it's not very good and really should be 
> better.  Perhaps that's why I never sent it to CPAN - because I always 
> thought I'd improve on it one day (or better yet, someone would beat me 
> to it - long live Simon the Sitemap Pumpkin!).  Feel free to rip out and 
> reuse any bits you want.  

OK, I already did ! Added proper TT exceptions when called from TT and 
dieing when not.

Comments welcome.

Simon.
package Template::Plugin::XML::Sitemap;
use strict;
use XML::XPath;
use base qw/ Template::Plugin /;

our $VERSION = 0.01;

=head1 NAME

Template::Plugin::XML::Sitemap - Manipulate xml based sitemaps.

=head1 SYNOPSIS

    [% USE sitemap = XML.Sitemap( '/path/to/sitemap.xml' ) %]

    [% FOREACH node = sitemap.breadcrumb( uri ) %]
    <a href="[% node.uri %]">[% node.label %]</a>[% " :: " UNLESS loop.last %]
    [% END %]

    <ul>
    [% FOREACH node = sitemap.submenu( uri ) %]
      <li><a href="[% node.uri %]">[% node.label %]</a></li>
    [% END %]
    </ul>
    
=head1 DESCRIPTION

This module provides a simple interface for template authors to generate
breadcrumb trails and sub menus.

Under the hood it uses XML::XPath to parse and navigate a tree of nodes.

This module is designed to be used as a Template Toolkit plugin but it can
also be used outside of TT. See the C<new> method below.

=head1 XML FORMAT

Here is a complete example of a sitemap file.

    <sitemap site="www.peacedirect.org">
      <node uri="/" label="Home">
        <node uri="/peacedirect/" label="Peace Direct">
          <node uri="/peacedirect/aboutus.html" label="About us" />
          <node uri="/peacedirect/howwework.html" label="How we work" >
            <node uri="/peacedirect/otherorgs.html" label="Other organisations" />
          </node>
          <node uri="/peacedirect/events/" label="Events" />
          <node uri="/peacedirect/thankyous.html" label="Thank yous" />
        </node>
        <node uri="/news/" label="News &amp; Articles">
          <node uri="/news/news.html" label="News index" />
          <node uri="/news/pressinfo.html" label="Press information" />
        </node>
      </node>
    </sitemap>

=head1 METHODS

=over 4

=item *

C<new( '/path/to/sitemap.xml' )>

=item *

C<[% USE sitemap = XML.Sitemap( 'path/to/sitemap.xml' ) %]>

Parses the xml file using XML::XPath and returns an instance of 
Template::Plugin::XML::Sitemap. Throws an exception if it can't parse the 
xml.

Template Toolkit will automatically pass it's context to the C<new> method. If
it does then it will be stored and used to throw TT exceptions on errors.
Otherwise it defaults to using C<die>.

=item *

C<breadcrumb( $uri )>

=item *

C<[% sitemap.breadcrumb( uri ) %]>

Returns a reference to an array of hashes containing links from the root node
to the supplied uri. It will throw a Template exception if the uri cannot be 
found. (Outside of TT it will C<die>).

Calling C<$sitemap-E<gt>breadcrumb( '/peacedirect/' )> on the example sitemap
above returns the following data structure

    [
      { 
        uri   => '/',
        label => 'Home',
      },
      {
        uri   => '/peacedirect/',
        label => 'Peace Direct',
      },
    ]

=item *

C<submenu( $uri )>

=item *

C<[% sitemap.submenu( uri ) %]>

Returns a reference to an array of hashes containing the nodes beneath C<uri>.

The data structure and the error methods are the same as C<breadcrumb>.

=head1 CAVEATS

This module hasn't been tested heavily in a dynamic environment so there are
no performance benchmarks. It is likely to perform badly due to the XML parsing
but it works well enough in command line mode.

Some caching of the data probably wouldn't go amiss either !

=head1 AUTHOR

    Simon Wilcox
    [EMAIL PROTECTED]
    http://www.digitalcraftsmen.net/perl

=head1 COPYRIGHT

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with this module.


=head1 SEE ALSO

perl(1).

=cut


sub new {
        my ($class, $context, $datafile) = @_;

    unless( ref( $context )) {
        $datafile = $context;
        $context = undef;
    }
    
    $class->throw( "No sitemap file" ) unless defined $datafile;
    
        my $self = bless ({ _context => $context }, ref ($class) || $class);

    $self->{ _xp } = XML::XPath->new( $datafile );
    
        return ($self);
}

sub _findme {
    my ($self, $uri) = @_;

    my $xp = $self->{ _xp };

    $uri =~ s/\/index\.html$/\//; # strip trailing filename
    my $xpath = qq{//[EMAIL PROTECTED]"$uri"]};
    
    (my $node) = ( $xp->findnodes( $xpath ));

    $self->throw( "Can't find $uri using $xpath" ) unless $node;

    return $node;
}

sub throw {
    my $self = shift;

    if ( defined $self->{ _context } ) {
        $self->{ _context }->throw( sitemap => join( ''. @_ ));
    }else{
        die "Sitemap: ".join( '', @_ );
    }
}

sub breadcrumb {
    my ($self, $uri) = @_;

    my $node = $self->_findme( $uri );
    
    my @trail = ();
    while (defined $node) {
        unshift @trail, { uri => $node->getAttribute( 'uri' ),
                       label => $node->getAttribute( 'label' ) };
        $node = $node->getParentNode;
        last unless $node->getName eq 'node';
    }

    return [EMAIL PROTECTED];
}

sub submenu {
    my ($self, $uri) = @_;

    my $thisnode = $self->_findme( $uri );
    my $xp = $self->{ _xp };

    my $subnodes = $xp->find( 'node', $thisnode );

    my @submenu = ();
    foreach my $node ($subnodes->get_nodelist) {
        push @submenu, { uri => $node->getAttribute( 'uri' ),
                         label => $node->getAttribute( 'label' ) };
    }

    return [EMAIL PROTECTED];
}

1; #this line is important and will help the module return a true value
__END__

Reply via email to