1=pod 2 3=head1 NAME 4 5Pod::Simple::XHTML -- format Pod as validating XHTML 6 7=head1 SYNOPSIS 8 9 use Pod::Simple::XHTML; 10 11 my $parser = Pod::Simple::XHTML->new(); 12 13 ... 14 15 $parser->parse_file('path/to/file.pod'); 16 17=head1 DESCRIPTION 18 19This class is a formatter that takes Pod and renders it as XHTML 20validating HTML. 21 22This is a subclass of L<Pod::Simple::Methody> and inherits all its 23methods. The implementation is entirely different than 24L<Pod::Simple::HTML>, but it largely preserves the same interface. 25 26=cut 27 28package Pod::Simple::XHTML; 29use strict; 30use vars qw( $VERSION @ISA $HAS_HTML_ENTITIES ); 31$VERSION = '3.14'; 32use Carp (); 33use Pod::Simple::Methody (); 34@ISA = ('Pod::Simple::Methody'); 35 36BEGIN { 37 $HAS_HTML_ENTITIES = eval "require HTML::Entities; 1"; 38} 39 40my %entities = ( 41 q{>} => 'gt', 42 q{<} => 'lt', 43 q{'} => '#39', 44 q{"} => 'quot', 45 q{&} => 'amp', 46); 47 48sub encode_entities { 49 return HTML::Entities::encode_entities( $_[0] ) if $HAS_HTML_ENTITIES; 50 my $str = $_[0]; 51 my $ents = join '', keys %entities; 52 $str =~ s/([$ents])/'&' . $entities{$1} . ';'/ge; 53 return $str; 54} 55 56#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57 58=head1 METHODS 59 60Pod::Simple::XHTML offers a number of methods that modify the format of 61the HTML output. Call these after creating the parser object, but before 62the call to C<parse_file>: 63 64 my $parser = Pod::PseudoPod::HTML->new(); 65 $parser->set_optional_param("value"); 66 $parser->parse_file($file); 67 68=head2 perldoc_url_prefix 69 70In turning L<Foo::Bar> into http://whatever/Foo%3a%3aBar, what 71to put before the "Foo%3a%3aBar". The default value is 72"http://search.cpan.org/perldoc?". 73 74=head2 perldoc_url_postfix 75 76What to put after "Foo%3a%3aBar" in the URL. This option is not set by 77default. 78 79=head2 man_url_prefix 80 81In turning C<< L<crontab(5)> >> into http://whatever/man/1/crontab, what 82to put before the "1/crontab". The default value is 83"http://man.he.net/man". 84 85=head2 man_url_postfix 86 87What to put after "1/crontab" in the URL. This option is not set by default. 88 89=head2 title_prefix, title_postfix 90 91What to put before and after the title in the head. The values should 92already be &-escaped. 93 94=head2 html_css 95 96 $parser->html_css('path/to/style.css'); 97 98The URL or relative path of a CSS file to include. This option is not 99set by default. 100 101=head2 html_javascript 102 103The URL or relative path of a JavaScript file to pull in. This option is 104not set by default. 105 106=head2 html_doctype 107 108A document type tag for the file. This option is not set by default. 109 110=head2 html_header_tags 111 112Additional arbitrary HTML tags for the header of the document. The 113default value is just a content type header tag: 114 115 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 116 117Add additional meta tags here, or blocks of inline CSS or JavaScript 118(wrapped in the appropriate tags). 119 120=head2 html_h_level 121 122This is the level of HTML "Hn" element to which a Pod "head1" corresponds. For 123example, if C<html_h_level> is set to 2, a head1 will produce an H2, a head2 124will produce an H3, and so on. 125 126=head2 default_title 127 128Set a default title for the page if no title can be determined from the 129content. The value of this string should already be &-escaped. 130 131=head2 force_title 132 133Force a title for the page (don't try to determine it from the content). 134The value of this string should already be &-escaped. 135 136=head2 html_header, html_footer 137 138Set the HTML output at the beginning and end of each file. The default 139header includes a title, a doctype tag (if C<html_doctype> is set), a 140content tag (customized by C<html_header_tags>), a tag for a CSS file 141(if C<html_css> is set), and a tag for a Javascript file (if 142C<html_javascript> is set). The default footer simply closes the C<html> 143and C<body> tags. 144 145The options listed above customize parts of the default header, but 146setting C<html_header> or C<html_footer> completely overrides the 147built-in header or footer. These may be useful if you want to use 148template tags instead of literal HTML headers and footers or are 149integrating converted POD pages in a larger website. 150 151If you want no headers or footers output in the HTML, set these options 152to the empty string. 153 154=head2 index 155 156Whether to add a table-of-contents at the top of each page (called an 157index for the sake of tradition). 158 159 160=cut 161 162__PACKAGE__->_accessorize( 163 'perldoc_url_prefix', 164 'perldoc_url_postfix', 165 'man_url_prefix', 166 'man_url_postfix', 167 'title_prefix', 'title_postfix', 168 'html_css', 169 'html_javascript', 170 'html_doctype', 171 'html_header_tags', 172 'html_h_level', 173 'title', # Used internally for the title extracted from the content 174 'default_title', 175 'force_title', 176 'html_header', 177 'html_footer', 178 'index', 179 'batch_mode', # whether we're in batch mode 180 'batch_mode_current_level', 181 # When in batch mode, how deep the current module is: 1 for "LWP", 182 # 2 for "LWP::Procotol", 3 for "LWP::Protocol::GHTTP", etc 183); 184 185#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 186 187=head1 SUBCLASSING 188 189If the standard options aren't enough, you may want to subclass 190Pod::Simple::XHMTL. These are the most likely candidates for methods 191you'll want to override when subclassing. 192 193=cut 194 195sub new { 196 my $self = shift; 197 my $new = $self->SUPER::new(@_); 198 $new->{'output_fh'} ||= *STDOUT{IO}; 199 $new->perldoc_url_prefix('http://search.cpan.org/perldoc?'); 200 $new->man_url_prefix('http://man.he.net/man'); 201 $new->html_header_tags('<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />'); 202 $new->nix_X_codes(1); 203 $new->codes_in_verbatim(1); 204 $new->{'scratch'} = ''; 205 $new->{'to_index'} = []; 206 $new->{'output'} = []; 207 $new->{'saved'} = []; 208 $new->{'ids'} = {}; 209 210 $new->{'__region_targets'} = []; 211 $new->{'__literal_targets'} = {}; 212 $new->accept_targets_as_html( 'html', 'HTML' ); 213 214 return $new; 215} 216 217#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 218 219=head2 handle_text 220 221This method handles the body of text within any element: it's the body 222of a paragraph, or everything between a "=begin" tag and the 223corresponding "=end" tag, or the text within an L entity, etc. You would 224want to override this if you are adding a custom element type that does 225more than just display formatted text. Perhaps adding a way to generate 226HTML tables from an extended version of POD. 227 228So, let's say you want add a custom element called 'foo'. In your 229subclass's C<new> method, after calling C<SUPER::new> you'd call: 230 231 $new->accept_targets_as_text( 'foo' ); 232 233Then override the C<start_for> method in the subclass to check for when 234"$flags->{'target'}" is equal to 'foo' and set a flag that marks that 235you're in a foo block (maybe "$self->{'in_foo'} = 1"). Then override the 236C<handle_text> method to check for the flag, and pass $text to your 237custom subroutine to construct the HTML output for 'foo' elements, 238something like: 239 240 sub handle_text { 241 my ($self, $text) = @_; 242 if ($self->{'in_foo'}) { 243 $self->{'scratch'} .= build_foo_html($text); 244 } else { 245 $self->{'scratch'} .= $text; 246 } 247 } 248 249=head2 accept_targets_as_html 250 251This method behaves like C<accept_targets_as_text>, but also marks the region 252as one whose content should be emitted literally, without HTML entity escaping 253or wrapping in a C<div> element. 254 255=cut 256 257sub __in_literal_xhtml_region { 258 return unless @{ $_[0]{__region_targets} }; 259 my $target = $_[0]{__region_targets}[-1]; 260 return $_[0]{__literal_targets}{ $target }; 261} 262 263sub accept_targets_as_html { 264 my ($self, @targets) = @_; 265 $self->accept_targets(@targets); 266 $self->{__literal_targets}{$_} = 1 for @targets; 267} 268 269sub handle_text { 270 # escape special characters in HTML (<, >, &, etc) 271 $_[0]{'scratch'} .= $_[0]->__in_literal_xhtml_region 272 ? $_[1] 273 : encode_entities( $_[1] ); 274} 275 276sub start_Para { $_[0]{'scratch'} = '<p>' } 277sub start_Verbatim { $_[0]{'scratch'} = '<pre><code>' } 278 279sub start_head1 { $_[0]{'in_head'} = 1 } 280sub start_head2 { $_[0]{'in_head'} = 2 } 281sub start_head3 { $_[0]{'in_head'} = 3 } 282sub start_head4 { $_[0]{'in_head'} = 4 } 283 284sub start_item_number { 285 $_[0]{'scratch'} = "</li>\n" if $_[0]{'in_li'}; 286 $_[0]{'scratch'} .= '<li><p>'; 287 $_[0]{'in_li'} = 1 288} 289 290sub start_item_bullet { 291 $_[0]{'scratch'} = "</li>\n" if $_[0]{'in_li'}; 292 $_[0]{'scratch'} .= '<li><p>'; 293 $_[0]{'in_li'} = 1 294} 295 296sub start_item_text { 297 if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) { 298 $_[0]{'scratch'} = "</dd>\n"; 299 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0; 300 } 301 $_[0]{'scratch'} .= '<dt>'; 302} 303 304sub start_over_bullet { $_[0]{'scratch'} = '<ul>'; $_[0]->emit } 305sub start_over_block { $_[0]{'scratch'} = '<ul>'; $_[0]->emit } 306sub start_over_number { $_[0]{'scratch'} = '<ol>'; $_[0]->emit } 307sub start_over_text { 308 $_[0]{'scratch'} = '<dl>'; 309 $_[0]{'dl_level'}++; 310 $_[0]{'in_dd'} ||= []; 311 $_[0]->emit 312} 313 314sub end_over_block { $_[0]{'scratch'} .= '</ul>'; $_[0]->emit } 315 316sub end_over_number { 317 $_[0]{'scratch'} = "</li>\n" if delete $_[0]{'in_li'}; 318 $_[0]{'scratch'} .= '</ol>'; 319 $_[0]->emit; 320} 321 322sub end_over_bullet { 323 $_[0]{'scratch'} = "</li>\n" if delete $_[0]{'in_li'}; 324 $_[0]{'scratch'} .= '</ul>'; 325 $_[0]->emit; 326} 327 328sub end_over_text { 329 if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) { 330 $_[0]{'scratch'} = "</dd>\n"; 331 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0; 332 } 333 $_[0]{'scratch'} .= '</dl>'; 334 $_[0]{'dl_level'}--; 335 $_[0]->emit; 336} 337 338# . . . . . Now the actual formatters: 339 340sub end_Para { $_[0]{'scratch'} .= '</p>'; $_[0]->emit } 341sub end_Verbatim { 342 $_[0]{'scratch'} .= '</code></pre>'; 343 $_[0]->emit; 344} 345 346sub _end_head { 347 my $h = delete $_[0]{in_head}; 348 349 my $add = $_[0]->html_h_level; 350 $add = 1 unless defined $add; 351 $h += $add - 1; 352 353 my $id = $_[0]->idify($_[0]{scratch}); 354 my $text = $_[0]{scratch}; 355 $_[0]{'scratch'} = qq{<h$h id="$id">$text</h$h>}; 356 $_[0]->emit; 357 push @{ $_[0]{'to_index'} }, [$h, $id, $text]; 358} 359 360sub end_head1 { shift->_end_head(@_); } 361sub end_head2 { shift->_end_head(@_); } 362sub end_head3 { shift->_end_head(@_); } 363sub end_head4 { shift->_end_head(@_); } 364 365sub end_item_bullet { $_[0]{'scratch'} .= '</p>'; $_[0]->emit } 366sub end_item_number { $_[0]{'scratch'} .= '</p>'; $_[0]->emit } 367 368sub end_item_text { 369 $_[0]{'scratch'} .= "</dt>\n<dd>"; 370 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 1; 371 $_[0]->emit; 372} 373 374# This handles =begin and =for blocks of all kinds. 375sub start_for { 376 my ($self, $flags) = @_; 377 378 push @{ $self->{__region_targets} }, $flags->{target_matching}; 379 380 unless ($self->__in_literal_xhtml_region) { 381 $self->{scratch} .= '<div'; 382 $self->{scratch} .= qq( class="$flags->{target}") if $flags->{target}; 383 $self->{scratch} .= '>'; 384 } 385 386 $self->emit; 387 388} 389sub end_for { 390 my ($self) = @_; 391 392 $self->{'scratch'} .= '</div>' unless $self->__in_literal_xhtml_region; 393 394 pop @{ $self->{__region_targets} }; 395 $self->emit; 396} 397 398sub start_Document { 399 my ($self) = @_; 400 if (defined $self->html_header) { 401 $self->{'scratch'} .= $self->html_header; 402 $self->emit unless $self->html_header eq ""; 403 } else { 404 my ($doctype, $title, $metatags); 405 $doctype = $self->html_doctype || ''; 406 $title = $self->force_title || $self->title || $self->default_title || ''; 407 $metatags = $self->html_header_tags || ''; 408 if ($self->html_css) { 409 $metatags .= "\n<link rel='stylesheet' href='" . 410 $self->html_css . "' type='text/css'>"; 411 } 412 if ($self->html_javascript) { 413 $metatags .= "\n<script type='text/javascript' src='" . 414 $self->html_javascript . "'></script>"; 415 } 416 $self->{'scratch'} .= <<"HTML"; 417$doctype 418<html> 419<head> 420<title>$title</title> 421$metatags 422</head> 423<body> 424HTML 425 $self->emit; 426 } 427} 428 429sub end_Document { 430 my ($self) = @_; 431 my $to_index = $self->{'to_index'}; 432 if ($self->index && @{ $to_index } ) { 433 my @out; 434 my $level = 0; 435 my $indent = -1; 436 my $space = ''; 437 my $id = ' id="index"'; 438 439 for my $h (@{ $to_index }, [0]) { 440 my $target_level = $h->[0]; 441 # Get to target_level by opening or closing ULs 442 if ($level == $target_level) { 443 $out[-1] .= '</li>'; 444 } elsif ($level > $target_level) { 445 $out[-1] .= '</li>' if $out[-1] =~ /^\s+<li>/; 446 while ($level > $target_level) { 447 --$level; 448 push @out, (' ' x --$indent) . '</li>' if @out && $out[-1] =~ m{^\s+<\/ul}; 449 push @out, (' ' x --$indent) . '</ul>'; 450 } 451 push @out, (' ' x --$indent) . '</li>' if $level; 452 } else { 453 while ($level < $target_level) { 454 ++$level; 455 push @out, (' ' x ++$indent) . '<li>' if @out && $out[-1]=~ /^\s*<ul/; 456 push @out, (' ' x ++$indent) . "<ul$id>"; 457 $id = ''; 458 } 459 ++$indent; 460 } 461 462 next unless $level; 463 $space = ' ' x $indent; 464 push @out, sprintf '%s<li><a href="#%s">%s</a>', 465 $space, $h->[1], $h->[2]; 466 } 467 # Splice the index in between the HTML headers and the first element. 468 my $offset = defined $self->html_header ? $self->html_header eq '' ? 0 : 1 : 1; 469 splice @{ $self->{'output'} }, $offset, 0, join "\n", @out; 470 } 471 472 if (defined $self->html_footer) { 473 $self->{'scratch'} .= $self->html_footer; 474 $self->emit unless $self->html_footer eq ""; 475 } else { 476 $self->{'scratch'} .= "</body>\n</html>"; 477 $self->emit; 478 } 479 480 if ($self->index) { 481 print {$self->{'output_fh'}} join ("\n\n", @{ $self->{'output'} }), "\n\n"; 482 @{$self->{'output'}} = (); 483 } 484 485} 486 487# Handling code tags 488sub start_B { $_[0]{'scratch'} .= '<b>' } 489sub end_B { $_[0]{'scratch'} .= '</b>' } 490 491sub start_C { $_[0]{'scratch'} .= '<code>' } 492sub end_C { $_[0]{'scratch'} .= '</code>' } 493 494sub start_F { $_[0]{'scratch'} .= '<i>' } 495sub end_F { $_[0]{'scratch'} .= '</i>' } 496 497sub start_I { $_[0]{'scratch'} .= '<i>' } 498sub end_I { $_[0]{'scratch'} .= '</i>' } 499 500sub start_L { 501 my ($self, $flags) = @_; 502 my ($type, $to, $section) = @{$flags}{'type', 'to', 'section'}; 503 my $url = $type eq 'url' ? $to 504 : $type eq 'pod' ? $self->resolve_pod_page_link($to, $section) 505 : $type eq 'man' ? $self->resolve_man_page_link($to, $section) 506 : undef; 507 508 # If it's an unknown type, use an attribute-less <a> like HTML.pm. 509 $self->{'scratch'} .= '<a' . ($url ? ' href="'. $url . '">' : '>'); 510} 511 512sub end_L { $_[0]{'scratch'} .= '</a>' } 513 514sub start_S { $_[0]{'scratch'} .= '<nobr>' } 515sub end_S { $_[0]{'scratch'} .= '</nobr>' } 516 517sub emit { 518 my($self) = @_; 519 if ($self->index) { 520 push @{ $self->{'output'} }, $self->{'scratch'}; 521 } else { 522 print {$self->{'output_fh'}} $self->{'scratch'}, "\n\n"; 523 } 524 $self->{'scratch'} = ''; 525 return; 526} 527 528=head2 resolve_pod_page_link 529 530 my $url = $pod->resolve_pod_page_link('Net::Ping', 'INSTALL'); 531 my $url = $pod->resolve_pod_page_link('perlpodspec'); 532 my $url = $pod->resolve_pod_page_link(undef, 'SYNOPSIS'); 533 534Resolves a POD link target (typically a module or POD file name) and section 535name to a URL. The resulting link will be returned for the above examples as: 536 537 http://search.cpan.org/perldoc?Net::Ping#INSTALL 538 http://search.cpan.org/perldoc?perlpodspec 539 #SYNOPSIS 540 541Note that when there is only a section argument the URL will simply be a link 542to a section in the current document. 543 544=cut 545 546sub resolve_pod_page_link { 547 my ($self, $to, $section) = @_; 548 return undef unless defined $to || defined $section; 549 if (defined $section) { 550 $section = '#' . $self->idify($section, 1); 551 return $section unless defined $to; 552 } else { 553 $section = '' 554 } 555 556 return ($self->perldoc_url_prefix || '') 557 . encode_entities($to) . $section 558 . ($self->perldoc_url_postfix || ''); 559} 560 561=head2 resolve_man_page_link 562 563 my $url = $pod->resolve_man_page_link('crontab(5)', 'EXAMPLE CRON FILE'); 564 my $url = $pod->resolve_man_page_link('crontab'); 565 566Resolves a man page link target and numeric section to a URL. The resulting 567link will be returned for the above examples as: 568 569 http://man.he.net/man5/crontab 570 http://man.he.net/man1/crontab 571 572Note that the first argument is required. The section number will be parsed 573from it, and if it's missing will default to 1. The second argument is 574currently ignored, as L<man.he.net|http://man.he.net> does not currently 575include linkable IDs or anchor names in its pages. Subclass to link to a 576different man page HTTP server. 577 578=cut 579 580sub resolve_man_page_link { 581 my ($self, $to, $section) = @_; 582 return undef unless defined $to; 583 my ($page, $part) = $to =~ /^([^(]+)(?:[(](\d+)[)])?$/; 584 return undef unless $page; 585 return ($self->man_url_prefix || '') 586 . ($part || 1) . "/" . encode_entities($page) 587 . ($self->man_url_postfix || ''); 588 589} 590 591=head2 idify 592 593 my $id = $pod->idify($text); 594 my $hash = $pod->idify($text, 1); 595 596This method turns an arbitrary string into a valid XHTML ID attribute value. 597The rules enforced, following 598L<http://webdesign.about.com/od/htmltags/a/aa031707.htm>, are: 599 600=over 601 602=item * 603 604The id must start with a letter (a-z or A-Z) 605 606=item * 607 608All subsequent characters can be letters, numbers (0-9), hyphens (-), 609underscores (_), colons (:), and periods (.). 610 611=item * 612 613Each id must be unique within the document. 614 615=back 616 617In addition, the returned value will be unique within the context of the 618Pod::Simple::XHTML object unless a second argument is passed a true value. ID 619attributes should always be unique within a single XHTML document, but pass 620the true value if you are creating not an ID but a URL hash to point to 621an ID (i.e., if you need to put the "#foo" in C<< <a href="#foo">foo</a> >>. 622 623=cut 624 625sub idify { 626 my ($self, $t, $not_unique) = @_; 627 for ($t) { 628 s/<[^>]+>//g; # Strip HTML. 629 s/&[^;]+;//g; # Strip entities. 630 s/^([^a-zA-Z]+)$/pod$1/; # Prepend "pod" if no valid chars. 631 s/^[^a-zA-Z]+//; # First char must be a letter. 632 s/[^-a-zA-Z0-9_:.]+/-/g; # All other chars must be valid. 633 } 634 return $t if $not_unique; 635 my $i = ''; 636 $i++ while $self->{ids}{"$t$i"}++; 637 return "$t$i"; 638} 639 640=head2 batch_mode_page_object_init 641 642 $pod->batch_mode_page_object_init($batchconvobj, $module, $infile, $outfile, $depth); 643 644Called by L<Pod::Simple::HTMLBatch> so that the class has a chance to 645initialize the converter. Internally it sets the C<batch_mode> property to 646true and sets C<batch_mode_current_level()>, but Pod::Simple::XHTML does not 647currently use those features. Subclasses might, though. 648 649=cut 650 651sub batch_mode_page_object_init { 652 my ($self, $batchconvobj, $module, $infile, $outfile, $depth) = @_; 653 $self->batch_mode(1); 654 $self->batch_mode_current_level($depth); 655 return $self; 656} 657 6581; 659 660__END__ 661 662=head1 SEE ALSO 663 664L<Pod::Simple>, L<Pod::Simple::Text>, L<Pod::Spell> 665 666=head1 SUPPORT 667 668Questions or discussion about POD and Pod::Simple should be sent to the 669pod-people@perl.org mail list. Send an empty email to 670pod-people-subscribe@perl.org to subscribe. 671 672This module is managed in an open GitHub repository, 673L<http://github.com/theory/pod-simple/>. Feel free to fork and contribute, or 674to clone L<git://github.com/theory/pod-simple.git> and send patches! 675 676Patches against Pod::Simple are welcome. Please send bug reports to 677<bug-pod-simple@rt.cpan.org>. 678 679=head1 COPYRIGHT AND DISCLAIMERS 680 681Copyright (c) 2003-2005 Allison Randal. 682 683This library is free software; you can redistribute it and/or modify it 684under the same terms as Perl itself. 685 686This program is distributed in the hope that it will be useful, but 687without any warranty; without even the implied warranty of 688merchantability or fitness for a particular purpose. 689 690=head1 ACKNOWLEDGEMENTS 691 692Thanks to L<Hurricane Electrict|http://he.net/> for permission to use its 693L<Linux man pages online|http://man.he.net/> site for man page links. 694 695Thanks to L<search.cpan.org|http://search.cpan.org/> for permission to use the 696site for Perl module links. 697 698=head1 AUTHOR 699 700Pod::Simpele::XHTML was created by Allison Randal <allison@perl.org>. 701 702Pod::Simple was created by Sean M. Burke <sburke@cpan.org>. 703But don't bother him, he's retired. 704 705Pod::Simple is maintained by: 706 707=over 708 709=item * Allison Randal C<allison@perl.org> 710 711=item * Hans Dieter Pearcey C<hdp@cpan.org> 712 713=item * David E. Wheeler C<dwheeler@cpan.org> 714 715=back 716 717=cut 718