File Coverage

blib/lib/Pod/POM/View/DocBook.pm
Criterion Covered Total %
statement 27 230 11.7
branch 0 98 0.0
condition 0 42 0.0
subroutine 9 37 24.3
pod 23 23 100.0
total 59 430 13.7


line stmt bran cond sub pod time code
1             #============================================================= -*-Perl-*-
2             #
3             # Pod::POM::View::DocBook
4             #
5             # DESCRIPTION
6             # DocBook XML view of a Pod Object Model.
7             #
8             # AUTHOR
9             # Andrew Ford
10             #
11             # Based heavily on Pod::POM::View::HTML by Andy Wardley
12             #
13             # COPYRIGHT
14             # Copyright (C) 2009 Andrew Ford and Ford & Mason Ltd. All Rights Reserved.
15             # Copyright (C) 2000 Andy Wardley. All Rights Reserved.
16             #
17             # This module is free software; you can redistribute it and/or
18             # modify it under the same terms as Perl itself.
19             #
20             # REVISION
21             # $Id: DocBook.pm 4118 2009-03-08 09:25:39Z andrew $
22             #
23             # TODO
24             # * get all the view_* methods outputting valid DocBook XML
25             # * check all list items for common item formats
26             #========================================================================
27              
28             package Pod::POM::View::DocBook;
29              
30             require 5.004;
31              
32 2     2   121432 use strict;
  2         6  
  2         93  
33              
34 2     2   10021 use Pod::POM::View;
  2         991  
  2         59  
35 2     2   1114 use Pod::POM::Constants qw( :all );
  2         1711  
  2         353  
36 2     2   12 use base qw( Pod::POM::View );
  2         4  
  2         215  
37              
38 2     2   7911 use Text::Wrap;
  2         7614  
  2         132  
39 2     2   1866 use List::MoreUtils qw(firstidx);
  2         2478  
  2         230  
40             #use Clone; # cloning doesn't seem to work at the moment
41             #use Data::Dumper; # for debugging
42              
43 2     2   16 use constant DEFAULT_ROOT_ELEMENT => 'article';
  2         3  
  2         148  
44 2     2   11 use constant DEFAULT_TOPSECT_ELEMENT => 'sect1';
  2         4  
  2         2953  
45              
46              
47             #########################################################################
48             # Don't forget to update the VERSION section in the POD!!!
49             our $VERSION = '0.08';
50             #########################################################################
51              
52             our $DEBUG = 0 unless defined $DEBUG;
53              
54             my $XML_PROTECT = 0;
55             my @OVER;
56             my %topsect = ( book => 'chapter',
57             article => 'sect1',
58             chapter => 'sect1',
59             sect1 => 'sect2' );
60             my @section = qw( part chapter sect1 sect2 sect3 sect4 sect5 );
61             my $head1off = (firstidx { $_ eq 'sect1' } @section) - 1;
62              
63             my %dont_ucfirst = map { $_ =>1 } qw {
64             a an at as and are
65             but by
66             ere
67             for from
68             in into is
69             of on onto or over
70             per
71             the to that than
72             until unto upon
73             via
74             with while whilst within without
75             de von
76             };
77              
78              
79              
80             #------------------------------------------------------------------------
81             # new(%options)
82             #
83             # Constructor for the view. Called implicitly by Pod::POM
84             # Options:
85             # * root - the root element (defaults to 'article')
86             # * topsect - top sectional element
87             # * pubid
88             # * title
89             # * author
90             # * extracttoptitle
91             # * titlecasing
92             #------------------------------------------------------------------------
93              
94             sub new {
95 0     0 1   my $class = shift;
96 0   0       my $self = $class->SUPER::new(@_)
97             || return;
98              
99             # initalise stack for maintaining info for nested lists
100 0           $self->{ OVER } = [];
101              
102             # Determine the index of the topmost level section
103              
104 0 0         if (!exists $self->{topsect}) {
105 0 0         if (exists $self->{root}) {
106 0           my $root = $self->{root};
107 0 0         if (exists $topsect{$root}) {
108 0           $self->{topsect} = $topsect{$root};
109             }
110             }
111             }
112              
113 0   0       $self->{preservecase} ||= {};
114 0 0         if (!ref $self->{preservecase}) {
    0          
115 0           $self->{preservecase} = { map { lc($_) => 1 } split(/[\,\|\s]+/, $self->{preservecase}) };
  0            
116             }
117             elsif (ref $self->{preservecase} eq 'ARRAY') {
118 0           $self->{preservecase} = { map { lc($_) => 1 } @{$self->{preservecase}} };
  0            
  0            
119             }
120              
121 0   0       $self->{forcecase} ||= {};
122 0 0         if (!ref $self->{forcecase}) {
    0          
123 0           $self->{forcecase} = { map { lc($_) => $_ } split(/[\,\|\s]+/, $self->{forcecase}) };
  0            
124             }
125             elsif (ref $self->{forcecase} eq 'ARRAY') {
126 0           $self->{forcecase} = { map { lc($_) => $_ } @{$self->{forcecase}} };
  0            
  0            
127             }
128              
129            
130 0   0       $self->{root} ||= DEFAULT_ROOT_ELEMENT;
131 0   0       $self->{topsect} ||= DEFAULT_TOPSECT_ELEMENT;
132 0     0     $self->{_head1off} = (firstidx { $_ eq $self->{topsect} } @section) - 1;
  0            
133 0           return $self;
134             }
135              
136             #------------------------------------------------------------------------
137             # view($self, $type, $item)
138             #------------------------------------------------------------------------
139              
140             sub view {
141 0     0 1   my ($self, $type, $item) = @_;
142              
143 0           DEBUG("view $type");
144              
145 0 0         if ($type =~ s/^seq_//) {
    0          
    0          
146 0           return $item;
147             }
148             elsif (UNIVERSAL::isa($item, 'HASH')) {
149 0 0         if (defined $item->{ content }) {
    0          
150 0           return $item->{ content }->present($self);
151             }
152             elsif (defined $item->{ text }) {
153 0           my $text = $item->{ text };
154 0 0         return ref $text ? $text->present($self) : $text;
155             }
156             else {
157 0           return '';
158             }
159             }
160             elsif (! ref $item) {
161 0           return $item;
162             }
163             else {
164 0           return '';
165             }
166             }
167              
168             #------------------------------------------------------------------------
169             # view_pod($self, $pod)
170             #
171             # View method for top-level node. Outputs the doctype and root element
172             # and its content.
173             #------------------------------------------------------------------------
174              
175             sub view_pod {
176 0     0 1   my ($self, $pod) = @_;
177              
178 0           DEBUG("view_pod\n");
179 0           my ($root, $author, $pubid, $sysid, $intsubset);
180 0           my $title = "";
181 0           my @content = $pod->content;
182 0           my $version_msg = sprintf("\n",
183             __PACKAGE__, $VERSION, $Pod::POM::VERSION);
184              
185 0 0         if (ref $self) {
186 0           $root = $self->{root};
187 0 0         if ($self->{suppressversion}) {
188 0           $version_msg = "";
189             }
190             }
191              
192 0 0 0       if (ref $content[0] eq 'Pod::POM::Node::Head1'
  0   0        
193             and $content[0]->title eq 'NAME'
194             and int(@{$content[0]->content}) == 1)
195             {
196 0           my ($titlecontent) = (shift @content)->content;
197              
198 0           $title = $titlecontent->text->present($self);
199              
200             }
201              
202 0   0       $root ||= DEFAULT_ROOT_ELEMENT;
203 0   0       $pubid ||= "-//OASIS//DTD DocBook XML V4.5//EN";
204 0   0       $sysid ||= "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd";
205 0   0       $intsubset ||= "";
206              
207 0           return "\n"
208             . "
209             . " \"$pubid\"\n"
210             . " \"$sysid\">\n"
211             . $version_msg
212             . "<$root>\n"
213             . "$title\n\n"
214 0           . join('', ( map { $_->present($self) } @content ))
215             . "\n\n";
216             }
217              
218              
219             #------------------------------------------------------------------------
220             # _title_case_text($self, $text, $forcecase, $preservecase, $is_subsequent)
221             #
222             # Convert the case of words in a text string to "title case". There are
223             # a couple of implementations of this (Text::Autoformat and
224             # Text::Capitalize). This is a fairly simple implementation.
225             #
226             #
227             #------------------------------------------------------------------------
228              
229              
230             sub _title_case_text {
231 0     0     my ($self, $text, $forcecase, $preservecase, $is_subsequent) = @_;
232              
233 0           my @words = grep { $_ } split(/\s+/, $text);
  0            
234 0           foreach my $word (@words) {
235 0           my ($pre, $theword, $post) = ($word =~ /^(\W)*(\w.*?)(\W*)$/);
236 0           my $lc_word = lc $theword;
237 0 0         if ($forcecase->{$lc_word}) {
    0          
238 0           $theword = $forcecase->{$lc_word};
239             }
240             elsif (!$preservecase->{$lc_word}) {
241 0           $theword = $lc_word;
242 0 0 0       $theword = ucfirst $theword unless $dont_ucfirst{$lc_word} and $is_subsequent;
243             }
244 0           $is_subsequent++;
245              
246             # any of $pre, $theword and $post may be undefined
247 2     2   16 no warnings 'uninitialized';
  2         3  
  2         5459  
248 0           $word = $pre . $theword . $post;
249             }
250 0           my $newtext = join(" ", @words);
251 0           $text =~ s/(\S.*\S)/$newtext/s;
252 0           return $text;
253             }
254              
255              
256             sub _title_case_seq {
257 0     0     my ($self, $node, $forcecase, $preservecase, $is_subsequent) = @_;
258              
259 0 0         return unless ref $node;
260              
261 0           $node = $$node;
262 0 0         if ($node->[CMD] =~ /^[BI]?$/) {
263 0           foreach ( @{$node->[CONTENT]} ) {
  0            
264 0 0         if (ref $_) {
265 0           $self->_title_case_seq($_, $forcecase, $preservecase, $is_subsequent);
266             }
267             else {
268 0           $_ = $self->_title_case_text($_, $forcecase, $preservecase, $is_subsequent);
269             }
270 0           $is_subsequent = 1;
271             }
272             }
273             }
274              
275             sub _view_headn {
276 0     0     my ($self, $head, $level) = @_;
277              
278 0           DEBUG("view_head$level\n");
279              
280 0 0         my $sect = $section[$level + (ref $self ? $self->{_head1off} : $head1off)];
281 0           my $title = $head->title;
282 0 0 0       if (ref $self and $self->{titlecasing}) {
283             # $title = clone($title);
284 0           $self->_title_case_seq($title, $self->{forcecase}, $self->{preservecase});
285             }
286              
287 0           $title = $title->present($self, "head$level");
288 0           return "<$sect>\n"
289             . "$title\n\n"
290             . $head->content->present($self)
291             . "\n\n";
292             }
293              
294             sub view_head1 {
295 0     0 1   my ($self, $head1) = @_;
296 0           return $self->_view_headn($head1, 1);
297             }
298              
299              
300             sub view_head2 {
301 0     0 1   my ($self, $head2) = @_;
302 0           return $self->_view_headn($head2, 2);
303             }
304              
305              
306             sub view_head3 {
307 0     0 1   my ($self, $head3) = @_;
308 0           return $self->_view_headn($head3, 3);
309             }
310              
311              
312             sub view_head4 {
313 0     0 1   my ($self, $head4) = @_;
314 0           return $self->_view_headn($head4, 4);
315             }
316              
317              
318             #------------------------------------------------------------------------
319             # view_over($self, $over)
320             #
321             # View method for =over. Maps to some sort of list - except if the content
322             # contains no "=item"s in which case it is a blockquote.
323             #------------------------------------------------------------------------
324              
325             sub view_over {
326 0     0 1   my ($self, $over) = @_;
327 0           my ($start, $end, $strip);
328              
329 0           DEBUG("view_over");
330              
331 0           my $items = $over->item();
332              
333 0 0 0       return '' unless @$items || @{$over->content};
  0            
334              
335 0 0         if (@$items) {
336 0           my $first_title = $items->[0]->title();
337              
338 0 0         if ($first_title =~ /^\s*\*\s*/) {
    0          
339             # '=item *' =>
340 0           $start = "\n";
341 0           $end = "\n";
342 0           $strip = qr/^\s*\*\s*/;
343             }
344             elsif ($first_title =~ /^\s*\d+\.?\s*/) {
345             # '=item 1.' or '=item 1 ' =>
346 0           $start = "\n";
347 0           $end = "\n";
348 0           $strip = qr/^\s*\d+\.?\s*/;
349             }
350             else {
351 0           $start = "\n";
352 0           $end = "\n";
353 0           $strip = '';
354             }
355             }
356             else {
357 0           $start = "
\n";
358 0           $end = "\n";
359 0           $strip = '';
360             }
361              
362 0 0         my $overstack = ref $self ? $self->{ OVER } : \@OVER;
363 0           push(@$overstack, $strip);
364 0           my $content = $over->content->present($self);
365 0           pop(@$overstack);
366              
367 0           return "\n"
368             . $start
369             . $content
370             . $end;
371             }
372              
373              
374             sub view_item {
375 0     0 1   my ($self, $item) = @_;
376              
377 0           DEBUG("view_item");
378              
379 0 0         my $over = ref $self ? $self->{ OVER } : \@OVER;
380 0           my $title = $item->title();
381 0           my $strip = $over->[-1];
382              
383 0 0         if (defined $title) {
384 0 0         $title = $title->present($self) if ref $title;
385 0 0         $title =~ s/$strip// if $strip;
386 0 0         if (length $title) {
387 0           my $anchor = $title;
388 0           $anchor =~ s/^\s*|\s*$//g; # strip leading and closing spaces
389 0           $anchor =~ s/\W/_/g;
390 0           $title = qq{$title};
391             }
392             }
393              
394 0           return ''
395             . "$title\n"
396             . $item->content->present($self)
397             . "\n";
398             }
399              
400              
401             sub view_for {
402 0     0 1   my ($self, $for) = @_;
403 0 0         return '' unless $for->format() =~ /\bdocbook\b/;
404 0           return $for->text()
405             . "\n\n";
406             }
407            
408              
409             sub view_begin {
410 0     0 1   my ($self, $begin) = @_;
411 0 0         return '' unless $begin->format() =~ /\bdocbook\b/;
412 0           $XML_PROTECT++;
413 0           my $output = $begin->content->present($self);
414 0           $XML_PROTECT--;
415 0           return $output;
416             }
417            
418              
419             sub view_textblock {
420 0     0 1   my ($self, $text) = @_;
421 0           return "$text\n";
422             }
423              
424              
425             sub view_verbatim {
426 0     0 1   my ($self, $text) = @_;
427             # for ($text) {
428             # s/&/&/g;
429             # s/
430             # s/>/>/g;
431             # }
432 0           return "\n\n\n";
433             }
434              
435              
436             sub view_seq_bold {
437 0     0 1   my ($self, $text) = @_;
438 0           return "$text";
439             }
440              
441              
442             sub view_seq_italic {
443 0     0 1   my ($self, $text) = @_;
444 0           return "$text";
445             }
446              
447              
448             sub view_seq_code {
449 0     0 1   my ($self, $text) = @_;
450 0           return "$text";
451             }
452              
453             sub view_seq_file {
454 0     0 1   my ($self, $text) = @_;
455 0           return "$text";
456             }
457              
458              
459              
460             sub view_seq_space {
461 0     0 1   my ($self, $text) = @_;
462 0           $text =~ s/\s/ /g;
463 0           return $text;
464             }
465              
466              
467             sub view_seq_entity {
468 0     0 1   my ($self, $entity) = @_;
469 0           return "&$entity;"
470             }
471              
472             #------------------------------------------------------------------------
473             # view_seq_link($self, $link)
474             #
475             # View sequence method for links
476             # L link to Perl manual page
477             # L
478             # L link to section in man page
479             # L link with display text
480             # L link to section in this doc
481             # L link to absolute URL (text is not allowed)
482             #------------------------------------------------------------------------
483              
484             sub view_seq_link {
485 0     0 1   my ($self, $link) = @_;
486              
487             # view_seq_text has already taken care of L
488 0 0         if ($link =~ /^
489 0           return $link;
490             }
491              
492             # full-blown URL's are emitted as-is
493 0 0         if ($link =~ m{^\w+://}s ) {
494 0           return _make_ulink($link);
495             }
496              
497 0           $link =~ s/\n/ /g; # undo line-wrapped tags
498              
499 0           my $orig_link = $link;
500 0           my $linktext;
501             # strip the sub-title and the following '|' char
502 0 0         if ( $link =~ s/^ ([^|]+) \| //x ) {
503 0           $linktext = $1;
504             }
505              
506             # make sure sections start with a /
507 0           $link =~ s|^"|/"|;
508              
509 0           my $page;
510             my $section;
511 0 0         if ($link =~ m|^ (.*?) / "? (.*?) "? $|x) { # [name]/"section"
    0          
512 0           ($page, $section) = ($1, $2);
513             }
514             elsif ($link =~ /\s/) { # this must be a section with missing quotes
515 0           ($page, $section) = ('', $link);
516             }
517             else {
518 0           ($page, $section) = ($link, '');
519             }
520              
521             # warning; show some text.
522 0 0         $linktext = $orig_link unless defined $linktext;
523              
524 0           my $url = '';
525 0 0 0       if (defined $page && length $page) {
526 0           $url = $self->view_seq_link_transform_path($page);
527             }
528              
529             # append the #section if exists
530 0 0 0       $url .= "#$section" if defined $url and
      0        
531             defined $section and length $section;
532              
533 0           return _make_ulink($url, $linktext);
534             }
535              
536              
537             # should be sub-classed if extra transformations are needed
538             #
539             # for example a sub-class may search for the given page and return a
540             # relative path to it.
541             #
542             # META: where this functionality should be documented? This module
543             # doesn't have docs section
544             #
545             sub view_seq_link_transform_path {
546 0     0 1   my($self, $page) = @_;
547              
548             # right now the default transform doesn't check whether the link
549             # is not dead (i.e. whether there is a corresponding file.
550             # therefore we don't link L<>'s other than L
551             # subclass to change the default (and of course add validation)
552              
553             # this is the minimal transformation that will be required if enabled
554             # $page = "$page.html";
555             # $page =~ s|::|/|g;
556             #print "page $page\n";
557 0           return;
558             }
559              
560              
561             sub _make_ulink {
562 0     0     my($url, $title) = @_;
563              
564 0 0         if (!defined $url) {
565 0 0         return defined $title ? "$title" : '';
566             }
567              
568 0 0         $title = $url unless defined $title;
569             #print "$url, $title\n";
570 0           return qq{$title};
571             }
572              
573              
574              
575              
576             # this code has been borrowed from Pod::Html
577             my $urls = '(' . join ('|',
578             qw{
579             http
580             telnet
581             mailto
582             news
583             gopher
584             file
585             wais
586             ftp
587             } ) . ')';
588             my $ltrs = '\w';
589             my $gunk = '/#~:.?+=&%@!\-';
590             my $punc = '.:!?\-;';
591             my $any = "${ltrs}${gunk}${punc}";
592              
593             sub view_seq_text {
594 0     0 1   my ($self, $text) = @_;
595              
596 0 0         unless ($XML_PROTECT) {
597 0           for ($text) {
598 0           s/&/&/g;
599 0           s/
600 0           s/>/>/g;
601             }
602             }
603              
604 0           $text =~ s{
605             \b # start at word boundary
606             ( # begin $1 {
607             $urls : # need resource and a colon
608             (?!:) # Ignore File::, among others.
609             [$any] +? # followed by one or more of any valid
610             # character, but be conservative and
611             # take only what you need to....
612             ) # end $1 }
613             (?= # look-ahead non-consumptive assertion
614             [$punc]* # either 0 or more punctuation followed
615             (?: # followed
616             [^$any] # by a non-url char
617             | # or
618             $ # end of the string
619             ) #
620             | # or else
621             $ # then end of the string
622             )
623             }{$1}igox;
624              
625 0           return $text;
626             }
627              
628              
629             #------------------------------------------------------------------------
630             # DEBUG(@msg)
631             #------------------------------------------------------------------------
632              
633             sub DEBUG {
634 0 0   0 1   print STDERR "DEBUG: ", @_ if $DEBUG;
635             }
636              
637              
638             1;
639              
640              
641             =pod
642              
643             =head1 NAME
644              
645             Pod::POM::View::DocBook - DocBook XML view of a Pod Object Model
646              
647             =head1 SYNOPSIS
648              
649             use Pod::POM;
650             use Pod::POM::View::DocBook;
651              
652             $parser = Pod::POM->new;
653             $pom = $parser->parse($file);
654              
655             $parser->default_view('Pod::POM::View::DocBook')
656             $pom->present;
657              
658             # or
659              
660             $view = Pod::Pom::View::DocBook->new(%options);
661             $parser->default_view($view)
662             $pom->present;
663              
664             # or even
665              
666             $pom->present(Pod::Pom::View::DocBook->new(%options));
667              
668              
669             =head1 DESCRIPTION
670              
671             This module provides a view for C that outputs the content as a DocBook XML document.
672             (I is an XML schema particularly suited for computing articles and books - see
673             L for details.)
674              
675             Use the module like any other C subclass.
676              
677             If C<< Pod::POM-Edefault_view >> is passed this modules class name then
678             when the C method is called on the Pod object, this constructor
679             will be called without any options. If you want to override the default
680             options then you have to create a view object and pass it to
681             C or on the C method.
682              
683             For example to convert a Pod document to a DocBook chapter document (for
684             inclusion in another document), you might use the following code:
685              
686             $pom = $parser->parse($file);
687             $view = Pod::Pom::View::DocBook( root => 'chapter' );
688             print $pom->present($view);
689              
690             Specifying the root element type determines how the C<=headI> sections map to DocBook sections.
691              
692             =head1 SUBROUTINES/METHODS
693              
694             Apart from the C methods (see L for details), this
695             module supports the two following methods:
696              
697             =over 4
698              
699             =item C
700              
701             Constructor for the view object.
702              
703             Options:
704              
705             =over 4
706              
707             =item C
708              
709             name of the root element (default: C
)
710              
711             =item C
712              
713             name of the topmost sectional element (defaults to C if C
714             is C
or C if C is C
715              
716             =item C
717              
718             if true then if the first C<=head1> is C then its content is
719             extracted as the title of the root element (default is true)
720              
721             =item C
722              
723             if true then title text is converted to initial caps format, i.e. all
724             words are initial capped except for stopwords such as "a", "the", "and",
725             "of", "on", etc. Code sequences within titles are not left alone. (default is enabled)
726              
727             =item C
728              
729             list of words for which case should be preserved in titles. The list may be an array ref or a
730             string of words separated by spaces, commas or vertical bar characters.
731              
732             =item C
733              
734             list of words for which the case in titles should be as specified. The list may be an array ref or
735             a string of words separated by spaces, commas or vertical bar characters.
736              
737             =item C
738              
739             if true then the content of HTML blocks (indicated with C<=begin html> or C<=for html>) will be
740             parsed and converted to DocBook markup. The contents of blocks marked with C are always
741             included. (NOT YET IMPLEMENTED)
742              
743             =back
744              
745             =item C
746              
747             Return the given Pod::POM node as formatted by the View.
748              
749             =back
750              
751             =head2 INTERNAL METHODS
752              
753             The following methods are specializations of the methods in L:
754              
755             =over 4
756              
757             =item C
758              
759             =item C
760              
761             =item C
762              
763             =item C
764              
765             =item C
766              
767             =item C
768              
769             =item C
770              
771             =item C
772              
773             =item C
774              
775             =item C
776              
777             =item C
778              
779             =item C
780              
781             =item C
782              
783             =item C
784              
785             =item C
786              
787             =item C
788              
789             =item C
790              
791             =item C
792              
793             =item C
794              
795             =item C
796              
797             =item DEBUG
798              
799             =back
800              
801             =head1 AUTHOR
802              
803             Andrew Ford, C<< EA.Ford@ford-mason.co.ukE >>
804              
805             =head1 VERSION
806              
807             This is version 0.08 of C.
808              
809              
810             =head1 BUGS AND LIMITATIONS
811              
812             This is still alpha-level code, many features are not fully implemented.
813              
814             Please report any bugs or feature requests to C
815             at rt.cpan.org>, or through the web interface at
816             L.
817             I will be notified, and then you'll automatically be notified of
818             progress on your bug as I make changes.
819              
820             =head1 DEPENDENCIES
821              
822             This module depends on L.
823              
824              
825             =head1 SUPPORT
826              
827             You can find documentation for this module with the perldoc command.
828              
829             perldoc Pod::POM::View::DocBook
830              
831             You can also look for information at:
832              
833             =over 4
834              
835             =item * RT: CPAN's request tracker
836              
837             L
838              
839             =item * AnnoCPAN: Annotated CPAN documentation
840              
841             L
842              
843             =item * CPAN Ratings
844              
845             L
846              
847             =item * Search CPAN
848              
849             L
850              
851             =back
852              
853             =head1 SEE ALSO
854              
855             =over 4
856              
857             =item * L
858              
859             =item * L
860              
861             =item * L
862              
863             =back
864              
865              
866             =head1 LICENSE AND COPYRIGHT
867              
868             Copyright 2009 Andrew Ford and Ford & Mason Ltd, all rights reserved.
869              
870             This program is free software; you can redistribute it and/or modify it
871             under the same terms as Perl itself.
872              
873             =cut
874