Ticket #134: pod_update.diff
File pod_update.diff, 173.0 KB (added by geraud, 13 years ago) |
---|
-
MANIFEST
2796 2796 lib/Pod/Simple/DumpAsText.pm [devel] 2797 2797 lib/Pod/Simple/DumpAsXML.pm [devel] 2798 2798 lib/Pod/Simple/HTML.pm [devel] 2799 lib/Pod/Simple/HTMLBatch.pm [devel] 2800 lib/Pod/Simple/HTMLLegacy.pm [devel] 2799 2801 lib/Pod/Simple/LinkSection.pm [devel] 2800 2802 lib/Pod/Simple/Methody.pm [devel] 2803 lib/Pod/Simple/Progress.pm [devel] 2801 2804 lib/Pod/Simple/PullParser.pm [devel] 2802 2805 lib/Pod/Simple/PullParserEndToken.pm [devel] 2803 2806 lib/Pod/Simple/PullParserStartToken.pm [devel] 2804 2807 lib/Pod/Simple/PullParserTextToken.pm [devel] 2805 2808 lib/Pod/Simple/PullParserToken.pm [devel] 2806 2809 lib/Pod/Simple/RTF.pm [devel] 2810 lib/Pod/Simple/Search.pm [devel] 2807 2811 lib/Pod/Simple/SimpleTree.pm [devel] 2808 2812 lib/Pod/Simple/Text.pm [devel] 2809 2813 lib/Pod/Simple/TextContent.pm [devel] … … 2811 2815 lib/Pod/Simple/Transcode.pm [devel] 2812 2816 lib/Pod/Simple/TranscodeDumb.pm [devel] 2813 2817 lib/Pod/Simple/TranscodeSmart.pm [devel] 2818 lib/Pod/Simple/XHTML.pm [devel] 2814 2819 lib/Pod/Simple/XMLOutStream.pm [devel] 2815 2820 parrot.spec [] 2816 2821 parrotbug [] -
lib/Pod/Simple.pm
18 18 ); 19 19 20 20 @ISA = ('Pod::Simple::BlackBox'); 21 $VERSION = ' 2.05';21 $VERSION = '3.07'; 22 22 23 23 @Known_formatting_codes = qw(I B C L E F S X Z); 24 24 %Known_formatting_codes = map(($_=>1), @Known_formatting_codes); … … 80 80 'bare_output', # For some subclasses: whether to prepend 81 81 # header-code and postpend footer-code 82 82 83 'fullstop_space_harden', # Whether to turn ". " into ".[nbsp] "; 84 83 85 'nix_X_codes', # whether to ignore X<...> codes 84 86 'merge_text', # whether to avoid breaking a single piece of 85 87 # text up into several events 86 88 89 'preserve_whitespace', # whether to try to keep whitespace as-is 90 87 91 'content_seen', # whether we've seen any real Pod content 88 92 'errors_seen', # TODO: document. whether we've seen any errors (fatal or not) 89 93 94 'codes_in_verbatim', # for PseudoPod extensions 95 90 96 'code_handler', # coderef to call when a code (non-pod) line is seen 91 97 'cut_handler', # coderef to call when a =cut line is seen 92 98 #Called like: … … 139 145 $$x = '' unless defined $$x; 140 146 DEBUG > 4 and print "# Output string set to $x ($$x)\n"; 141 147 $this->{'output_fh'} = Pod::Simple::TiedOutFH->handle_on($_[0]); 142 return $this->{'output_string'} = ${ $this->{'output_fh'} }; 148 return 149 $this->{'output_string'} = $_[0]; 150 #${ ${ $this->{'output_fh'} } }; 143 151 } 144 152 153 sub abandon_output_string { $_[0]->abandon_output_fh; delete $_[0]{'output_string'} } 154 sub abandon_output_fh { $_[0]->output_fh(undef) } 155 # These don't delete the string or close the FH -- they just delete our 156 # references to it/them. 157 # TODO: document these 158 145 159 #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 146 160 147 161 sub new { … … 969 983 # L<text|name/"sec"> or L<text|name/sec> 970 984 # L<text|/"sec"> or L<text|/sec> or L<text|"sec"> 971 985 # L<scheme:...> 986 # Ltext|scheme:...> 972 987 973 988 my($self,@stack) = @_; 974 989 … … 988 1003 989 1004 990 1005 # By here, $treelet->[$i] is definitely an L node 991 DEBUG > 1 and print "Ogling L node $treelet->[$i]\n"; 1006 my $ell = $treelet->[$i]; 1007 DEBUG > 1 and print "Ogling L node $ell\n"; 992 1008 993 1009 # bitch if it's empty 994 if( @{$ treelet->[$i]} == 2995 or (@{$ treelet->[$i]} == 3 and $treelet->[$i][2] eq '')1010 if( @{$ell} == 2 1011 or (@{$ell} == 3 and $ell->[2] eq '') 996 1012 ) { 997 1013 $self->whine( $start_line, "An empty L<>" ); 998 1014 $treelet->[$i] = 'L<>'; # just make it a text node … … 1000 1016 } 1001 1017 1002 1018 # Catch URLs: 1003 # URLs can, alas, contain E<...> sequences, so we can't /assume/ 1004 # that this is one text node. But it has to START with one text 1005 # node... 1006 if(! ref $treelet->[$i][2] and 1007 $treelet->[$i][2] =~ m/^\w+:[^:\s]\S*$/s 1019 1020 # there are a number of possible cases: 1021 # 1) text node containing url: http://foo.com 1022 # -> [ 'http://foo.com' ] 1023 # 2) text node containing url and text: foo|http://foo.com 1024 # -> [ 'foo|http://foo.com' ] 1025 # 3) text node containing url start: mailto:xE<at>foo.com 1026 # -> [ 'mailto:x', [ E ... ], 'foo.com' ] 1027 # 4) text node containing url start and text: foo|mailto:xE<at>foo.com 1028 # -> [ 'foo|mailto:x', [ E ... ], 'foo.com' ] 1029 # 5) other nodes containing text and url start: OE<39>Malley|http://foo.com 1030 # -> [ 'O', [ E ... ], 'Malley', '|http://foo.com' ] 1031 # ... etc. 1032 1033 # anything before the url is part of the text. 1034 # anything after it is part of the url. 1035 # the url text node itself may contain parts of both. 1036 1037 if (my ($url_index, $text_part, $url_part) = 1038 # grep is no good here; we want to bail out immediately so that we can 1039 # use $1, $2, etc. without having to do the match twice. 1040 sub { 1041 for (2..$#$ell) { 1042 next if ref $ell->[$_]; 1043 next unless $ell->[$_] =~ m/^(?:([^|]*)\|)?(\w+:[^:\s]\S*)$/s; 1044 return ($_, $1, $2); 1045 } 1046 return; 1047 }->() 1008 1048 ) { 1009 $treelet->[$i][1]{'type'} = 'url'; 1010 $treelet->[$i][1]{'content-implicit'} = 'yes'; 1011 1012 if( 3 == @{ $treelet->[$i] } ) { 1013 # But if it IS just one text node (most common case) 1014 DEBUG > 1 and printf qq{Catching "%s as " as ho-hum L<URL> link.\n}, 1015 $treelet->[$i][2] 1016 ; 1017 $treelet->[$i][1]{'to'} = Pod::Simple::LinkSection->new( 1018 $treelet->[$i][2] 1019 ); # its own treelet 1020 } else { 1021 # It's a URL but complex (like "L<foo:bazE<123>bar>"). Feh. 1022 #$treelet->[$i][1]{'to'} = [ @{$treelet->[$i]} ]; 1023 #splice @{ $treelet->[$i][1]{'to'} }, 0,2; 1024 #DEBUG > 1 and printf qq{Catching "%s as " as complex L<URL> link.\n}, 1025 # join '~', @{$treelet->[$i][1]{'to' }}; 1026 1027 $treelet->[$i][1]{'to'} = Pod::Simple::LinkSection->new( 1028 $treelet->[$i] # yes, clone the whole content as a treelet 1029 ); 1030 $treelet->[$i][1]{'to'}[0] = ''; # set the copy's tagname to nil 1031 die "SANITY FAILURE" if $treelet->[0] eq ''; # should never happen! 1032 DEBUG > 1 and print 1033 qq{Catching "$treelet->[$i][1]{'to'}" as a complex L<URL> link.\n}; 1049 $ell->[1]{'type'} = 'url'; 1050 1051 my @text = @{$ell}[2..$url_index-1]; 1052 push @text, $text_part if defined $text_part; 1053 1054 my @url = @{$ell}[$url_index+1..$#$ell]; 1055 unshift @url, $url_part; 1056 1057 unless (@text) { 1058 $ell->[1]{'content-implicit'} = 'yes'; 1059 @text = @url; 1034 1060 } 1035 1061 1036 next; # and move on 1062 $ell->[1]{to} = Pod::Simple::LinkSection->new( 1063 @url == 1 1064 ? $url[0] 1065 : [ '', {}, @url ], 1066 ); 1067 1068 splice @$ell, 2, $#$ell, @text; 1069 1070 next; 1037 1071 } 1038 1072 1039 1040 1073 # Catch some very simple and/or common cases 1041 if(@{$ treelet->[$i]} == 3 and ! ref $treelet->[$i][2]) {1042 my $it = $ treelet->[$i][2];1074 if(@{$ell} == 3 and ! ref $ell->[2]) { 1075 my $it = $ell->[2]; 1043 1076 if($it =~ m/^[-a-zA-Z0-9]+\([-a-zA-Z0-9]+\)$/s) { # man sections 1044 1077 # Hopefully neither too broad nor too restrictive a RE 1045 1078 DEBUG > 1 and print "Catching \"$it\" as manpage link.\n"; 1046 $ treelet->[$i][1]{'type'} = 'man';1079 $ell->[1]{'type'} = 'man'; 1047 1080 # This's the only place where man links can get made. 1048 $ treelet->[$i][1]{'content-implicit'} = 'yes';1049 $ treelet->[$i][1]{'to' } =1081 $ell->[1]{'content-implicit'} = 'yes'; 1082 $ell->[1]{'to' } = 1050 1083 Pod::Simple::LinkSection->new( $it ); # treelet! 1051 1084 1052 1085 next; … … 1055 1088 # Extremely forgiving idea of what constitutes a bare 1056 1089 # modulename link like L<Foo::Bar> or even L<Thing::1.0::Docs::Tralala> 1057 1090 DEBUG > 1 and print "Catching \"$it\" as ho-hum L<Modulename> link.\n"; 1058 $ treelet->[$i][1]{'type'} = 'pod';1059 $ treelet->[$i][1]{'content-implicit'} = 'yes';1060 $ treelet->[$i][1]{'to' } =1091 $ell->[1]{'type'} = 'pod'; 1092 $ell->[1]{'content-implicit'} = 'yes'; 1093 $ell->[1]{'to' } = 1061 1094 Pod::Simple::LinkSection->new( $it ); # treelet! 1062 1095 next; 1063 1096 } … … 1073 1106 1074 1107 1075 1108 my $link_text; # set to an arrayref if found 1076 my $ell = $treelet->[$i];1077 1109 my @ell_content = @$ell; 1078 1110 splice @ell_content,0,2; # Knock off the 'L' and {} bits 1079 1111 … … 1357 1389 $i += @$to_pull_up - 1; # Make $i skip the pulled-up stuff 1358 1390 } 1359 1391 } else { 1360 $treelet->[$i] =~ tr/ /\xA0/if ASCII and $in_s;1392 $treelet->[$i] =~ s/\s/\xA0/g if ASCII and $in_s; 1361 1393 # (If not in ASCIIland, we can't assume that \xA0 == nbsp.) 1362 1394 1363 1395 # Note that if you apply nbsp_for_S to text, and so turn … … 1427 1459 "\nAbout to parse source: {{\n$_[0]\n}}\n\n"; 1428 1460 1429 1461 1430 my $parser = $class->new;1462 my $parser = ref $class && $class->isa(__PACKAGE__) ? $class : $class->new; 1431 1463 $parser->hide_line_numbers(1); 1432 1464 1433 1465 my $out = ''; -
lib/Pod/Simple/XMLOutStream.pm
113 113 L<Pod::Simple::DumpAsXML> is rather like this class; see its 114 114 documentation for a discussion of the differences. 115 115 116 L<Pod::Simple>, L<Pod::Simple::DumpAsXML> 116 L<Pod::Simple>, L<Pod::Simple::DumpAsXML>, L<Pod::SAX> 117 117 118 The older libraries L<Pod::PXML>, L<Pod::XML>, L<Pod::SAX>118 L<Pod::Simple::Subclassing> 119 119 120 The older (and possibly obsolete) libraries L<Pod::PXML>, L<Pod::XML> 120 121 122 123 =head1 ABOUT EXTENDING POD 124 125 TODO: An example or two of =extend, then point to Pod::Simple::Subclassing 126 127 128 =head1 ASK ME! 129 130 If you actually want to use Pod as a format that you want to render to 131 XML (particularly if to an XML instance with more elements than normal 132 Pod has), please email me (C<sburke@cpan.org>) and I'll probably have 133 some recommendations. 134 135 For reasons of concision and energetic laziness, some methods and 136 options in this module (and the dozen modules it depends on) are 137 undocumented; but one of those undocumented bits might be just what 138 you're looking for. 139 140 121 141 =head1 COPYRIGHT AND DISCLAIMERS 122 142 123 Copyright (c) 2002 Sean M. Burke. All rights reserved.143 Copyright (c) 2002-4 Sean M. Burke. All rights reserved. 124 144 125 145 This library is free software; you can redistribute it and/or modify it 126 146 under the same terms as Perl itself. -
lib/Pod/Simple/HTML.pm
3 3 package Pod::Simple::HTML; 4 4 use strict; 5 5 use Pod::Simple::PullParser (); 6 use vars qw(@ISA %Tagmap $Computerese $Lame $Linearization_Limit $VERSION); 6 use vars qw( 7 @ISA %Tagmap $Computerese $LamePad $Linearization_Limit $VERSION 8 $Perldoc_URL_Prefix $Perldoc_URL_Postfix 9 $Title_Prefix $Title_Postfix $HTML_EXTENSION %ToIndex 10 $Doctype_decl $Content_decl 11 ); 7 12 @ISA = ('Pod::Simple::PullParser'); 8 $VERSION = ' 2.02';13 $VERSION = '3.03'; 9 14 10 15 use UNIVERSAL (); 11 sub DEBUG () {0} 16 BEGIN { 17 if(defined &DEBUG) { } # no-op 18 elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG } 19 else { *DEBUG = sub () {0}; } 20 } 12 21 13 $Computerese = " lang='und' xml:lang='und'" unless defined $Computerese; 14 $Lame = ' class="pad"' unless defined $Lame; 22 $Doctype_decl ||= ''; # No. Just No. Don't even ask me for it. 23 # qq{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 24 # "http://www.w3.org/TR/html4/loose.dtd">\n}; 15 25 16 $Linearization_Limit = 90 unless defined $Linearization_Limit; 26 $Content_decl ||= 27 q{<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" >}; 28 29 $HTML_EXTENSION = '.html' unless defined $HTML_EXTENSION; 30 $Computerese = "" unless defined $Computerese; 31 $LamePad = '' unless defined $LamePad; 32 33 $Linearization_Limit = 120 unless defined $Linearization_Limit; 17 34 # headings/items longer than that won't get an <a name="..."> 35 $Perldoc_URL_Prefix = 'http://search.cpan.org/perldoc?' 36 unless defined $Perldoc_URL_Prefix; 37 $Perldoc_URL_Postfix = '' 38 unless defined $Perldoc_URL_Postfix; 18 39 40 $Title_Prefix = '' unless defined $Title_Prefix; 41 $Title_Postfix = '' unless defined $Title_Postfix; 42 %ToIndex = map {; $_ => 1 } qw(head1 head2 head3 head4 ); # item-text 43 # 'item-text' stuff in the index doesn't quite work, and may 44 # not be a good idea anyhow. 45 46 47 __PACKAGE__->_accessorize( 48 'perldoc_url_prefix', 49 # In turning L<Foo::Bar> into http://whatever/Foo%3a%3aBar, what 50 # to put before the "Foo%3a%3aBar". 51 # (for singleton mode only?) 52 'perldoc_url_postfix', 53 # what to put after "Foo%3a%3aBar" in the URL. Normally "". 54 55 'batch_mode', # whether we're in batch mode 56 'batch_mode_current_level', 57 # When in batch mode, how deep the current module is: 1 for "LWP", 58 # 2 for "LWP::Procotol", 3 for "LWP::Protocol::GHTTP", etc 59 60 'title_prefix', 'title_postfix', 61 # What to put before and after the title in the head. 62 # Should already be &-escaped 63 64 'html_header_before_title', 65 'html_header_after_title', 66 'html_footer', 67 68 'index', # whether to add an index at the top of each page 69 # (actually it's a table-of-contents, but we'll call it an index, 70 # out of apparently longstanding habit) 71 72 'html_css', # URL of CSS file to point to 73 'html_javascript', # URL of CSS file to point to 74 75 'force_title', # should already be &-escaped 76 'default_title', # should already be &-escaped 77 ); 78 19 79 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 80 my @_to_accept; 21 81 … … 77 137 ] # no point in providing a way to get <q>...</q>, I think 78 138 ), 79 139 80 '/item-bullet' => "</li> <p$Lame></p>\n",81 '/item-number' => "</li> <p$Lame></p>\n",82 '/item-text' => "</a></dt> <p$Lame></p>\n",83 ' Para_item' => "\n<dd>",84 '/ Para_item' => "</dd><p$Lame></p>\n",140 '/item-bullet' => "</li>$LamePad\n", 141 '/item-number' => "</li>$LamePad\n", 142 '/item-text' => "</a></dt>$LamePad\n", 143 'item-body' => "\n<dd>", 144 '/item-body' => "</dd>\n", 85 145 146 86 147 'B' => "<b>", '/B' => "</b>", 87 148 'I' => "<i>", '/I' => "</i>", 88 149 'F' => "<em$Computerese>", '/F' => "</em>", … … 103 164 } 104 165 105 166 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 167 sub go { Pod::Simple::HTML->parse_from_file(@ARGV); exit 0 } 168 # Just so we can run from the command line. No options. 169 # For that, use perldoc! 170 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 106 171 107 172 sub new { 108 173 my $new = shift->SUPER::new(@_); … … 112 177 $new->accept_codes('VerbatimFormatted'); 113 178 $new->accept_codes(@_to_accept); 114 179 DEBUG > 2 and print "To accept: ", join(' ',@_to_accept), "\n"; 115 180 181 $new->perldoc_url_prefix( $Perldoc_URL_Prefix ); 182 $new->perldoc_url_postfix( $Perldoc_URL_Postfix ); 183 $new->title_prefix( $Title_Prefix ); 184 $new->title_postfix( $Title_Postfix ); 185 186 $new->html_header_before_title( 187 qq[$Doctype_decl<html><head><title>] 188 ); 189 $new->html_header_after_title( join "\n" => 190 "</title>", 191 $Content_decl, 192 "</head>\n<body class='pod'>", 193 $new->version_tag_comment, 194 "<!-- start doc -->\n", 195 ); 196 $new->html_footer( qq[\n<!-- end doc -->\n\n</body></html>\n] ); 197 116 198 $new->{'Tagmap'} = {%Tagmap}; 117 199 return $new; 118 200 } 119 201 202 sub batch_mode_page_object_init { 203 my($self, $batchconvobj, $module, $infile, $outfile, $depth) = @_; 204 DEBUG and print "Initting $self\n for $module\n", 205 " in $infile\n out $outfile\n depth $depth\n"; 206 $self->batch_mode(1); 207 $self->batch_mode_current_level($depth); 208 return $self; 209 } 210 120 211 sub run { 121 212 my $self = $_[0]; 122 213 return $self->do_middle if $self->bare_output; … … 126 217 127 218 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 128 219 129 sub do_pod_link { 130 my($self, $link) = @_; 131 my $to = $link->attr('to'); 132 my $section = $link->attr('section'); 133 return undef unless( # should never happen 134 (defined $to and length $to) or 135 (defined $section and length $section) 136 ); 220 sub do_beginning { 221 my $self = $_[0]; 137 222 138 if(defined $to and length $to) { 139 $to = $self->resolve_pod_page_link($to, $section); 140 return undef unless defined $to and length $to; 141 # resolve_pod_page_link returning undef is how it 142 # can signal that it gives up on making a link 143 # (I pass it the section value, but I don't see a 144 # particular reason it'd use it.) 223 my $title; 224 225 if(defined $self->force_title) { 226 $title = $self->force_title; 227 DEBUG and print "Forcing title to be $title\n"; 228 } else { 229 # Actually try looking for the title in the document: 230 $title = $self->get_short_title(); 231 unless($self->content_seen) { 232 DEBUG and print "No content seen in search for title.\n"; 233 return; 234 } 235 $self->{'Title'} = $title; 236 237 if(defined $title and $title =~ m/\S/) { 238 $title = $self->title_prefix . esc($title) . $self->title_postfix; 239 } else { 240 $title = $self->default_title; 241 $title = '' unless defined $title; 242 DEBUG and print "Title defaults to $title\n"; 243 } 145 244 } 245 146 246 147 if(defined $section and length($section .= '')) { 148 $section =~ tr/ /_/; 149 $section =~ tr/\x00-\x1F\x80-\x9F//d if 'A' eq chr(65); 150 $section = $self->unicode_escape_url($section); 151 # Turn char 1234 into "(1234)" 152 $section = '_' unless length $section; 247 my $after = $self->html_header_after_title || ''; 248 if($self->html_css) { 249 my $link = 250 $self->html_css =~ m/</ 251 ? $self->html_css # It's a big blob of markup, let's drop it in 252 : sprintf( # It's just a URL, so let's wrap it up 253 qq[<link rel="stylesheet" type="text/css" title="pod_stylesheet" href="%s">\n], 254 $self->html_css, 255 ); 256 $after =~ s{(</head>)}{$link\n$1}i; # otherwise nevermind 153 257 } 154 155 258 $self->_add_top_anchor(\$after); 156 259 157 foreach my $it ($to, $section) {158 if( defined $it ) {159 $it =~ s/([^\x00-\xFF])/join '', map sprintf('%%%02X',$_), unpack 'C*', $1/eg;160 $it =~ s/([^\._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/sprintf('%%%02X',ord($1))/eg;161 # Yes, stipulate the list without a range, so that this can work right on162 # all charsets that this module happens to run under.163 # Altho, hmm, what about that ord? Presumably that won't work right164 # under non-ASCII charsets. Something should be done about that.165 }260 if($self->html_javascript) { 261 my $link = 262 $self->html_javascript =~ m/</ 263 ? $self->html_javascript # It's a big blob of markup, let's drop it in 264 : sprintf( # It's just a URL, so let's wrap it up 265 qq[<script type="text/javascript" src="%s"></script>\n], 266 $self->html_javascript, 267 ); 268 $after =~ s{(</head>)}{$link\n$1}i; # otherwise nevermind 166 269 } 167 168 my $out = $to if defined $to and length $to; 169 $out .= "#" . $section if defined $section and length $section; 170 return undef unless length $out; 171 return $out; 270 271 print {$self->{'output_fh'}} 272 $self->html_header_before_title || '', 273 $title, # already escaped 274 $after, 275 ; 276 277 DEBUG and print "Returning from do_beginning...\n"; 278 return 1; 172 279 } 173 280 281 sub _add_top_anchor { 282 my($self, $text_r) = @_; 283 unless($$text_r and $$text_r =~ m/name=['"]___top['"]/) { # a hack 284 $$text_r .= "<a name='___top' class='dummyTopAnchor' ></a>\n"; 285 } 286 return; 287 } 174 288 175 sub resolve_pod_page_link { 176 my($self, $to) = @_; 177 178 return 'TODO'; 289 sub version_tag_comment { 290 my $self = shift; 291 return sprintf 292 "<!--\n generated by %s v%s,\n using %s v%s,\n under Perl v%s at %s GMT.\n\n %s\n\n-->\n", 293 esc( 294 ref($self), $self->VERSION(), $ISA[0], $ISA[0]->VERSION(), 295 $], scalar(gmtime), 296 ), $self->_modnote(), 297 ; 179 298 } 180 299 181 sub do_url_link { return $_[1]->attr('to') } 300 sub _modnote { 301 my $class = ref($_[0]) || $_[0]; 302 return join "\n " => grep m/\S/, split "\n", 182 303 183 sub do_man_link { return undef } 184 # But subclasses are welcome to override this if they have man 185 # pages somewhere URL-accessible. 304 qq{ 305 If you want to change this HTML document, you probably shouldn't do that 306 by changing it directly. Instead, see about changing the calling options 307 to $class, and/or subclassing $class, 308 then reconverting this document from the Pod source. 309 When in doubt, email the author of $class for advice. 310 See 'perldoc $class' for more info. 311 }; 186 312 187 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 313 } 188 314 189 sub do_link { 190 my($self, $token) = @_; 191 my $type = $token->attr('type'); 192 if(!defined $type) { 193 $self->whine("Typeless L!?", $token->attr('start_line')); 194 } elsif( $type eq 'pod') { return $self->do_pod_link($token); 195 } elsif( $type eq 'url') { return $self->do_url_link($token); 196 } elsif( $type eq 'man') { return $self->do_man_link($token); 197 } else { 198 $self->whine("L of unknown type $type!?", $token->attr('start_line')); 315 sub do_end { 316 my $self = $_[0]; 317 print {$self->{'output_fh'}} $self->html_footer || ''; 318 return 1; 319 } 320 321 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 322 # Normally this would just be a call to _do_middle_main_loop -- but we 323 # have to do some elaborate things to emit all the content and then 324 # summarize it and output it /before/ the content that it's a summary of. 325 326 sub do_middle { 327 my $self = $_[0]; 328 return $self->_do_middle_main_loop unless $self->index; 329 330 if( $self->output_string ) { 331 # An efficiency hack 332 my $out = $self->output_string; #it's a reference to it 333 my $sneakytag = "\f\f\e\e\b\bIndex Here\e\e\b\b\f\f\n"; 334 $$out .= $sneakytag; 335 $self->_do_middle_main_loop; 336 $sneakytag = quotemeta($sneakytag); 337 my $index = $self->index_as_html(); 338 if( $$out =~ s/$sneakytag/$index/s ) { 339 # Expected case 340 DEBUG and print "Inserted ", length($index), " bytes of index HTML into $out.\n"; 341 } else { 342 DEBUG and print "Odd, couldn't find where to insert the index in the output!\n"; 343 # I don't think this should ever happen. 344 } 345 return 1; 199 346 } 200 return 'FNORG'; 347 348 unless( $self->output_fh ) { 349 require Carp; 350 Carp::confess("Parser object \$p doesn't seem to have any output object! I don't know how to deal with that."); 351 } 352 353 # If we get here, we're outputting to a FH. So we need to do some magic. 354 # Namely, divert all content to a string, which we output after the index. 355 my $fh = $self->output_fh; 356 my $content = ''; 357 { 358 # Our horrible bait and switch: 359 $self->output_string( \$content ); 360 $self->_do_middle_main_loop; 361 $self->abandon_output_string(); 362 $self->output_fh($fh); 363 } 364 print $fh $self->index_as_html(); 365 print $fh $content; 366 367 return 1; 201 368 } 202 369 370 ########################################################################### 203 371 204 sub do_middle { # the main work372 sub index_as_html { 205 373 my $self = $_[0]; 374 # This is meant to be called AFTER the input document has been parsed! 375 376 my $points = $self->{'PSHTML_index_points'} || []; 377 378 @$points > 1 or return qq[<div class='indexgroupEmpty'></div>\n]; 379 # There's no point in having a 0-item or 1-item index, I dare say. 380 381 my(@out) = qq{\n<div class='indexgroup'>}; 382 my $level = 0; 383 384 my( $target_level, $previous_tagname, $tagname, $text, $anchorname, $indent); 385 foreach my $p (@$points, ['head0', '(end)']) { 386 ($tagname, $text) = @$p; 387 $anchorname = $self->section_escape($text); 388 if( $tagname =~ m{^head(\d+)$} ) { 389 $target_level = 0 + $1; 390 } else { # must be some kinda list item 391 if($previous_tagname =~ m{^head\d+$} ) { 392 $target_level = $level + 1; 393 } else { 394 $target_level = $level; # no change needed 395 } 396 } 397 398 # Get to target_level by opening or closing ULs 399 while($level > $target_level) 400 { --$level; push @out, (" " x $level) . "</ul>"; } 401 while($level < $target_level) 402 { ++$level; push @out, (" " x ($level-1)) 403 . "<ul class='indexList indexList$level'>"; } 404 405 $previous_tagname = $tagname; 406 next unless $level; 407 408 $indent = ' ' x $level; 409 push @out, sprintf 410 "%s<li class='indexItem indexItem%s'><a href='#%s'>%s</a>", 411 $indent, $level, $anchorname, esc($text) 412 ; 413 } 414 push @out, "</div>\n"; 415 return join "\n", @out; 416 } 417 418 ########################################################################### 419 420 sub _do_middle_main_loop { 421 my $self = $_[0]; 206 422 my $fh = $self->{'output_fh'}; 423 my $tagmap = $self->{'Tagmap'}; 207 424 208 my($token, $type, $tagname );425 my($token, $type, $tagname, $linkto, $linktype); 209 426 my @stack; 210 427 my $dont_wrap = 0; 428 211 429 while($token = $self->get_token) { 212 430 213 431 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 214 432 if( ($type = $token->type) eq 'start' ) { 215 433 if(($tagname = $token->tagname) eq 'L') { 216 esc($type = $self->do_link($token)); # reuse it, why not 217 if(defined $type and length $type) { 218 print $fh "<a href='$type'>"; 434 $linktype = $token->attr('type') || 'insane'; 435 436 $linkto = $self->do_link($token); 437 438 if(defined $linkto and length $linkto) { 439 esc($linkto); 440 # (Yes, SGML-escaping applies on top of %-escaping! 441 # But it's rarely noticeable in practice.) 442 print $fh qq{<a href="$linkto" class="podlink$linktype"\n>}; 219 443 } else { 220 444 print $fh "<a>"; # Yes, an 'a' element with no attributes! 221 445 } 222 446 223 447 } elsif ($tagname eq 'item-text' or $tagname =~ m/^head\d$/s) { 224 print $fh $ self->{'Tagmap'}{$tagname} || next;448 print $fh $tagmap->{$tagname} || next; 225 449 226 450 my @to_unget; 227 451 while(1) { 228 452 push @to_unget, $self->get_token; 229 453 last if $to_unget[-1]->is_end 230 454 and $to_unget[-1]->tagname eq $tagname; 455 456 # TODO: support for X<...>'s found in here? (maybe hack into linearize_tokens) 231 457 } 458 232 459 my $name = $self->linearize_tokens(@to_unget); 233 460 234 if(defined $name) { # ludicrously long, so nevermind 235 $name =~ tr/ /_/; 236 print $fh "<a name=\"", esc($name), "\"\n>"; 461 print $fh "<a "; 462 print $fh "class='u' href='#___top' title='click to go to top of document'\n" 463 if $tagname =~ m/^head\d$/s; 464 465 if(defined $name) { 466 my $esc = esc( $self->section_name_tidy( $name ) ); 467 print $fh qq[name="$esc"]; 237 468 DEBUG and print "Linearized ", scalar(@to_unget), 238 469 " tokens as \"$name\".\n"; 239 } else { 240 print $fh "<a\n>"; # Yes, an 'a' element with no attributes! 470 push @{ $self->{'PSHTML_index_points'} }, [$tagname, $name] 471 if $ToIndex{ $tagname }; 472 # Obviously, this discards all formatting codes (saving 473 # just their content), but ahwell. 474 475 } else { # ludicrously long, so nevermind 241 476 DEBUG and print "Linearized ", scalar(@to_unget), 242 477 " tokens, but it was too long, so nevermind.\n"; 243 478 } 479 print $fh "\n>"; 244 480 $self->unget_token(@to_unget); 245 481 246 482 } elsif ($tagname eq 'Data') { … … 255 491 next; 256 492 257 493 } else { 258 if( $tagname =~ m/^over-(.+)$/s ) { 259 push @stack, $1; 260 } elsif( $tagname eq 'Para') { 261 $tagname = 'Para_item' if @stack and $stack[-1] eq 'text'; 494 if( $tagname =~ m/^over-/s ) { 495 push @stack, ''; 496 } elsif( $tagname =~ m/^item-/s and @stack and $stack[-1] ) { 497 print $fh $stack[-1]; 498 $stack[-1] = ''; 262 499 } 263 print $fh $ self->{'Tagmap'}{$tagname} || next;500 print $fh $tagmap->{$tagname} || next; 264 501 ++$dont_wrap if $tagname eq 'Verbatim' or $tagname eq "VerbatimFormatted" 265 502 or $tagname eq 'X'; 266 503 } … … 268 505 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 269 506 } elsif( $type eq 'end' ) { 270 507 if( ($tagname = $token->tagname) =~ m/^over-/s ) { 271 pop @stack; 272 } elsif( $tagname eq 'Para' ) { 273 $tagname = 'Para_item' if @stack and $stack[-1] eq 'text'; 508 if( my $end = pop @stack ) { 509 print $fh $end; 510 } 511 } elsif( $tagname =~ m/^item-/s and @stack) { 512 $stack[-1] = $tagmap->{"/$tagname"}; 513 if( $tagname eq 'item-text' and defined(my $next = $self->get_token) ) { 514 $self->unget_token($next); 515 if( $next->type eq 'start' and $next->tagname !~ m/^item-/s ) { 516 print $fh $tagmap->{"/item-text"},$tagmap->{"item-body"}; 517 $stack[-1] = $tagmap->{"/item-body"}; 518 } 519 } 520 next; 274 521 } 275 print $fh $ self->{'Tagmap'}{"/$tagname"} || next;522 print $fh $tagmap->{"/$tagname"} || next; 276 523 --$dont_wrap if $tagname eq 'Verbatim' or $tagname eq 'X'; 277 524 278 525 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - … … 286 533 return 1; 287 534 } 288 535 536 ########################################################################### 537 # 538 539 sub do_link { 540 my($self, $token) = @_; 541 my $type = $token->attr('type'); 542 if(!defined $type) { 543 $self->whine("Typeless L!?", $token->attr('start_line')); 544 } elsif( $type eq 'pod') { return $self->do_pod_link($token); 545 } elsif( $type eq 'url') { return $self->do_url_link($token); 546 } elsif( $type eq 'man') { return $self->do_man_link($token); 547 } else { 548 $self->whine("L of unknown type $type!?", $token->attr('start_line')); 549 } 550 return 'FNORG'; # should never get called 551 } 552 289 553 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 290 554 291 sub do_beginning { 292 my $self = $_[0]; 555 sub do_url_link { return $_[1]->attr('to') } 293 556 294 my $title = $self->get_short_title(); 295 unless($self->content_seen) { 296 DEBUG and print "No content seen in search for title.\n"; 297 return; 557 sub do_man_link { return undef } 558 # But subclasses are welcome to override this if they have man 559 # pages somewhere URL-accessible. 560 561 562 sub do_pod_link { 563 # And now things get really messy... 564 my($self, $link) = @_; 565 my $to = $link->attr('to'); 566 my $section = $link->attr('section'); 567 return undef unless( # should never happen 568 (defined $to and length $to) or 569 (defined $section and length $section) 570 ); 571 572 $section = $self->section_escape($section) 573 if defined $section and length($section .= ''); # (stringify) 574 575 DEBUG and printf "Resolving \"%s\" \"%s\"...\n", 576 $to || "(nil)", $section || "(nil)"; 577 578 { 579 # An early hack: 580 my $complete_url = $self->resolve_pod_link_by_table($to, $section); 581 if( $complete_url ) { 582 DEBUG > 1 and print "resolve_pod_link_by_table(T,S) gives ", 583 $complete_url, "\n (Returning that.)\n"; 584 return $complete_url; 585 } else { 586 DEBUG > 4 and print " resolve_pod_link_by_table(T,S)", 587 " didn't return anything interesting.\n"; 588 } 298 589 } 299 $self->{'Title'} = $title;300 590 301 esc($title); 302 print {$self->{'output_fh'}} 303 "<html><head>\n<title>$title</title>\n</head>\n<body>\n", 304 $self->version_tag_comment, 305 "<!-- start doc -->\n", 306 ; 307 # TODO: more configurability there 591 if(defined $to and length $to) { 592 # Give this routine first hack again 593 my $there = $self->resolve_pod_link_by_table($to); 594 if(defined $there and length $there) { 595 DEBUG > 1 596 and print "resolve_pod_link_by_table(T) gives $there\n"; 597 } else { 598 $there = 599 $self->resolve_pod_page_link($to, $section); 600 # (I pass it the section value, but I don't see a 601 # particular reason it'd use it.) 602 DEBUG > 1 and print "resolve_pod_page_link gives ", $to || "(nil)", "\n"; 603 unless( defined $there and length $there ) { 604 DEBUG and print "Can't resolve $to\n"; 605 return undef; 606 } 607 # resolve_pod_page_link returning undef is how it 608 # can signal that it gives up on making a link 609 } 610 $to = $there; 611 } 308 612 309 DEBUG and print "Returning from do_beginning...\n"; 310 return 1; 613 #DEBUG and print "So far [", $to||'nil', "] [", $section||'nil', "]\n"; 614 615 my $out = (defined $to and length $to) ? $to : ''; 616 $out .= "#" . $section if defined $section and length $section; 617 618 unless(length $out) { # sanity check 619 DEBUG and printf "Oddly, couldn't resolve \"%s\" \"%s\"...\n", 620 $to || "(nil)", $section || "(nil)"; 621 return undef; 622 } 623 624 DEBUG and print "Resolved to $out\n"; 625 return $out; 311 626 } 312 627 313 sub version_tag_comment { 314 my $self = shift; 315 return sprintf 316 "<!-- generated by %s v%s, using %s v%s, under Perl v%s at %s GMT -->\n", 317 # None of the following things should need escaping, I dare say!318 ref($self), $self->VERSION(), $ISA[0], $ISA[0]->VERSION(),319 $ ], scalar(gmtime),320 ;628 629 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630 631 sub section_escape { 632 my($self, $section) = @_; 633 return $self->section_url_escape( 634 $self->section_name_tidy($section) 635 ); 321 636 } 322 637 638 sub section_name_tidy { 639 my($self, $section) = @_; 640 $section =~ tr/ /_/; 641 $section =~ tr/\x00-\x1F\x80-\x9F//d if 'A' eq chr(65); # drop crazy characters 642 $section = $self->unicode_escape_url($section); 643 $section = '_' unless length $section; 644 return $section; 645 } 323 646 324 sub do_end { 325 my $self = $_[0]; 326 print {$self->{'output_fh'}} "\n<!-- end doc -->\n</body></html>\n"; 327 # TODO: allow for a footer 328 return 1; 647 sub section_url_escape { shift->general_url_escape(@_) } 648 sub pagepath_url_escape { shift->general_url_escape(@_) } 649 650 sub general_url_escape { 651 my($self, $string) = @_; 652 653 $string =~ s/([^\x00-\xFF])/join '', map sprintf('%%%02X',$_), unpack 'C*', $1/eg; 654 # express Unicode things as urlencode(utf(orig)). 655 656 # A pretty conservative escaping, behoovey even for query components 657 # of a URL (see RFC 2396) 658 659 $string =~ s/([^-_\.!~*()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/sprintf('%%%02X',ord($1))/eg; 660 # Yes, stipulate the list without a range, so that this can work right on 661 # all charsets that this module happens to run under. 662 # Altho, hmm, what about that ord? Presumably that won't work right 663 # under non-ASCII charsets. Something should be done 664 # about that, I guess? 665 666 return $string; 329 667 } 330 668 331 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 332 sub esc { 333 if(defined wantarray) { 334 if(wantarray) { 335 @_ = splice @_; # break aliasing 336 } else { 337 my $x = shift; 338 $x =~ s/([^\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(ord($1)).';'/eg; 339 return $x; 340 } 669 #-------------------------------------------------------------------------- 670 # 671 # Oh look, a yawning portal to Hell! Let's play touch football right by it! 672 # 673 674 sub resolve_pod_page_link { 675 # resolve_pod_page_link must return a properly escaped URL 676 my $self = shift; 677 return $self->batch_mode() 678 ? $self->resolve_pod_page_link_batch_mode(@_) 679 : $self->resolve_pod_page_link_singleton_mode(@_) 680 ; 681 } 682 683 sub resolve_pod_page_link_singleton_mode { 684 my($self, $it) = @_; 685 return undef unless defined $it and length $it; 686 my $url = $self->pagepath_url_escape($it); 687 688 $url =~ s{::$}{}s; # probably never comes up anyway 689 $url =~ s{::}{/}g unless $self->perldoc_url_prefix =~ m/\?/s; # sane DWIM? 690 691 return undef unless length $url; 692 return $self->perldoc_url_prefix . $url . $self->perldoc_url_postfix; 693 } 694 695 sub resolve_pod_page_link_batch_mode { 696 my($self, $to) = @_; 697 DEBUG > 1 and print " During batch mode, resolving $to ...\n"; 698 my @path = grep length($_), split m/::/s, $to, -1; 699 unless( @path ) { # sanity 700 DEBUG and print "Very odd! Splitting $to gives (nil)!\n"; 701 return undef; 341 702 } 342 foreach my $x (@_) { 343 # Escape things very cautiously: 344 $x =~ s/([^\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(ord($1)).';'/eg; 345 # Leave out "- so that "--" won't make it thru in X-generated comments 346 # with text in them. 703 $self->batch_mode_rectify_path(\@path); 704 my $out = join('/', map $self->pagepath_url_escape($_), @path) 705 . $HTML_EXTENSION; 706 DEBUG > 1 and print " => $out\n"; 707 return $out; 708 } 347 709 348 # Yes, stipulate the list without a range, so that this can work right on 349 # all charsets that this module happens to run under. 350 # Altho, hmm, what about that ord? Presumably that won't work right 351 # under non-ASCII charsets. Something should be done about that. 710 sub batch_mode_rectify_path { 711 my($self, $pathbits) = @_; 712 my $level = $self->batch_mode_current_level; 713 $level--; # how many levels up to go to get to the root 714 if($level < 1) { 715 unshift @$pathbits, '.'; # just to be pretty 716 } else { 717 unshift @$pathbits, ('..') x $level; 352 718 } 353 return @_;719 return; 354 720 } 355 721 356 722 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 357 723 724 sub resolve_pod_link_by_table { 725 # A crazy hack to allow specifying custom L<foo> => URL mappings 726 727 return unless $_[0]->{'podhtml_LOT'}; # An optimizy shortcut 728 729 my($self, $to, $section) = @_; 730 731 # TODO: add a method that actually populates podhtml_LOT from a file? 732 733 if(defined $section) { 734 $to = '' unless defined $to and length $to; 735 return $self->{'podhtml_LOT'}{"$to#$section"}; # quite possibly undef! 736 } else { 737 return $self->{'podhtml_LOT'}{$to}; # quite possibly undef! 738 } 739 return; 740 } 741 742 ########################################################################### 743 358 744 sub linearize_tokens { # self, tokens 359 745 my $self = shift; 360 746 my $out = ''; … … 362 748 my $t; 363 749 while($t = shift @_) { 364 750 if(!ref $t or !UNIVERSAL::can($t, 'is_text')) { 365 $out .= $t; 751 $out .= $t; # a string, or some insane thing 366 752 } elsif($t->is_text) { 367 753 $out .= $t->text; 368 754 } elsif($t->is_start and $t->tag eq 'X') { 369 # ignore until the end of this X<...> sequence755 # Ignore until the end of this X<...> sequence: 370 756 my $x_open = 1; 371 757 while($x_open) { 372 758 next if( ($t = shift @_)->is_text ); … … 375 761 } 376 762 } 377 763 } 378 379 $out =~ tr/\x00-\x1F\x80-\x9F//d if 'A' eq chr(65);380 764 return undef if length $out > $Linearization_Limit; 381 382 $out = $self->unicode_escape_url($out);383 $out = '_' unless length $out;384 385 765 return $out; 386 766 } 387 767 … … 395 775 } 396 776 397 777 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 778 sub esc { # a function. 779 if(defined wantarray) { 780 if(wantarray) { 781 @_ = splice @_; # break aliasing 782 } else { 783 my $x = shift; 784 $x =~ s/([^-\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(ord($1)).';'/eg; 785 return $x; 786 } 787 } 788 foreach my $x (@_) { 789 # Escape things very cautiously: 790 $x =~ s/([^-\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(ord($1)).';'/eg 791 if defined $x; 792 # Leave out "- so that "--" won't make it thru in X-generated comments 793 # with text in them. 398 794 795 # Yes, stipulate the list without a range, so that this can work right on 796 # all charsets that this module happens to run under. 797 # Altho, hmm, what about that ord? Presumably that won't work right 798 # under non-ASCII charsets. Something should be done about that. 799 } 800 return @_; 801 } 802 803 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 804 399 805 1; 400 806 __END__ 401 807 402 808 =head1 NAME 403 809 404 TODO - TODO 810 Pod::Simple::HTML - convert Pod to HTML 405 811 406 812 =head1 SYNOPSIS 407 813 408 TODO814 perl -MPod::Simple::HTML -e Pod::Simple::HTML::go thingy.pod 409 815 410 perl -MPod::Simple::HTML -e \411 "exit Pod::Simple::HTML->filter(shift)->errors_seen" \412 thingy.pod413 816 414 415 817 =head1 DESCRIPTION 416 818 417 This class is for TODO. 819 This class is for making an HTML rendering of a Pod document. 820 418 821 This is a subclass of L<Pod::Simple::PullParser> and inherits all its 419 methods .822 methods (and options). 420 823 824 Note that if you want to do a batch conversion of a lot of Pod 825 documents to HTML, you should see the module L<Pod::Simple::HTMLBatch>. 826 827 828 829 =head1 CALLING FROM THE COMMAND LINE 830 421 831 TODO 422 832 833 perl -MPod::Simple::HTML -e Pod::Simple::HTML::go Thing.pod Thing.html 834 835 836 837 =head1 CALLING FROM PERL 838 839 TODO make a new object, set any options, and use parse_from_file 840 841 842 =head1 METHODS 843 844 TODO 845 all (most?) accessorized methods 846 847 848 =head1 SUBCLASSING 849 850 TODO 851 852 can just set any of: html_css html_javascript title_prefix 853 'html_header_before_title', 854 'html_header_after_title', 855 'html_footer', 856 857 maybe override do_pod_link 858 859 maybe override do_beginning do_end 860 861 862 423 863 =head1 SEE ALSO 424 864 425 L<Pod::Simple> 865 L<Pod::Simple>, L<Pod::Simple::HTMLBatch> 426 866 867 868 TODO: a corpus of sample Pod input and HTML output? Or common 869 idioms? 870 871 872 427 873 =head1 COPYRIGHT AND DISCLAIMERS 428 874 429 Copyright (c) 2002 Sean M. Burke. All rights reserved.875 Copyright (c) 2002-2004 Sean M. Burke. All rights reserved. 430 876 431 877 This library is free software; you can redistribute it and/or modify it 432 878 under the same terms as Perl itself. -
lib/Pod/Simple/BlackBox.pm
525 525 DEBUG > 1 and print "Pondering a $para_type paragraph, given the stack: (", 526 526 $self->_dump_curr_open(), ")\n"; 527 527 528 if($para_type eq '=for') { #////////////////////////////////////////////// 529 # Fake it out as a begin/end 530 my $target; 528 if($para_type eq '=for') { 529 next if $self->_ponder_for($para,$curr_open,$paras); 531 530 532 if(grep $_->[1]{'~ignore'}, @$curr_open) { 533 DEBUG > 1 and print "Ignoring ignorable =for\n"; 534 next; 535 } 531 } elsif($para_type eq '=begin') { 532 next if $self->_ponder_begin($para,$curr_open,$paras); 536 533 537 for(my $i = 2; $i < @$para; ++$i) { 538 if($para->[$i] =~ s/^\s*(\S+)\s*//s) { 539 $target = $1; 540 last; 541 } 542 } 543 unless(defined $target) { 544 $self->whine( 545 $para->[1]{'start_line'}, 546 "=for without a target?" 547 ); 548 next; 549 } 550 DEBUG > 1 and 551 print "Faking out a =for $target as a =begin $target / =end $target\n"; 552 553 $para->[0] = 'Data'; 554 555 unshift @$paras, 556 ['=begin', 557 {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, 558 $target, 559 ], 560 $para, 561 ['=end', 562 {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, 563 $target, 564 ], 565 ; 566 567 next; 568 569 } elsif($para_type eq '=begin') { #/////////////////////////////////////// 534 } elsif($para_type eq '=end') { 535 next if $self->_ponder_end($para,$curr_open,$paras); 570 536 571 my $content = join ' ', splice @$para, 2; 572 $content =~ s/^\s+//s; 573 $content =~ s/\s+$//s; 574 unless(length($content)) { 575 $self->whine( 576 $para->[1]{'start_line'}, 577 "=begin without a target?" 578 ); 579 DEBUG and print "Ignoring targetless =begin\n"; 580 next; 581 } 582 583 unless($content =~ m/^\S+$/s) { # i.e., unless it's one word 584 $self->whine( 585 $para->[1]{'start_line'}, 586 "'=begin' only takes one parameter, not several as in '=begin $content'" 587 ); 588 DEBUG and print "Ignoring unintelligible =begin $content\n"; 589 next; 590 } 591 592 593 $para->[1]{'target'} = $content; # without any ':' 594 595 $content =~ s/^:!/!:/s; 596 my $neg; # whether this is a negation-match 597 $neg = 1 if $content =~ s/^!//s; 598 my $to_resolve; # whether to process formatting codes 599 $to_resolve = 1 if $content =~ s/^://s; 600 601 my $dont_ignore; # whether this target matches us 602 603 foreach my $target_name ( 604 split(',', $content, -1), 605 $neg ? () : '*' 606 ) { 607 DEBUG > 2 and 608 print " Considering whether =begin $content matches $target_name\n"; 609 next unless $self->{'accept_targets'}{$target_name}; 610 611 DEBUG > 2 and 612 print " It DOES match the acceptable target $target_name!\n"; 613 $to_resolve = 1 614 if $self->{'accept_targets'}{$target_name} eq 'force_resolve'; 615 $dont_ignore = 1; 616 $para->[1]{'target_matching'} = $target_name; 617 last; # stop looking at other target names 618 } 619 620 if($neg) { 621 if( $dont_ignore ) { 622 $dont_ignore = ''; 623 delete $para->[1]{'target_matching'}; 624 DEBUG > 2 and print " But the leading ! means that this is a NON-match!\n"; 625 } else { 626 $dont_ignore = 1; 627 $para->[1]{'target_matching'} = '!'; 628 DEBUG > 2 and print " But the leading ! means that this IS a match!\n"; 629 } 630 } 631 632 $para->[0] = '=for'; # Just what we happen to call these, internally 633 $para->[1]{'~really'} ||= '=begin'; 634 $para->[1]{'~ignore'} = (! $dont_ignore) || 0; 635 $para->[1]{'~resolve'} = $to_resolve || 0; 636 637 DEBUG > 1 and print " Making note to ", $dont_ignore ? 'not ' : '', 638 "ignore contents of this region\n"; 639 DEBUG > 1 and $dont_ignore and print " Making note to treat contents as ", 640 ($to_resolve ? 'verbatim/plain' : 'data'), " paragraphs\n"; 641 DEBUG > 1 and print " (Stack now: ", $self->_dump_curr_open(), ")\n"; 642 643 push @$curr_open, $para; 644 if(!$dont_ignore or scalar grep $_->[1]{'~ignore'}, @$curr_open) { 645 DEBUG > 1 and print "Ignoring ignorable =begin\n"; 646 } else { 647 $self->{'content_seen'} ||= 1; 648 $self->_handle_element_start(($scratch='for'), $para->[1]); 649 } 650 651 next; 652 653 } elsif($para_type eq '=end') { #///////////////////////////////////////// 654 655 my $content = join ' ', splice @$para, 2; 656 $content =~ s/^\s+//s; 657 $content =~ s/\s+$//s; 658 DEBUG and print "Ogling '=end $content' directive\n"; 659 660 unless(length($content)) { 661 $self->whine( 662 $para->[1]{'start_line'}, 663 "'=end' without a target?" . ( 664 ( @$curr_open and $curr_open->[-1][0] eq '=for' ) 665 ? ( " (Should be \"=end " . $curr_open->[-1][1]{'target'} . '")' ) 666 : '' 667 ) 668 ); 669 DEBUG and print "Ignoring targetless =end\n"; 670 next; 671 } 672 673 unless($content =~ m/^\S+$/) { # i.e., unless it's one word 674 $self->whine( 675 $para->[1]{'start_line'}, 676 "'=end $content' is invalid. (Stack: " 677 . $self->_dump_curr_open() . ')' 678 ); 679 DEBUG and print "Ignoring mistargetted =end $content\n"; 680 next; 681 } 682 683 unless(@$curr_open and $curr_open->[-1][0] eq '=for') { 684 $self->whine( 685 $para->[1]{'start_line'}, 686 "=end $content without matching =begin. (Stack: " 687 . $self->_dump_curr_open() . ')' 688 ); 689 DEBUG and print "Ignoring mistargetted =end $content\n"; 690 next; 691 } 692 693 unless($content eq $curr_open->[-1][1]{'target'}) { 694 $self->whine( 695 $para->[1]{'start_line'}, 696 "=end $content doesn't match =begin " 697 . $curr_open->[-1][1]{'target'} 698 . ". (Stack: " 699 . $self->_dump_curr_open() . ')' 700 ); 701 DEBUG and print "Ignoring mistargetted =end $content at line $para->[1]{'start_line'}\n"; 702 next; 703 } 704 705 # Else it's okay to close... 706 if(grep $_->[1]{'~ignore'}, @$curr_open) { 707 DEBUG > 1 and print "Not firing any event for this =end $content because in an ignored region\n"; 708 # And that may be because of this to-be-closed =for region, or some 709 # other one, but it doesn't matter. 710 } else { 711 $curr_open->[-1][1]{'start_line'} = $para->[1]{'start_line'}; 712 # what's that for? 713 714 $self->{'content_seen'} ||= 1; 715 $self->_handle_element_end( $scratch = 'for' ); 716 } 717 DEBUG > 1 and print "Popping $curr_open->[-1][0] $curr_open->[-1][1]{'target'} because of =end $content\n"; 718 pop @$curr_open; 719 720 next; 721 722 } elsif($para_type eq '~end') { #///////////////////////////////////////// 723 # The virtual end-document signal 724 725 if(@$curr_open) { # Deal with things left open 726 DEBUG and print "Stack is nonempty at end-document: (", 727 $self->_dump_curr_open(), ")\n"; 728 729 DEBUG > 9 and print "Stack: ", pretty($curr_open), "\n"; 730 unshift @$paras, $self->_closers_for_all_curr_open; 731 # Make sure there is exactly one ~end in the parastack, at the end: 732 @$paras = grep $_->[0] ne '~end', @$paras; 733 push @$paras, $para, $para; 734 # We need two -- once for the next cycle where we 735 # generate errata, and then another to be at the end 736 # when that loop back around to process the errata. 737 next; 738 739 } else { 740 DEBUG and print "Okay, stack is empty now.\n"; 741 } 742 743 # Try generating errata section, if applicable 744 unless($self->{'~tried_gen_errata'}) { 745 $self->{'~tried_gen_errata'} = 1; 746 my @extras = $self->_gen_errata(); 747 if(@extras) { 748 unshift @$paras, @extras; 749 DEBUG and print "Generated errata... relooping...\n"; 750 next; # I.e., loop around again to process these fake-o paragraphs 751 } 752 } 753 754 splice @$paras; # Well, that's that for this paragraph buffer. 755 DEBUG and print "Throwing end-document event.\n"; 756 757 $self->_handle_element_end( $scratch = 'Document' ); 758 next; # Hasta la byebye 537 } elsif($para_type eq '~end') { # The virtual end-document signal 538 next if $self->_ponder_doc_end($para,$curr_open,$paras); 759 539 } 760 540 761 541 … … 769 549 #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 770 550 # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 771 551 772 if($para_type eq '=pod') { #////////////////////////////////////////////// 773 $self->whine( 774 $para->[1]{'start_line'}, 775 "=pod directives shouldn't be over one line long! Ignoring all " 776 . (@$para - 2) . " lines of content" 777 ) if @$para > 3; 778 # Content is always ignored. 779 552 if($para_type eq '=pod') { 553 $self->_ponder_pod($para,$curr_open,$paras); 780 554 781 } elsif($para_type eq '=over') { #//////////////////////////////////////// 782 next unless @$paras; 783 my $list_type; 555 } elsif($para_type eq '=over') { 556 next if $self->_ponder_over($para,$curr_open,$paras); 784 557 785 if($paras->[0][0] eq '=item') { # most common case786 $list_type = $self->_get_initial_item_type($paras->[0]);558 } elsif($para_type eq '=back') { 559 next if $self->_ponder_back($para,$curr_open,$paras); 787 560 788 } elsif($paras->[0][0] eq '=back') { 789 # Ignore empty lists. TODO: make this an option? 790 shift @$paras; 791 next; 792 793 } elsif($paras->[0][0] eq '~end') { 794 $self->whine( 795 $para->[1]{'start_line'}, 796 "=over is the last thing in the document?!" 797 ); 798 next; # But feh, ignore it. 799 } else { 800 $list_type = 'block'; 801 } 802 $para->[1]{'~type'} = $list_type; 803 push @$curr_open, $para; 804 # yes, we reuse the paragraph as a stack item 805 806 my $content = join ' ', splice @$para, 2; 807 my $overness; 808 if($content =~ m/^\s*$/s) { 809 $para->[1]{'indent'} = 4; 810 } elsif($content =~ m/^\s*((?:\d*\.)?\d+)\s*$/s) { 811 no integer; 812 $para->[1]{'indent'} = $1; 813 if($1 == 0) { 814 $self->whine( 815 $para->[1]{'start_line'}, 816 "Can't have a 0 in =over $content" 817 ); 818 $para->[1]{'indent'} = 4; 819 } 820 } else { 821 $self->whine( 822 $para->[1]{'start_line'}, 823 "=over should be: '=over' or '=over positive_number'" 824 ); 825 $para->[1]{'indent'} = 4; 826 } 827 DEBUG > 1 and print "=over found of type $list_type\n"; 828 829 $self->{'content_seen'} ||= 1; 830 $self->_handle_element_start(($scratch = 'over-' . $list_type), $para->[1]); 831 832 } elsif($para_type eq '=back') { #//////////////////////////////////////// 561 } else { 833 562 834 # TODO: fire off </item-number> or </item-bullet> or </item-text> ??835 836 my $content = join ' ', splice @$para, 2;837 if($content =~ m/\S/) {838 $self->whine(839 $para->[1]{'start_line'},840 "=back doesn't take any parameters, but you said =back $content"841 );842 }843 844 if(@$curr_open and $curr_open->[-1][0] eq '=over') {845 DEBUG > 1 and print "=back happily closes matching =over\n";846 # Expected case: we're closing the most recently opened thing847 #my $over = pop @$curr_open;848 $self->{'content_seen'} ||= 1;849 $self->_handle_element_end( $scratch =850 'over-' . ( (pop @$curr_open)->[1]{'~type'} )851 );852 } else {853 DEBUG > 1 and print "=back found without a matching =over. Stack: (",854 join(', ', map $_->[0], @$curr_open), ").\n";855 $self->whine(856 $para->[1]{'start_line'},857 '=back without =over'858 );859 next; # and ignore it860 }861 862 } else { #////////////////////////////////////////////////////////////////863 563 # All non-magical codes!!! 864 564 865 565 # Here we start using $para_type for our own twisted purposes, to … … 1123 823 1124 824 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1125 825 if($para_type eq 'Plain') { 1126 DEBUG and print " giving plain treatment...\n"; 1127 unless( @$para == 2 or ( @$para == 3 and $para->[2] eq '' ) 1128 or $para->[1]{'~cooked'} 1129 ) { 1130 push @$para, 1131 @{$self->_make_treelet( 1132 join("\n", splice(@$para, 2)), 1133 $para->[1]{'start_line'} 1134 )}; 1135 } 1136 # Empty paragraphs don't need a treelet for any reason I can see. 1137 # And precooked paragraphs already have a treelet. 1138 826 $self->_ponder_Plain($para); 1139 827 } elsif($para_type eq 'Verbatim') { 1140 DEBUG and print " giving verbatim treatment...\n"; 1141 1142 $para->[1]{'xml:space'} = 'preserve'; 1143 for($i = 2; $i < @$para; $i++) { 1144 foreach my $line ($para->[$i]) { # just for aliasing 1145 while( $line =~ 1146 # Sort of adapted from Text::Tabs -- yes, it's hardwired in that 1147 # tabs are at every EIGHTH column. For portability, it has to be 1148 # one setting everywhere, and 8th wins. 1149 s/^([^\t]*)(\t+)/$1.(" " x ((length($2)<<3)-(length($1)&7)))/e 1150 ) {} 1151 1152 # TODO: whinge about (or otherwise treat) unindented or overlong lines 1153 1154 } 1155 } 1156 1157 # Now the VerbatimFormatted hoodoo... 1158 if( $self->{'accept_codes'} and 1159 $self->{'accept_codes'}{'VerbatimFormatted'} 1160 ) { 1161 while(@$para > 3 and $para->[-1] !~ m/\S/) { pop @$para } 1162 # Kill any number of terminal newlines 1163 $self->_verbatim_format($para); 1164 } else { 1165 push @$para, join "\n", splice(@$para, 2) if @$para > 3; 1166 $para->[-1] =~ s/\n+$//s; # Kill any number of terminal newlines 1167 } 1168 828 $self->_ponder_Verbatim($para); 1169 829 } elsif($para_type eq 'Data') { 1170 DEBUG and print " giving data treatment...\n"; 1171 $para->[1]{'xml:space'} = 'preserve'; 1172 push @$para, join "\n", splice(@$para, 2) if @$para > 3; 1173 830 $self->_ponder_Data($para); 1174 831 } else { 1175 832 die "\$para type is $para_type -- how did that happen?"; 1176 833 # Shouldn't happen. … … 1190 847 return; 1191 848 } 1192 849 850 ########################################################################### 851 # The sub-ponderers... 852 853 854 855 sub _ponder_for { 856 my ($self,$para,$curr_open,$paras) = @_; 857 858 # Fake it out as a begin/end 859 my $target; 860 861 if(grep $_->[1]{'~ignore'}, @$curr_open) { 862 DEBUG > 1 and print "Ignoring ignorable =for\n"; 863 return 1; 864 } 865 866 for(my $i = 2; $i < @$para; ++$i) { 867 if($para->[$i] =~ s/^\s*(\S+)\s*//s) { 868 $target = $1; 869 last; 870 } 871 } 872 unless(defined $target) { 873 $self->whine( 874 $para->[1]{'start_line'}, 875 "=for without a target?" 876 ); 877 return 1; 878 } 879 DEBUG > 1 and 880 print "Faking out a =for $target as a =begin $target / =end $target\n"; 881 882 $para->[0] = 'Data'; 883 884 unshift @$paras, 885 ['=begin', 886 {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, 887 $target, 888 ], 889 $para, 890 ['=end', 891 {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, 892 $target, 893 ], 894 ; 895 896 return 1; 897 } 898 899 sub _ponder_begin { 900 my ($self,$para,$curr_open,$paras) = @_; 901 my $content = join ' ', splice @$para, 2; 902 $content =~ s/^\s+//s; 903 $content =~ s/\s+$//s; 904 unless(length($content)) { 905 $self->whine( 906 $para->[1]{'start_line'}, 907 "=begin without a target?" 908 ); 909 DEBUG and print "Ignoring targetless =begin\n"; 910 return 1; 911 } 912 913 my ($target, $title) = $content =~ m/^(\S+)\s*(.*)$/; 914 $para->[1]{'title'} = $title if ($title); 915 $para->[1]{'target'} = $target; # without any ':' 916 $content = $target; # strip off the title 917 918 $content =~ s/^:!/!:/s; 919 my $neg; # whether this is a negation-match 920 $neg = 1 if $content =~ s/^!//s; 921 my $to_resolve; # whether to process formatting codes 922 $to_resolve = 1 if $content =~ s/^://s; 923 924 my $dont_ignore; # whether this target matches us 925 926 foreach my $target_name ( 927 split(',', $content, -1), 928 $neg ? () : '*' 929 ) { 930 DEBUG > 2 and 931 print " Considering whether =begin $content matches $target_name\n"; 932 next unless $self->{'accept_targets'}{$target_name}; 933 934 DEBUG > 2 and 935 print " It DOES match the acceptable target $target_name!\n"; 936 $to_resolve = 1 937 if $self->{'accept_targets'}{$target_name} eq 'force_resolve'; 938 $dont_ignore = 1; 939 $para->[1]{'target_matching'} = $target_name; 940 last; # stop looking at other target names 941 } 942 943 if($neg) { 944 if( $dont_ignore ) { 945 $dont_ignore = ''; 946 delete $para->[1]{'target_matching'}; 947 DEBUG > 2 and print " But the leading ! means that this is a NON-match!\n"; 948 } else { 949 $dont_ignore = 1; 950 $para->[1]{'target_matching'} = '!'; 951 DEBUG > 2 and print " But the leading ! means that this IS a match!\n"; 952 } 953 } 954 955 $para->[0] = '=for'; # Just what we happen to call these, internally 956 $para->[1]{'~really'} ||= '=begin'; 957 $para->[1]{'~ignore'} = (! $dont_ignore) || 0; 958 $para->[1]{'~resolve'} = $to_resolve || 0; 959 960 DEBUG > 1 and print " Making note to ", $dont_ignore ? 'not ' : '', 961 "ignore contents of this region\n"; 962 DEBUG > 1 and $dont_ignore and print " Making note to treat contents as ", 963 ($to_resolve ? 'verbatim/plain' : 'data'), " paragraphs\n"; 964 DEBUG > 1 and print " (Stack now: ", $self->_dump_curr_open(), ")\n"; 965 966 push @$curr_open, $para; 967 if(!$dont_ignore or scalar grep $_->[1]{'~ignore'}, @$curr_open) { 968 DEBUG > 1 and print "Ignoring ignorable =begin\n"; 969 } else { 970 $self->{'content_seen'} ||= 1; 971 $self->_handle_element_start((my $scratch='for'), $para->[1]); 972 } 973 974 return 1; 975 } 976 977 sub _ponder_end { 978 my ($self,$para,$curr_open,$paras) = @_; 979 my $content = join ' ', splice @$para, 2; 980 $content =~ s/^\s+//s; 981 $content =~ s/\s+$//s; 982 DEBUG and print "Ogling '=end $content' directive\n"; 983 984 unless(length($content)) { 985 $self->whine( 986 $para->[1]{'start_line'}, 987 "'=end' without a target?" . ( 988 ( @$curr_open and $curr_open->[-1][0] eq '=for' ) 989 ? ( " (Should be \"=end " . $curr_open->[-1][1]{'target'} . '")' ) 990 : '' 991 ) 992 ); 993 DEBUG and print "Ignoring targetless =end\n"; 994 return 1; 995 } 996 997 unless($content =~ m/^\S+$/) { # i.e., unless it's one word 998 $self->whine( 999 $para->[1]{'start_line'}, 1000 "'=end $content' is invalid. (Stack: " 1001 . $self->_dump_curr_open() . ')' 1002 ); 1003 DEBUG and print "Ignoring mistargetted =end $content\n"; 1004 return 1; 1005 } 1006 1007 unless(@$curr_open and $curr_open->[-1][0] eq '=for') { 1008 $self->whine( 1009 $para->[1]{'start_line'}, 1010 "=end $content without matching =begin. (Stack: " 1011 . $self->_dump_curr_open() . ')' 1012 ); 1013 DEBUG and print "Ignoring mistargetted =end $content\n"; 1014 return 1; 1015 } 1016 1017 unless($content eq $curr_open->[-1][1]{'target'}) { 1018 $self->whine( 1019 $para->[1]{'start_line'}, 1020 "=end $content doesn't match =begin " 1021 . $curr_open->[-1][1]{'target'} 1022 . ". (Stack: " 1023 . $self->_dump_curr_open() . ')' 1024 ); 1025 DEBUG and print "Ignoring mistargetted =end $content at line $para->[1]{'start_line'}\n"; 1026 return 1; 1027 } 1028 1029 # Else it's okay to close... 1030 if(grep $_->[1]{'~ignore'}, @$curr_open) { 1031 DEBUG > 1 and print "Not firing any event for this =end $content because in an ignored region\n"; 1032 # And that may be because of this to-be-closed =for region, or some 1033 # other one, but it doesn't matter. 1034 } else { 1035 $curr_open->[-1][1]{'start_line'} = $para->[1]{'start_line'}; 1036 # what's that for? 1037 1038 $self->{'content_seen'} ||= 1; 1039 $self->_handle_element_end( my $scratch = 'for' ); 1040 } 1041 DEBUG > 1 and print "Popping $curr_open->[-1][0] $curr_open->[-1][1]{'target'} because of =end $content\n"; 1042 pop @$curr_open; 1043 1044 return 1; 1045 } 1046 1047 sub _ponder_doc_end { 1048 my ($self,$para,$curr_open,$paras) = @_; 1049 if(@$curr_open) { # Deal with things left open 1050 DEBUG and print "Stack is nonempty at end-document: (", 1051 $self->_dump_curr_open(), ")\n"; 1052 1053 DEBUG > 9 and print "Stack: ", pretty($curr_open), "\n"; 1054 unshift @$paras, $self->_closers_for_all_curr_open; 1055 # Make sure there is exactly one ~end in the parastack, at the end: 1056 @$paras = grep $_->[0] ne '~end', @$paras; 1057 push @$paras, $para, $para; 1058 # We need two -- once for the next cycle where we 1059 # generate errata, and then another to be at the end 1060 # when that loop back around to process the errata. 1061 return 1; 1062 1063 } else { 1064 DEBUG and print "Okay, stack is empty now.\n"; 1065 } 1066 1067 # Try generating errata section, if applicable 1068 unless($self->{'~tried_gen_errata'}) { 1069 $self->{'~tried_gen_errata'} = 1; 1070 my @extras = $self->_gen_errata(); 1071 if(@extras) { 1072 unshift @$paras, @extras; 1073 DEBUG and print "Generated errata... relooping...\n"; 1074 return 1; # I.e., loop around again to process these fake-o paragraphs 1075 } 1076 } 1077 1078 splice @$paras; # Well, that's that for this paragraph buffer. 1079 DEBUG and print "Throwing end-document event.\n"; 1080 1081 $self->_handle_element_end( my $scratch = 'Document' ); 1082 return 1; # Hasta la byebye 1083 } 1084 1085 sub _ponder_pod { 1086 my ($self,$para,$curr_open,$paras) = @_; 1087 $self->whine( 1088 $para->[1]{'start_line'}, 1089 "=pod directives shouldn't be over one line long! Ignoring all " 1090 . (@$para - 2) . " lines of content" 1091 ) if @$para > 3; 1092 # Content is always ignored. 1093 return; 1094 } 1095 1096 sub _ponder_over { 1097 my ($self,$para,$curr_open,$paras) = @_; 1098 return 1 unless @$paras; 1099 my $list_type; 1100 1101 if($paras->[0][0] eq '=item') { # most common case 1102 $list_type = $self->_get_initial_item_type($paras->[0]); 1103 1104 } elsif($paras->[0][0] eq '=back') { 1105 # Ignore empty lists. TODO: make this an option? 1106 shift @$paras; 1107 return 1; 1108 1109 } elsif($paras->[0][0] eq '~end') { 1110 $self->whine( 1111 $para->[1]{'start_line'}, 1112 "=over is the last thing in the document?!" 1113 ); 1114 return 1; # But feh, ignore it. 1115 } else { 1116 $list_type = 'block'; 1117 } 1118 $para->[1]{'~type'} = $list_type; 1119 push @$curr_open, $para; 1120 # yes, we reuse the paragraph as a stack item 1121 1122 my $content = join ' ', splice @$para, 2; 1123 my $overness; 1124 if($content =~ m/^\s*$/s) { 1125 $para->[1]{'indent'} = 4; 1126 } elsif($content =~ m/^\s*((?:\d*\.)?\d+)\s*$/s) { 1127 no integer; 1128 $para->[1]{'indent'} = $1; 1129 if($1 == 0) { 1130 $self->whine( 1131 $para->[1]{'start_line'}, 1132 "Can't have a 0 in =over $content" 1133 ); 1134 $para->[1]{'indent'} = 4; 1135 } 1136 } else { 1137 $self->whine( 1138 $para->[1]{'start_line'}, 1139 "=over should be: '=over' or '=over positive_number'" 1140 ); 1141 $para->[1]{'indent'} = 4; 1142 } 1143 DEBUG > 1 and print "=over found of type $list_type\n"; 1144 1145 $self->{'content_seen'} ||= 1; 1146 $self->_handle_element_start((my $scratch = 'over-' . $list_type), $para->[1]); 1147 1148 return; 1149 } 1150 1151 sub _ponder_back { 1152 my ($self,$para,$curr_open,$paras) = @_; 1153 # TODO: fire off </item-number> or </item-bullet> or </item-text> ?? 1154 1155 my $content = join ' ', splice @$para, 2; 1156 if($content =~ m/\S/) { 1157 $self->whine( 1158 $para->[1]{'start_line'}, 1159 "=back doesn't take any parameters, but you said =back $content" 1160 ); 1161 } 1162 1163 if(@$curr_open and $curr_open->[-1][0] eq '=over') { 1164 DEBUG > 1 and print "=back happily closes matching =over\n"; 1165 # Expected case: we're closing the most recently opened thing 1166 #my $over = pop @$curr_open; 1167 $self->{'content_seen'} ||= 1; 1168 $self->_handle_element_end( my $scratch = 1169 'over-' . ( (pop @$curr_open)->[1]{'~type'} ) 1170 ); 1171 } else { 1172 DEBUG > 1 and print "=back found without a matching =over. Stack: (", 1173 join(', ', map $_->[0], @$curr_open), ").\n"; 1174 $self->whine( 1175 $para->[1]{'start_line'}, 1176 '=back without =over' 1177 ); 1178 return 1; # and ignore it 1179 } 1180 } 1181 1182 sub _ponder_item { 1183 my ($self,$para,$curr_open,$paras) = @_; 1184 my $over; 1185 unless(@$curr_open and ($over = $curr_open->[-1])->[0] eq '=over') { 1186 $self->whine( 1187 $para->[1]{'start_line'}, 1188 "'=item' outside of any '=over'" 1189 ); 1190 unshift @$paras, 1191 ['=over', {'start_line' => $para->[1]{'start_line'}}, ''], 1192 $para 1193 ; 1194 return 1; 1195 } 1196 1197 1198 my $over_type = $over->[1]{'~type'}; 1199 1200 if(!$over_type) { 1201 # Shouldn't happen1 1202 die "Typeless over in stack, starting at line " 1203 . $over->[1]{'start_line'}; 1204 1205 } elsif($over_type eq 'block') { 1206 unless($curr_open->[-1][1]{'~bitched_about'}) { 1207 $curr_open->[-1][1]{'~bitched_about'} = 1; 1208 $self->whine( 1209 $curr_open->[-1][1]{'start_line'}, 1210 "You can't have =items (as at line " 1211 . $para->[1]{'start_line'} 1212 . ") unless the first thing after the =over is an =item" 1213 ); 1214 } 1215 # Just turn it into a paragraph and reconsider it 1216 $para->[0] = '~Para'; 1217 unshift @$paras, $para; 1218 return 1; 1219 1220 } elsif($over_type eq 'text') { 1221 my $item_type = $self->_get_item_type($para); 1222 # That kills the content of the item if it's a number or bullet. 1223 DEBUG and print " Item is of type ", $para->[0], " under $over_type\n"; 1224 1225 if($item_type eq 'text') { 1226 # Nothing special needs doing for 'text' 1227 } elsif($item_type eq 'number' or $item_type eq 'bullet') { 1228 die "Unknown item type $item_type" 1229 unless $item_type eq 'number' or $item_type eq 'bullet'; 1230 # Undo our clobbering: 1231 push @$para, $para->[1]{'~orig_content'}; 1232 delete $para->[1]{'number'}; 1233 # Only a PROPER item-number element is allowed 1234 # to have a number attribute. 1235 } else { 1236 die "Unhandled item type $item_type"; # should never happen 1237 } 1238 1239 # =item-text thingies don't need any assimilation, it seems. 1240 1241 } elsif($over_type eq 'number') { 1242 my $item_type = $self->_get_item_type($para); 1243 # That kills the content of the item if it's a number or bullet. 1244 DEBUG and print " Item is of type ", $para->[0], " under $over_type\n"; 1245 1246 my $expected_value = ++ $curr_open->[-1][1]{'~counter'}; 1247 1248 if($item_type eq 'bullet') { 1249 # Hm, it's not numeric. Correct for this. 1250 $para->[1]{'number'} = $expected_value; 1251 $self->whine( 1252 $para->[1]{'start_line'}, 1253 "Expected '=item $expected_value'" 1254 ); 1255 push @$para, $para->[1]{'~orig_content'}; 1256 # restore the bullet, blocking the assimilation of next para 1257 1258 } elsif($item_type eq 'text') { 1259 # Hm, it's not numeric. Correct for this. 1260 $para->[1]{'number'} = $expected_value; 1261 $self->whine( 1262 $para->[1]{'start_line'}, 1263 "Expected '=item $expected_value'" 1264 ); 1265 # Text content will still be there and will block next ~Para 1266 1267 } elsif($item_type ne 'number') { 1268 die "Unknown item type $item_type"; # should never happen 1269 1270 } elsif($expected_value == $para->[1]{'number'}) { 1271 DEBUG > 1 and print " Numeric item has the expected value of $expected_value\n"; 1272 1273 } else { 1274 DEBUG > 1 and print " Numeric item has ", $para->[1]{'number'}, 1275 " instead of the expected value of $expected_value\n"; 1276 $self->whine( 1277 $para->[1]{'start_line'}, 1278 "You have '=item " . $para->[1]{'number'} . 1279 "' instead of the expected '=item $expected_value'" 1280 ); 1281 $para->[1]{'number'} = $expected_value; # correcting!! 1282 } 1283 1284 if(@$para == 2) { 1285 # For the cases where we /didn't/ push to @$para 1286 if($paras->[0][0] eq '~Para') { 1287 DEBUG and print "Assimilating following ~Para content into $over_type item\n"; 1288 push @$para, splice @{shift @$paras},2; 1289 } else { 1290 DEBUG and print "Can't assimilate following ", $paras->[0][0], "\n"; 1291 push @$para, ''; # Just so it's not contentless 1292 } 1293 } 1294 1295 1296 } elsif($over_type eq 'bullet') { 1297 my $item_type = $self->_get_item_type($para); 1298 # That kills the content of the item if it's a number or bullet. 1299 DEBUG and print " Item is of type ", $para->[0], " under $over_type\n"; 1300 1301 if($item_type eq 'bullet') { 1302 # as expected! 1303 1304 if( $para->[1]{'~_freaky_para_hack'} ) { 1305 DEBUG and print "Accomodating '=item * Foo' tolerance hack.\n"; 1306 push @$para, delete $para->[1]{'~_freaky_para_hack'}; 1307 } 1308 1309 } elsif($item_type eq 'number') { 1310 $self->whine( 1311 $para->[1]{'start_line'}, 1312 "Expected '=item *'" 1313 ); 1314 push @$para, $para->[1]{'~orig_content'}; 1315 # and block assimilation of the next paragraph 1316 delete $para->[1]{'number'}; 1317 # Only a PROPER item-number element is allowed 1318 # to have a number attribute. 1319 } elsif($item_type eq 'text') { 1320 $self->whine( 1321 $para->[1]{'start_line'}, 1322 "Expected '=item *'" 1323 ); 1324 # But doesn't need processing. But it'll block assimilation 1325 # of the next para. 1326 } else { 1327 die "Unhandled item type $item_type"; # should never happen 1328 } 1329 1330 if(@$para == 2) { 1331 # For the cases where we /didn't/ push to @$para 1332 if($paras->[0][0] eq '~Para') { 1333 DEBUG and print "Assimilating following ~Para content into $over_type item\n"; 1334 push @$para, splice @{shift @$paras},2; 1335 } else { 1336 DEBUG and print "Can't assimilate following ", $paras->[0][0], "\n"; 1337 push @$para, ''; # Just so it's not contentless 1338 } 1339 } 1340 1341 } else { 1342 die "Unhandled =over type \"$over_type\"?"; 1343 # Shouldn't happen! 1344 } 1345 $para->[0] .= '-' . $over_type; 1346 1347 return; 1348 } 1349 1350 sub _ponder_Plain { 1351 my ($self,$para) = @_; 1352 DEBUG and print " giving plain treatment...\n"; 1353 unless( @$para == 2 or ( @$para == 3 and $para->[2] eq '' ) 1354 or $para->[1]{'~cooked'} 1355 ) { 1356 push @$para, 1357 @{$self->_make_treelet( 1358 join("\n", splice(@$para, 2)), 1359 $para->[1]{'start_line'} 1360 )}; 1361 } 1362 # Empty paragraphs don't need a treelet for any reason I can see. 1363 # And precooked paragraphs already have a treelet. 1364 return; 1365 } 1366 1367 sub _ponder_Verbatim { 1368 my ($self,$para) = @_; 1369 DEBUG and print " giving verbatim treatment...\n"; 1370 1371 $para->[1]{'xml:space'} = 'preserve'; 1372 for(my $i = 2; $i < @$para; $i++) { 1373 foreach my $line ($para->[$i]) { # just for aliasing 1374 while( $line =~ 1375 # Sort of adapted from Text::Tabs -- yes, it's hardwired in that 1376 # tabs are at every EIGHTH column. For portability, it has to be 1377 # one setting everywhere, and 8th wins. 1378 s/^([^\t]*)(\t+)/$1.(" " x ((length($2)<<3)-(length($1)&7)))/e 1379 ) {} 1380 1381 # TODO: whinge about (or otherwise treat) unindented or overlong lines 1382 1383 } 1384 } 1385 1386 # Now the VerbatimFormatted hoodoo... 1387 if( $self->{'accept_codes'} and 1388 $self->{'accept_codes'}{'VerbatimFormatted'} 1389 ) { 1390 while(@$para > 3 and $para->[-1] !~ m/\S/) { pop @$para } 1391 # Kill any number of terminal newlines 1392 $self->_verbatim_format($para); 1393 } elsif ($self->{'codes_in_verbatim'}) { 1394 push @$para, 1395 @{$self->_make_treelet( 1396 join("\n", splice(@$para, 2)), 1397 $para->[1]{'start_line'}, $para->[1]{'xml:space'} 1398 )}; 1399 $para->[-1] =~ s/\n+$//s; # Kill any number of terminal newlines 1400 } else { 1401 push @$para, join "\n", splice(@$para, 2) if @$para > 3; 1402 $para->[-1] =~ s/\n+$//s; # Kill any number of terminal newlines 1403 } 1404 return; 1405 } 1406 1407 sub _ponder_Data { 1408 my ($self,$para) = @_; 1409 DEBUG and print " giving data treatment...\n"; 1410 $para->[1]{'xml:space'} = 'preserve'; 1411 push @$para, join "\n", splice(@$para, 2) if @$para > 3; 1412 return; 1413 } 1414 1415 1416 1417 1418 ########################################################################### 1419 1193 1420 sub _traverse_treelet_bit { # for use only by the routine above 1194 1421 my($self, $name) = splice @_,0,2; 1195 1422 … … 1382 1609 # "!" 1383 1610 # ] 1384 1611 1385 my($self, $para, $start_line) = @_; 1612 my($self, $para, $start_line, $preserve_space) = @_; 1613 1386 1614 my $treelet = ['~Top', {'start_line' => $start_line},]; 1387 1615 1388 $para =~ s/\s+/ /g; # collapse and trim all whitespace first. 1389 $para =~ s/ $//g; 1390 $para =~ s/^ //g; 1616 unless ($preserve_space || $self->{'preserve_whitespace'}) { 1617 $para =~ s/\. /\.\xA0 /g if $self->{'fullstop_space_harden'}; 1391 1618 1619 $para =~ s/\s+/ /g; # collapse and trim all whitespace first. 1620 $para =~ s/ $//; 1621 $para =~ s/^ //; 1622 } 1623 1392 1624 # Only apparent problem the above code is that N<< >> turns into 1393 1625 # N<< >>. But then, word wrapping does that too! So don't do that! 1394 1626 … … 1396 1628 my @lineage = ($treelet); 1397 1629 1398 1630 DEBUG > 4 and print "Paragraph:\n$para\n\n"; 1399 1400 while($para =~ # Here begins our frightening tokenizer RE. 1631 1632 # Here begins our frightening tokenizer RE. The following regex matches 1633 # text in four main parts: 1634 # 1635 # * Start-codes. The first alternative matches C< or C<<, the latter 1636 # followed by some whitespace. $1 will hold the entire start code 1637 # (including any space following a multiple-angle-bracket delimiter), 1638 # and $2 will hold only the additional brackets past the first in a 1639 # multiple-bracket delimiter. length($2) + 1 will be the number of 1640 # closing brackets we have to find. 1641 # 1642 # * Closing brackets. Match some amount of whitespace followed by 1643 # multiple close brackets. The logic to see if this closes anything 1644 # is down below. Note that in order to parse C<< >> correctly, we 1645 # have to use look-behind (?<=\s\s), since the match of the starting 1646 # code will have consumed the whitespace. 1647 # 1648 # * A single closing bracket, to close a simple code like C<>. 1649 # 1650 # * Something that isn't a start or end code. We have to be careful 1651 # about accepting whitespace, since perlpodspec says that any whitespace 1652 # before a multiple-bracket closing delimiter should be ignored. 1653 # 1654 while($para =~ 1401 1655 m/\G 1402 1656 (?: 1403 ([A-Z]<(<+\ )?) # that's $1 and $2 for both kinds of start-codes 1657 # Match starting codes, including the whitespace following a 1658 # multiple-delimiter start code. $1 gets the whole start code and 1659 # $2 gets all but one of the <s in the multiple-bracket case. 1660 ([A-Z]<(?:(<+)\s+)?) 1404 1661 | 1405 (\ >{2,}) # $3: end-codes of the type " >>", " >>>", etc. 1662 # Match multiple-bracket end codes. $3 gets the whitespace that 1663 # should be discarded before an end bracket but kept in other cases 1664 # and $4 gets the end brackets themselves. 1665 (\s+|(?<=\s\s))(>{2,}) 1406 1666 | 1407 (\ ?>) # $4: simple end-codes1667 (\s?>) # $5: simple end-codes 1408 1668 | 1409 ( # $ 5: stuff containing no start-codes or end-codes1669 ( # $6: stuff containing no start-codes or end-codes 1410 1670 (?: 1411 [^A-Z\ >]+1671 [^A-Z\s>] 1412 1672 | 1413 1673 (?: 1414 1674 [A-Z](?!<) 1415 1675 ) 1416 1676 | 1677 # whitespace is ok, but we don't want to eat the whitespace before 1678 # a multiple-bracket end code. 1679 # NOTE: we may still have problems with e.g. S<< >> 1417 1680 (?: 1418 \ (?!>)1681 \s(?!\s*>{2,}) 1419 1682 ) 1420 1683 )+ 1421 1684 ) … … 1426 1689 if(defined $1) { 1427 1690 if(defined $2) { 1428 1691 DEBUG > 3 and print "Found complex start-text code \"$1\"\n"; 1429 push @stack, length($ 1) -1;1692 push @stack, length($2) + 1; 1430 1693 # length of the necessary complex end-code string 1431 1694 } else { 1432 1695 DEBUG > 3 and print "Found simple start-text code \"$1\"\n"; … … 1435 1698 push @lineage, [ substr($1,0,1), {}, ]; # new node object 1436 1699 push @{ $lineage[-2] }, $lineage[-1]; 1437 1700 1438 } elsif(defined $ 3) {1439 DEBUG > 3 and print "Found apparent complex end-text code \"$3 \"\n";1701 } elsif(defined $4) { 1702 DEBUG > 3 and print "Found apparent complex end-text code \"$3$4\"\n"; 1440 1703 # This is where it gets messy... 1441 1704 if(! @stack) { 1442 1705 # We saw " >>>>" but needed nothing. This is ALL just stuff then. 1443 1706 DEBUG > 4 and print " But it's really just stuff.\n"; 1444 push @{ $lineage[-1] }, $3 ;1707 push @{ $lineage[-1] }, $3, $4; 1445 1708 next; 1446 1709 } elsif(!$stack[-1]) { 1447 1710 # We saw " >>>>" but needed only ">". Back pos up. 1448 1711 DEBUG > 4 and print " And that's more than we needed to close simple.\n"; 1449 push @{ $lineage[-1] }, ' '; # That was a for-real space, too.1450 pos($para) = pos($para) - length($ 3) + 2;1451 } elsif($stack[-1] == length($ 3)) {1712 push @{ $lineage[-1] }, $3; # That was a for-real space, too. 1713 pos($para) = pos($para) - length($4) + 1; 1714 } elsif($stack[-1] == length($4)) { 1452 1715 # We found " >>>>", and it was exactly what we needed. Commonest case. 1453 1716 DEBUG > 4 and print " And that's exactly what we needed to close complex.\n"; 1454 } elsif($stack[-1] < length($ 3)) {1717 } elsif($stack[-1] < length($4)) { 1455 1718 # We saw " >>>>" but needed only " >>". Back pos up. 1456 1719 DEBUG > 4 and print " And that's more than we needed to close complex.\n"; 1457 pos($para) = pos($para) - length($ 3) + $stack[-1];1720 pos($para) = pos($para) - length($4) + $stack[-1]; 1458 1721 } else { 1459 1722 # We saw " >>>>" but needed " >>>>>>". So this is all just stuff! 1460 1723 DEBUG > 4 and print " But it's really just stuff, because we needed more.\n"; 1461 push @{ $lineage[-1] }, $3 ;1724 push @{ $lineage[-1] }, $3, $4; 1462 1725 next; 1463 1726 } 1464 1727 #print "\nHOOBOY ", scalar(@{$lineage[-1]}), "!!!\n"; 1465 1728 1466 1729 push @{ $lineage[-1] }, '' if 2 == @{ $lineage[-1] }; 1467 1730 # Keep the element from being childless 1468 1731 1469 1732 pop @stack; 1470 1733 pop @lineage; 1471 1734 1472 } elsif(defined $ 4) {1735 } elsif(defined $5) { 1473 1736 DEBUG > 3 and print "Found apparent simple end-text code \"$4\"\n"; 1474 1737 1475 1738 if(@stack and ! $stack[-1]) { 1476 1739 # We're indeed expecting a simple end-code 1477 1740 DEBUG > 4 and print " It's indeed an end-code.\n"; 1478 1741 1479 if(length($ 4) == 2) { # There was a space there: " >"1742 if(length($5) == 2) { # There was a space there: " >" 1480 1743 push @{ $lineage[-1] }, ' '; 1481 1744 } elsif( 2 == @{ $lineage[-1] } ) { # Closing a childless element 1482 1745 push @{ $lineage[-1] }, ''; # keep it from being really childless … … 1486 1749 pop @lineage; 1487 1750 } else { 1488 1751 DEBUG > 4 and print " It's just stuff.\n"; 1489 push @{ $lineage[-1] }, $ 4;1752 push @{ $lineage[-1] }, $5; 1490 1753 } 1491 1754 1492 } elsif(defined $ 5) {1493 DEBUG > 3 and print "Found stuff \"$ 5\"\n";1494 push @{ $lineage[-1] }, $ 5;1755 } elsif(defined $6) { 1756 DEBUG > 3 and print "Found stuff \"$6\"\n"; 1757 push @{ $lineage[-1] }, $6; 1495 1758 1496 1759 } else { 1497 1760 # should never ever ever ever happen … … 1634 1897 } 1635 1898 1636 1899 #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1900 1901 # A rather unsubtle method of blowing away all the state information 1902 # from a parser object so it can be reused. Provided as a utility for 1903 # backward compatibilty in Pod::Man, etc. but not recommended for 1904 # general use. 1905 1906 sub reinit { 1907 my $self = shift; 1908 foreach (qw(source_dead source_filename doc_has_started 1909 start_of_pod_block content_seen last_was_blank paras curr_open 1910 line_count pod_para_count in_pod ~tried_gen_errata errata errors_seen 1911 Title)) { 1912 1913 delete $self->{$_}; 1914 } 1915 } 1916 1917 #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1637 1918 1; 1638 1919 -
lib/Pod/Escapes.pm
1 1 2 2 require 5; 3 3 # The documentation is at the end. 4 # Time-stamp: "200 2-08-27 19:58:02 MDT"4 # Time-stamp: "2004-05-07 15:31:25 ADT" 5 5 package Pod::Escapes; 6 6 require Exporter; 7 7 @ISA = ('Exporter'); 8 $VERSION = '1.0 3';8 $VERSION = '1.04'; 9 9 @EXPORT_OK = qw( 10 10 %Code2USASCII 11 11 %Name2character … … 44 44 # Convert to decimal: 45 45 if($in =~ m/^(0[0-7]*)$/s ) { 46 46 $in = oct $in; 47 } elsif($in =~ m/^0 x([0-9a-fA-F]+)$/s ) {47 } elsif($in =~ m/^0?x([0-9a-fA-F]+)$/s ) { 48 48 $in = hex $1; 49 49 } # else it's decimal, or named 50 50 … … 86 86 # Convert to decimal: 87 87 if($in =~ m/^(0[0-7]*)$/s ) { 88 88 $in = oct $in; 89 } elsif($in =~ m/^0 x([0-9a-fA-F]+)$/s ) {89 } elsif($in =~ m/^0?x([0-9a-fA-F]+)$/s ) { 90 90 $in = hex $1; 91 91 } # else it's decimal, or named 92 92 … … 649 649 650 650 =head1 COPYRIGHT AND DISCLAIMERS 651 651 652 Copyright (c) 2001 Sean M. Burke. All rights reserved.652 Copyright (c) 2001-2004 Sean M. Burke. All rights reserved. 653 653 654 654 This library is free software; you can redistribute it and/or modify 655 655 it under the same terms as Perl itself. … … 685 685 xhtml-lat1.ent 686 686 xhtml-special.ent 687 687 )) { 688 open(IN, "< $dir$file") or die "can't read-open $dir$file: $!";688 open(IN, "<", "$dir$file") or die "can't read-open $dir$file: $!"; 689 689 print "Reading $file...\n"; 690 690 while(<IN>) { 691 691 if(m/<!ENTITY\s+(\S+)\s+"&#([^;]+);">/) { -
(a) /dev/null vs. (b) lib/Pod/Simple/Progress.pm
a b 1 2 require 5; 3 package Pod::Simple::Progress; 4 $VERSION = "1.01"; 5 use strict; 6 7 # Objects of this class are used for noting progress of an 8 # operation every so often. Messages delivered more often than that 9 # are suppressed. 10 # 11 # There's actually nothing in here that's specific to Pod processing; 12 # but it's ad-hoc enough that I'm not willing to give it a name that 13 # implies that it's generally useful, like "IO::Progress" or something. 14 # 15 # -- sburke 16 # 17 #-------------------------------------------------------------------------- 18 19 sub new { 20 my($class,$delay) = @_; 21 my $self = bless {'quiet_until' => 1}, ref($class) || $class; 22 $self->to(*STDOUT{IO}); 23 $self->delay(defined($delay) ? $delay : 5); 24 return $self; 25 } 26 27 sub copy { 28 my $orig = shift; 29 bless {%$orig, 'quiet_until' => 1}, ref($orig); 30 } 31 #-------------------------------------------------------------------------- 32 33 sub reach { 34 my($self, $point, $note) = @_; 35 if( (my $now = time) >= $self->{'quiet_until'}) { 36 my $goal; 37 my $to = $self->{'to'}; 38 print $to join('', 39 ($self->{'quiet_until'} == 1) ? () : '... ', 40 (defined $point) ? ( 41 '#', 42 ($goal = $self->{'goal'}) ? ( 43 ' ' x (length($goal) - length($point)), 44 $point, '/', $goal, 45 ) : $point, 46 $note ? ': ' : (), 47 ) : (), 48 $note || '', 49 "\n" 50 ); 51 $self->{'quiet_until'} = $now + $self->{'delay'}; 52 } 53 return $self; 54 } 55 56 #-------------------------------------------------------------------------- 57 58 sub done { 59 my($self, $note) = @_; 60 $self->{'quiet_until'} = 1; 61 return $self->reach( undef, $note ); 62 } 63 64 #-------------------------------------------------------------------------- 65 # Simple accessors: 66 67 sub delay { 68 return $_[0]{'delay'} if @_ == 1; $_[0]{'delay'} = $_[1]; return $_[0] } 69 sub goal { 70 return $_[0]{'goal' } if @_ == 1; $_[0]{'goal' } = $_[1]; return $_[0] } 71 sub to { 72 return $_[0]{'to' } if @_ == 1; $_[0]{'to' } = $_[1]; return $_[0] } 73 74 #-------------------------------------------------------------------------- 75 76 unless(caller) { # Simple self-test: 77 my $p = __PACKAGE__->new->goal(5); 78 $p->reach(1, "Primus!"); 79 sleep 1; 80 $p->reach(2, "Secundus!"); 81 sleep 3; 82 $p->reach(3, "Tertius!"); 83 sleep 5; 84 $p->reach(4); 85 $p->reach(5, "Quintus!"); 86 sleep 1; 87 $p->done("All done"); 88 } 89 90 #-------------------------------------------------------------------------- 91 1; 92 __END__ 93 -
(a) /dev/null vs. (b) lib/Pod/Simple/Search.pm
a b 1 2 require 5.005; 3 package Pod::Simple::Search; 4 use strict; 5 6 use vars qw($VERSION $MAX_VERSION_WITHIN $SLEEPY); 7 $VERSION = 3.04; ## Current version of this package 8 9 BEGIN { *DEBUG = sub () {0} unless defined &DEBUG; } # set DEBUG level 10 use Carp (); 11 12 $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i; 13 # flag to occasionally sleep for $SLEEPY - 1 seconds. 14 15 $MAX_VERSION_WITHIN ||= 60; 16 17 ############################################################################# 18 19 #use diagnostics; 20 use File::Spec (); 21 use File::Basename qw( basename ); 22 use Config (); 23 use Cwd qw( cwd ); 24 25 #========================================================================== 26 __PACKAGE__->_accessorize( # Make my dumb accessor methods 27 'callback', 'progress', 'dir_prefix', 'inc', 'laborious', 'limit_glob', 28 'limit_re', 'shadows', 'verbose', 'name2path', 'path2name', 29 ); 30 #========================================================================== 31 32 sub new { 33 my $class = shift; 34 my $self = bless {}, ref($class) || $class; 35 $self->init; 36 return $self; 37 } 38 39 sub init { 40 my $self = shift; 41 $self->inc(1); 42 $self->verbose(DEBUG); 43 return $self; 44 } 45 46 #-------------------------------------------------------------------------- 47 48 sub survey { 49 my($self, @search_dirs) = @_; 50 $self = $self->new unless ref $self; # tolerate being a class method 51 52 $self->_expand_inc( \@search_dirs ); 53 54 55 $self->{'_scan_count'} = 0; 56 $self->{'_dirs_visited'} = {}; 57 $self->path2name( {} ); 58 $self->name2path( {} ); 59 $self->limit_re( $self->_limit_glob_to_limit_re ) if $self->{'limit_glob'}; 60 my $cwd = cwd(); 61 my $verbose = $self->verbose; 62 local $_; # don't clobber the caller's $_ ! 63 64 foreach my $try (@search_dirs) { 65 unless( File::Spec->file_name_is_absolute($try) ) { 66 # make path absolute 67 $try = File::Spec->catfile( $cwd ,$try); 68 } 69 # simplify path 70 $try = File::Spec->canonpath($try); 71 72 my $start_in; 73 my $modname_prefix; 74 if($self->{'dir_prefix'}) { 75 $start_in = File::Spec->catdir( 76 $try, 77 grep length($_), split '[\\/:]+', $self->{'dir_prefix'} 78 ); 79 $modname_prefix = [grep length($_), split m{[:/\\]}, $self->{'dir_prefix'}]; 80 $verbose and print "Appending \"$self->{'dir_prefix'}\" to $try, ", 81 "giving $start_in (= @$modname_prefix)\n"; 82 } else { 83 $start_in = $try; 84 } 85 86 if( $self->{'_dirs_visited'}{$start_in} ) { 87 $verbose and print "Directory '$start_in' already seen, skipping.\n"; 88 next; 89 } else { 90 $self->{'_dirs_visited'}{$start_in} = 1; 91 } 92 93 unless(-e $start_in) { 94 $verbose and print "Skipping non-existent $start_in\n"; 95 next; 96 } 97 98 my $closure = $self->_make_search_callback; 99 100 if(-d $start_in) { 101 # Normal case: 102 $verbose and print "Beginning excursion under $start_in\n"; 103 $self->_recurse_dir( $start_in, $closure, $modname_prefix ); 104 $verbose and print "Back from excursion under $start_in\n\n"; 105 106 } elsif(-f _) { 107 # A excursion consisting of just one file! 108 $_ = basename($start_in); 109 $verbose and print "Pondering $start_in ($_)\n"; 110 $closure->($start_in, $_, 0, []); 111 112 } else { 113 $verbose and print "Skipping mysterious $start_in\n"; 114 } 115 } 116 $self->progress and $self->progress->done( 117 "Noted $$self{'_scan_count'} Pod files total"); 118 119 return unless defined wantarray; # void 120 return $self->name2path unless wantarray; # scalar 121 return $self->name2path, $self->path2name; # list 122 } 123 124 125 #========================================================================== 126 sub _make_search_callback { 127 my $self = $_[0]; 128 129 # Put the options in variables, for easy access 130 my( $laborious, $verbose, $shadows, $limit_re, $callback, $progress,$path2name,$name2path) = 131 map scalar($self->$_()), 132 qw(laborious verbose shadows limit_re callback progress path2name name2path); 133 134 my($file, $shortname, $isdir, $modname_bits); 135 return sub { 136 ($file, $shortname, $isdir, $modname_bits) = @_; 137 138 if($isdir) { # this never gets called on the startdir itself, just subdirs 139 140 if( $self->{'_dirs_visited'}{$file} ) { 141 $verbose and print "Directory '$file' already seen, skipping.\n"; 142 return 'PRUNE'; 143 } 144 145 print "Looking in dir $file\n" if $verbose; 146 147 unless ($laborious) { # $laborious overrides pruning 148 if( m/^(\d+\.[\d_]{3,})\z/s 149 and do { my $x = $1; $x =~ tr/_//d; $x != $] } 150 ) { 151 $verbose and print "Perl $] version mismatch on $_, skipping.\n"; 152 return 'PRUNE'; 153 } 154 155 if( m/^([A-Za-z][a-zA-Z0-9_]*)\z/s ) { 156 $verbose and print "$_ is a well-named module subdir. Looking....\n"; 157 } else { 158 $verbose and print "$_ is a fishy directory name. Skipping.\n"; 159 return 'PRUNE'; 160 } 161 } # end unless $laborious 162 163 $self->{'_dirs_visited'}{$file} = 1; 164 return; # (not pruning); 165 } 166 167 168 # Make sure it's a file even worth even considering 169 if($laborious) { 170 unless( 171 m/\.(pod|pm|plx?)\z/i || -x _ and -T _ 172 # Note that the cheapest operation (the RE) is run first. 173 ) { 174 $verbose > 1 and print " Brushing off uninteresting $file\n"; 175 return; 176 } 177 } else { 178 unless( m/^[-_a-zA-Z0-9]+\.(?:pod|pm|plx?)\z/is ) { 179 $verbose > 1 and print " Brushing off oddly-named $file\n"; 180 return; 181 } 182 } 183 184 $verbose and print "Considering item $file\n"; 185 my $name = $self->_path2modname( $file, $shortname, $modname_bits ); 186 $verbose > 0.01 and print " Nominating $file as $name\n"; 187 188 if($limit_re and $name !~ m/$limit_re/i) { 189 $verbose and print "Shunning $name as not matching $limit_re\n"; 190 return; 191 } 192 193 if( !$shadows and $name2path->{$name} ) { 194 $verbose and print "Not worth considering $file ", 195 "-- already saw $name as ", 196 join(' ', grep($path2name->{$_} eq $name, keys %$path2name)), "\n"; 197 return; 198 } 199 200 # Put off until as late as possible the expense of 201 # actually reading the file: 202 if( m/\.pod\z/is ) { 203 # just assume it has pod, okay? 204 } else { 205 $progress and $progress->reach($self->{'_scan_count'}, "Scanning $file"); 206 return unless $self->contains_pod( $file ); 207 } 208 ++ $self->{'_scan_count'}; 209 210 # Or finally take note of it: 211 if( $name2path->{$name} ) { 212 $verbose and print 213 "Duplicate POD found (shadowing?): $name ($file)\n", 214 " Already seen in ", 215 join(' ', grep($path2name->{$_} eq $name, keys %$path2name)), "\n"; 216 } else { 217 $name2path->{$name} = $file; # Noting just the first occurrence 218 } 219 $verbose and print " Noting $name = $file\n"; 220 if( $callback ) { 221 local $_ = $_; # insulate from changes, just in case 222 $callback->($file, $name); 223 } 224 $path2name->{$file} = $name; 225 return; 226 } 227 } 228 229 #========================================================================== 230 231 sub _path2modname { 232 my($self, $file, $shortname, $modname_bits) = @_; 233 234 # this code simplifies the POD name for Perl modules: 235 # * remove "site_perl" 236 # * remove e.g. "i586-linux" (from 'archname') 237 # * remove e.g. 5.00503 238 # * remove pod/ if followed by perl*.pod (e.g. in pod/perlfunc.pod) 239 # * dig into the file for case-preserved name if not already mixed case 240 241 my @m = @$modname_bits; 242 my $x; 243 my $verbose = $self->verbose; 244 245 # Shaving off leading naughty-bits 246 while(@m 247 and defined($x = lc( $m[0] )) 248 and( $x eq 'site_perl' 249 or($x eq 'pod' and @m == 1 and $shortname =~ m{^perl.*\.pod$}s ) 250 or $x =~ m{\\d+\\.z\\d+([_.]?\\d+)?} # if looks like a vernum 251 or $x eq lc( $Config::Config{'archname'} ) 252 )) { shift @m } 253 254 my $name = join '::', @m, $shortname; 255 $self->_simplify_base($name); 256 257 # On VMS, case-preserved document names can't be constructed from 258 # filenames, so try to extract them from the "=head1 NAME" tag in the 259 # file instead. 260 if ($^O eq 'VMS' && ($name eq lc($name) || $name eq uc($name))) { 261 open PODFILE, "<" ,"$file" or die "_path2modname: Can't open $file: $!"; 262 my $in_pod = 0; 263 my $in_name = 0; 264 my $line; 265 while ($line = <PODFILE>) { 266 chomp $line; 267 $in_pod = 1 if ($line =~ m/^=\w/); 268 $in_pod = 0 if ($line =~ m/^=cut/); 269 next unless $in_pod; # skip non-pod text 270 next if ($line =~ m/^\s*\z/); # and blank lines 271 next if ($in_pod && ($line =~ m/^X</)); # and commands 272 if ($in_name) { 273 if ($line =~ m/(\w+::)?(\w+)/) { 274 # substitute case-preserved version of name 275 my $podname = $2; 276 my $prefix = $1 || ''; 277 $verbose and print "Attempting case restore of '$name' from '$prefix$podname'\n"; 278 unless ($name =~ s/$prefix$podname/$prefix$podname/i) { 279 $verbose and print "Attempting case restore of '$name' from '$podname'\n"; 280 $name =~ s/$podname/$podname/i; 281 } 282 last; 283 } 284 } 285 $in_name = 1 if ($line =~ m/^=head1 NAME/); 286 } 287 close PODFILE; 288 } 289 290 return $name; 291 } 292 293 #========================================================================== 294 295 sub _recurse_dir { 296 my($self, $startdir, $callback, $modname_bits) = @_; 297 298 my $maxdepth = $self->{'fs_recursion_maxdepth'} || 10; 299 my $verbose = $self->verbose; 300 301 my $here_string = File::Spec->curdir; 302 my $up_string = File::Spec->updir; 303 $modname_bits ||= []; 304 305 my $recursor; 306 $recursor = sub { 307 my($dir_long, $dir_bare) = @_; 308 if( @$modname_bits >= 10 ) { 309 $verbose and print "Too deep! [@$modname_bits]\n"; 310 return; 311 } 312 313 unless(-d $dir_long) { 314 $verbose > 2 and print "But it's not a dir! $dir_long\n"; 315 return; 316 } 317 unless( opendir(INDIR, $dir_long) ) { 318 $verbose > 2 and print "Can't opendir $dir_long : $!\n"; 319 closedir(INDIR); 320 return 321 } 322 my @items = sort readdir(INDIR); 323 closedir(INDIR); 324 325 push @$modname_bits, $dir_bare unless $dir_bare eq ''; 326 327 my $i_full; 328 foreach my $i (@items) { 329 next if $i eq $here_string or $i eq $up_string or $i eq ''; 330 $i_full = File::Spec->catfile( $dir_long, $i ); 331 332 if(!-r $i_full) { 333 $verbose and print "Skipping unreadable $i_full\n"; 334 335 } elsif(-f $i_full) { 336 $_ = $i; 337 $callback->( $i_full, $i, 0, $modname_bits ); 338 339 } elsif(-d _) { 340 $i =~ s/\.DIR\z//i if $^O eq 'VMS'; 341 $_ = $i; 342 my $rv = $callback->( $i_full, $i, 1, $modname_bits ) || ''; 343 344 if($rv eq 'PRUNE') { 345 $verbose > 1 and print "OK, pruning"; 346 } else { 347 # Otherwise, recurse into it 348 $recursor->( File::Spec->catdir($dir_long, $i) , $i); 349 } 350 } else { 351 $verbose > 1 and print "Skipping oddity $i_full\n"; 352 } 353 } 354 pop @$modname_bits; 355 return; 356 };; 357 358 local $_; 359 $recursor->($startdir, ''); 360 361 undef $recursor; # allow it to be GC'd 362 363 return; 364 } 365 366 367 #========================================================================== 368 369 sub run { 370 # A function, useful in one-liners 371 372 my $self = __PACKAGE__->new; 373 $self->limit_glob($ARGV[0]) if @ARGV; 374 $self->callback( sub { 375 my($file, $name) = @_; 376 my $version = ''; 377 378 # Yes, I know we won't catch the version in like a File/Thing.pm 379 # if we see File/Thing.pod first. That's just the way the 380 # cookie crumbles. -- SMB 381 382 if($file =~ m/\.pod$/i) { 383 # Don't bother looking for $VERSION in .pod files 384 DEBUG and print "Not looking for \$VERSION in .pod $file\n"; 385 } elsif( !open(INPOD, "<", $file) ) { 386 DEBUG and print "Couldn't open $file: $!\n"; 387 close(INPOD); 388 } else { 389 # Sane case: file is readable 390 my $lines = 0; 391 while(<INPOD>) { 392 last if $lines++ > $MAX_VERSION_WITHIN; # some degree of sanity 393 if( s/^\s*\$VERSION\s*=\s*//s and m/\d/ ) { 394 DEBUG and print "Found version line (#$lines): $_"; 395 s/\s*\#.*//s; 396 s/\;\s*$//s; 397 s/\s+$//s; 398 s/\t+/ /s; # nix tabs 399 # Optimize the most common cases: 400 $_ = "v$1" 401 if m{^v?["']?([0-9_]+(\.[0-9_]+)*)["']?$}s 402 # like in $VERSION = "3.14159"; 403 or m{\$Revision:\s*([0-9_]+(?:\.[0-9_]+)*)\s*\$}s 404 # like in sprintf("%d.%02d", q$Revision: 4.13 $ =~ /(\d+)\.(\d+)/); 405 ; 406 407 # Like in sprintf("%d.%s", map {s/_//g; $_} q$Name: release-0_55-public $ =~ /-(\d+)_([\d_]+)/) 408 $_ = sprintf("v%d.%s", 409 map {s/_//g; $_} 410 $1 =~ m/-(\d+)_([\d_]+)/) # snare just the numeric part 411 if m{\$Name:\s*([^\$]+)\$}s 412 ; 413 $version = $_; 414 DEBUG and print "Noting $version as version\n"; 415 last; 416 } 417 } 418 close(INPOD); 419 } 420 print "$name\t$version\t$file\n"; 421 return; 422 # End of callback! 423 }); 424 425 $self->survey; 426 } 427 428 #========================================================================== 429 430 sub simplify_name { 431 my($self, $str) = @_; 432 433 # Remove all path components 434 # XXX Why not just use basename()? -- SMB 435 436 if ($^O eq 'MacOS') { $str =~ s{^.*:+}{}s } 437 else { $str =~ s{^.*/+}{}s } 438 439 $self->_simplify_base($str); 440 return $str; 441 } 442 443 #========================================================================== 444 445 sub _simplify_base { # Internal method only 446 447 # strip Perl's own extensions 448 $_[1] =~ s/\.(pod|pm|plx?)\z//i; 449 450 # strip meaningless extensions on Win32 and OS/2 451 $_[1] =~ s/\.(bat|exe|cmd)\z//i if $^O =~ /mswin|os2/i; 452 453 # strip meaningless extensions on VMS 454 $_[1] =~ s/\.(com)\z//i if $^O eq 'VMS'; 455 456 return; 457 } 458 459 #========================================================================== 460 461 sub _expand_inc { 462 my($self, $search_dirs) = @_; 463 464 return unless $self->{'inc'}; 465 466 if ($^O eq 'MacOS') { 467 push @$search_dirs, 468 grep $_ ne File::Spec->curdir, $self->_mac_whammy(@INC); 469 # Any other OSs need custom handling here? 470 } else { 471 push @$search_dirs, grep $_ ne File::Spec->curdir, @INC; 472 } 473 474 $self->{'laborious'} = 0; # Since inc said to use INC 475 return; 476 } 477 478 #========================================================================== 479 480 sub _mac_whammy { # Tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS 481 my @them; 482 (undef,@them) = @_; 483 for $_ (@them) { 484 if ( $_ eq '.' ) { 485 $_ = ':'; 486 } elsif ( $_ =~ s|^((?:\.\./)+)|':' x (length($1)/3)|e ) { 487 $_ = ':'. $_; 488 } else { 489 $_ =~ s|^\./|:|; 490 } 491 } 492 return @them; 493 } 494 495 #========================================================================== 496 497 sub _limit_glob_to_limit_re { 498 my $self = $_[0]; 499 my $limit_glob = $self->{'limit_glob'} || return; 500 501 my $limit_re = '^' . quotemeta($limit_glob) . '$'; 502 $limit_re =~ s/\\\?/./g; # glob "?" => "." 503 $limit_re =~ s/\\\*/.*?/g; # glob "*" => ".*?" 504 $limit_re =~ s/\.\*\?\$$//s; # final glob "*" => ".*?$" => "" 505 506 $self->{'verbose'} and print "Turning limit_glob $limit_glob into re $limit_re\n"; 507 508 # A common optimization: 509 if(!exists($self->{'dir_prefix'}) 510 and $limit_glob =~ m/^(?:\w+\:\:)+/s # like "File::*" or "File::Thing*" 511 # Optimize for sane and common cases (but not things like "*::File") 512 ) { 513 $self->{'dir_prefix'} = join "::", $limit_glob =~ m/^(?:\w+::)+/sg; 514 $self->{'verbose'} and print " and setting dir_prefix to $self->{'dir_prefix'}\n"; 515 } 516 517 return $limit_re; 518 } 519 520 #========================================================================== 521 522 # contribution mostly from Tim Jenness <t.jenness@jach.hawaii.edu> 523 524 sub find { 525 my($self, $pod, @search_dirs) = @_; 526 $self = $self->new unless ref $self; # tolerate being a class method 527 528 # Check usage 529 Carp::carp 'Usage: \$self->find($podname, ...)' 530 unless defined $pod and length $pod; 531 532 my $verbose = $self->verbose; 533 534 # Split on :: and then join the name together using File::Spec 535 my @parts = split /::/, $pod; 536 $verbose and print "Chomping {$pod} => {@parts}\n"; 537 538 #@search_dirs = File::Spec->curdir unless @search_dirs; 539 540 if( $self->inc ) { 541 if( $^O eq 'MacOS' ) { 542 push @search_dirs, $self->_mac_whammy(@INC); 543 } else { 544 push @search_dirs, @INC; 545 } 546 547 # Add location of pod documentation for perl man pages (eg perlfunc) 548 # This is a pod directory in the private install tree 549 #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'}, 550 # 'pod'); 551 #push (@search_dirs, $perlpoddir) 552 # if -d $perlpoddir; 553 554 # Add location of binaries such as pod2text: 555 push @search_dirs, $Config::Config{'scriptdir'}; 556 # and if that's undef or q{} or nonexistent, we just ignore it later 557 } 558 559 my %seen_dir; 560 Dir: 561 foreach my $dir ( @search_dirs ) { 562 next unless defined $dir and length $dir; 563 next if $seen_dir{$dir}; 564 $seen_dir{$dir} = 1; 565 unless(-d $dir) { 566 print "Directory $dir does not exist\n" if $verbose; 567 next Dir; 568 } 569 570 print "Looking in directory $dir\n" if $verbose; 571 my $fullname = File::Spec->catfile( $dir, @parts ); 572 print "Filename is now $fullname\n" if $verbose; 573 574 foreach my $ext ('', '.pod', '.pm', '.pl') { # possible extensions 575 my $fullext = $fullname . $ext; 576 if( -f $fullext and $self->contains_pod( $fullext ) ){ 577 print "FOUND: $fullext\n" if $verbose; 578 return $fullext; 579 } 580 } 581 my $subdir = File::Spec->catdir($dir,'pod'); 582 if(-d $subdir) { # slip in the ./pod dir too 583 $verbose and print "Noticing $subdir and stopping there...\n"; 584 $dir = $subdir; 585 redo Dir; 586 } 587 } 588 589 return undef; 590 } 591 592 #========================================================================== 593 594 sub contains_pod { 595 my($self, $file) = @_; 596 my $verbose = $self->{'verbose'}; 597 598 # check for one line of POD 599 $verbose > 1 and print " Scanning $file for pod...\n"; 600 unless( open(MAYBEPOD,"<", "$file") ) { 601 print "Error: $file is unreadable: $!\n"; 602 return undef; 603 } 604 605 sleep($SLEEPY - 1) if $SLEEPY; 606 # avoid totally hogging the processor on OSs with poor process control 607 608 local $_; 609 while( <MAYBEPOD> ) { 610 if(m/^=(head\d|pod|over|item)\b/s) { 611 close(MAYBEPOD) || die "Bizarre error closing $file: $!\nAborting"; 612 chomp; 613 $verbose > 1 and print " Found some pod ($_) in $file\n"; 614 return 1; 615 } 616 } 617 close(MAYBEPOD) || die "Bizarre error closing $file: $!\nAborting"; 618 $verbose > 1 and print " No POD in $file, skipping.\n"; 619 return 0; 620 } 621 622 #========================================================================== 623 624 sub _accessorize { # A simple-minded method-maker 625 shift; 626 no strict 'refs'; 627 foreach my $attrname (@_) { 628 *{caller() . '::' . $attrname} = sub { 629 use strict; 630 $Carp::CarpLevel = 1, Carp::croak( 631 "Accessor usage: \$obj->$attrname() or \$obj->$attrname(\$new_value)" 632 ) unless (@_ == 1 or @_ == 2) and ref $_[0]; 633 634 # Read access: 635 return $_[0]->{$attrname} if @_ == 1; 636 637 # Write access: 638 $_[0]->{$attrname} = $_[1]; 639 return $_[0]; # RETURNS MYSELF! 640 }; 641 } 642 # Ya know, they say accessories make the ensemble! 643 return; 644 } 645 646 #========================================================================== 647 sub _state_as_string { 648 my $self = $_[0]; 649 return '' unless ref $self; 650 my @out = "{\n # State of $self ...\n"; 651 foreach my $k (sort keys %$self) { 652 push @out, " ", _esc($k), " => ", _esc($self->{$k}), ",\n"; 653 } 654 push @out, "}\n"; 655 my $x = join '', @out; 656 $x =~ s/^/#/mg; 657 return $x; 658 } 659 660 sub _esc { 661 my $in = $_[0]; 662 return 'undef' unless defined $in; 663 $in =~ 664 s<([^\x20\x21\x23\x27-\x3F\x41-\x5B\x5D-\x7E])> 665 <'\\x'.(unpack("H2",$1))>eg; 666 return qq{"$in"}; 667 } 668 669 #========================================================================== 670 671 run() unless caller; # run if "perl whatever/Search.pm" 672 673 1; 674 675 #========================================================================== 676 677 __END__ 678 679 680 =head1 NAME 681 682 Pod::Simple::Search - find POD documents in directory trees 683 684 =head1 SYNOPSIS 685 686 use Pod::Simple::Search; 687 my $name2path = Pod::Simple::Search->new->limit_glob('LWP::*')->survey; 688 print "Looky see what I found: ", 689 join(' ', sort keys %$name2path), "\n"; 690 691 print "LWPUA docs = ", 692 Pod::Simple::Search->new->find('LWP::UserAgent') || "?", 693 "\n"; 694 695 =head1 DESCRIPTION 696 697 B<Pod::Simple::Search> is a class that you use for running searches 698 for Pod files. An object of this class has several attributes 699 (mostly options for controlling search options), and some methods 700 for searching based on those attributes. 701 702 The way to use this class is to make a new object of this class, 703 set any options, and then call one of the search options 704 (probably C<survey> or C<find>). The sections below discuss the 705 syntaxes for doing all that. 706 707 708 =head1 CONSTRUCTOR 709 710 This class provides the one constructor, called C<new>. 711 It takes no parameters: 712 713 use Pod::Simple::Search; 714 my $search = Pod::Simple::Search->new; 715 716 =head1 ACCESSORS 717 718 This class defines several methods for setting (and, occasionally, 719 reading) the contents of an object. With two exceptions (discussed at 720 the end of this section), these attributes are just for controlling the 721 way searches are carried out. 722 723 Note that each of these return C<$self> when you call them as 724 C<< $self->I<whatever(value)> >>. That's so that you can chain 725 together set-attribute calls like this: 726 727 my $name2path = 728 Pod::Simple::Search->new 729 -> inc(0) -> verbose(1) -> callback(\&blab) 730 ->survey(@there); 731 732 ...which works exactly as if you'd done this: 733 734 my $search = Pod::Simple::Search->new; 735 $search->inc(0); 736 $search->verbose(1); 737 $search->callback(\&blab); 738 my $name2path = $search->survey(@there); 739 740 =over 741 742 =item $search->inc( I<true-or-false> ); 743 744 This attribute, if set to a true value, means that searches should 745 implicitly add perl's I<@INC> paths. This 746 automatically considers paths specified in the C<PERL5LIB> environment 747 as this is prepended to I<@INC> by the Perl interpreter itself. 748 This attribute's default value is B<TRUE>. If you want to search 749 only specific directories, set $self->inc(0) before calling 750 $inc->survey or $inc->find. 751 752 753 =item $search->verbose( I<nonnegative-number> ); 754 755 This attribute, if set to a nonzero positive value, will make searches output 756 (via C<warn>) notes about what they're doing as they do it. 757 This option may be useful for debugging a pod-related module. 758 This attribute's default value is zero, meaning that no C<warn> messages 759 are produced. (Setting verbose to 1 turns on some messages, and setting 760 it to 2 turns on even more messages, i.e., makes the following search(es) 761 even more verbose than 1 would make them.) 762 763 764 =item $search->limit_glob( I<some-glob-string> ); 765 766 This option means that you want to limit the results just to items whose 767 podnames match the given glob/wildcard expression. For example, you 768 might limit your search to just "LWP::*", to search only for modules 769 starting with "LWP::*" (but not including the module "LWP" itself); or 770 you might limit your search to "LW*" to see only modules whose (full) 771 names begin with "LW"; or you might search for "*Find*" to search for 772 all modules with "Find" somewhere in their full name. (You can also use 773 "?" in a glob expression; so "DB?" will match "DBI" and "DBD".) 774 775 776 =item $search->callback( I<\&some_routine> ); 777 778 This attribute means that every time this search sees a matching 779 Pod file, it should call this callback routine. The routine is called 780 with two parameters: the current file's filespec, and its pod name. 781 (For example: C<("/etc/perljunk/File/Crunk.pm", "File::Crunk")> would 782 be in C<@_>.) 783 784 The callback routine's return value is not used for anything. 785 786 This attribute's default value is false, meaning that no callback 787 is called. 788 789 =item $search->laborious( I<true-or-false> ); 790 791 Unless you set this attribute to a true value, Pod::Search will 792 apply Perl-specific heuristics to find the correct module PODs quickly. 793 This attribute's default value is false. You won't normally need 794 to set this to true. 795 796 Specifically: Turning on this option will disable the heuristics for 797 seeing only files with Perl-like extensions, omitting subdirectories 798 that are numeric but do I<not> match the current Perl interpreter's 799 version ID, suppressing F<site_perl> as a module hierarchy name, etc. 800 801 802 =item $search->shadows( I<true-or-false> ); 803 804 Unless you set this attribute to a true value, Pod::Simple::Search will 805 consider only the first file of a given modulename as it looks thru the 806 specified directories; that is, with this option off, if 807 Pod::Simple::Search has seen a C<somepathdir/Foo/Bar.pm> already in this 808 search, then it won't bother looking at a C<somelaterpathdir/Foo/Bar.pm> 809 later on in that search, because that file is merely a "shadow". But if 810 you turn on C<< $self->shadows(1) >>, then these "shadow" files are 811 inspected too, and are noted in the pathname2podname return hash. 812 813 This attribute's default value is false; and normally you won't 814 need to turn it on. 815 816 817 =item $search->limit_re( I<some-regxp> ); 818 819 Setting this attribute (to a value that's a regexp) means that you want 820 to limit the results just to items whose podnames match the given 821 regexp. Normally this option is not needed, and the more efficient 822 C<limit_glob> attribute is used instead. 823 824 825 =item $search->dir_prefix( I<some-string-value> ); 826 827 Setting this attribute to a string value means that the searches should 828 begin in the specified subdirectory name (like "Pod" or "File::Find", 829 also expressable as "File/Find"). For example, the search option 830 C<< $search->limit_glob("File::Find::R*") >> 831 is the same as the combination of the search options 832 C<< $search->limit_re("^File::Find::R") -> dir_prefix("File::Find") >>. 833 834 Normally you don't need to know about the C<dir_prefix> option, but I 835 include it in case it might prove useful for someone somewhere. 836 837 (Implementationally, searching with limit_glob ends up setting limit_re 838 and usually dir_prefix.) 839 840 841 =item $search->progress( I<some-progress-object> ); 842 843 If you set a value for this attribute, the value is expected 844 to be an object (probably of a class that you define) that has a 845 C<reach> method and a C<done> method. This is meant for reporting 846 progress during the search, if you don't want to use a simple 847 callback. 848 849 Normally you don't need to know about the C<progress> option, but I 850 include it in case it might prove useful for someone somewhere. 851 852 While a search is in progress, the progress object's C<reach> and 853 C<done> methods are called like this: 854 855 # Every time a file is being scanned for pod: 856 $progress->reach($count, "Scanning $file"); ++$count; 857 858 # And then at the end of the search: 859 $progress->done("Noted $count Pod files total"); 860 861 Internally, we often set this to an object of class 862 Pod::Simple::Progress. That class is probably undocumented, 863 but you may wish to look at its source. 864 865 866 =item $name2path = $self->name2path; 867 868 This attribute is not a search parameter, but is used to report the 869 result of C<survey> method, as discussed in the next section. 870 871 =item $path2name = $self->path2name; 872 873 This attribute is not a search parameter, but is used to report the 874 result of C<survey> method, as discussed in the next section. 875 876 =back 877 878 =head1 MAIN SEARCH METHODS 879 880 Once you've actually set any options you want (if any), you can go 881 ahead and use the following methods to search for Pod files 882 in particular ways. 883 884 885 =head2 C<< $search->survey( @directories ) >> 886 887 The method C<survey> searches for POD documents in a given set of 888 files and/or directories. This runs the search according to the various 889 options set by the accessors above. (For example, if the C<inc> attribute 890 is on, as it is by default, then the perl @INC directories are implicitly 891 added to the list of directories (if any) that you specify.) 892 893 The return value of C<survey> is two hashes: 894 895 =over 896 897 =item C<name2path> 898 899 A hash that maps from each pod-name to the filespec (like 900 "Stuff::Thing" => "/whatever/plib/Stuff/Thing.pm") 901 902 =item C<path2name> 903 904 A hash that maps from each Pod filespec to its pod-name (like 905 "/whatever/plib/Stuff/Thing.pm" => "Stuff::Thing") 906 907 =back 908 909 Besides saving these hashes as the hashref attributes 910 C<name2path> and C<path2name>, calling this function also returns 911 these hashrefs. In list context, the return value of 912 C<< $search->survey >> is the list C<(\%name2path, \%path2name)>. 913 In scalar context, the return value is C<\%name2path>. 914 Or you can just call this in void context. 915 916 Regardless of calling context, calling C<survey> saves 917 its results in its C<name2path> and C<path2name> attributes. 918 919 E.g., when searching in F<$HOME/perl5lib>, the file 920 F<$HOME/perl5lib/MyModule.pm> would get the POD name I<MyModule>, 921 whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be 922 I<Myclass::Subclass>. The name information can be used for POD 923 translators. 924 925 Only text files containing at least one valid POD command are found. 926 927 In verbose mode, a warning is printed if shadows are found (i.e., more 928 than one POD file with the same POD name is found, e.g. F<CPAN.pm> in 929 different directories). This usually indicates duplicate occurrences of 930 modules in the I<@INC> search path, which is occasionally inadvertent 931 (but is often simply a case of a user's path dir having a more recent 932 version than the system's general path dirs in general.) 933 934 The options to this argument is a list of either directories that are 935 searched recursively, or files. (Usually you wouldn't specify files, 936 but just dirs.) Or you can just specify an empty-list, as in 937 $name2path; with the 938 C<inc> option on, as it is by default, teh 939 940 The POD names of files are the plain basenames with any Perl-like 941 extension (.pm, .pl, .pod) stripped, and path separators replaced by 942 C<::>'s. 943 944 Calling Pod::Simple::Search->search(...) is short for 945 Pod::Simple::Search->new->search(...). That is, a throwaway object 946 with default attribute values is used. 947 948 949 =head2 C<< $search->simplify_name( $str ) >> 950 951 The method B<simplify_name> is equivalent to B<basename>, but also 952 strips Perl-like extensions (.pm, .pl, .pod) and extensions like 953 F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively. 954 955 956 =head2 C<< $search->find( $pod ) >> 957 958 =head2 C<< $search->find( $pod, @search_dirs ) >> 959 960 Returns the location of a Pod file, given a Pod/module/script name 961 (like "Foo::Bar" or "perlvar" or "perldoc"), and an idea of 962 what files/directories to look in. 963 It searches according to the various options set by the accessors above. 964 (For example, if the C<inc> attribute is on, as it is by default, then 965 the perl @INC directories are implicitly added to the list of 966 directories (if any) that you specify.) 967 968 This returns the full path of the first occurrence to the file. 969 Package names (eg 'A::B') are automatically converted to directory 970 names in the selected directory. Additionally, '.pm', '.pl' and '.pod' 971 are automatically appended to the search as required. 972 (So, for example, under Unix, "A::B" is converted to "somedir/A/B.pm", 973 "somedir/A/B.pod", or "somedir/A/B.pl", as appropriate.) 974 975 If no such Pod file is found, this method returns undef. 976 977 If any of the given search directories contains a F<pod/> subdirectory, 978 then it is searched. (That's how we manage to find F<perlfunc>, 979 for example, which is usually in F<pod/perlfunc> in most Perl dists.) 980 981 The C<verbose> and C<inc> attributes influence the behavior of this 982 search; notably, C<inc>, if true, adds @INC I<and also 983 $Config::Config{'scriptdir'}> to the list of directories to search. 984 985 It is common to simply say C<< $filename = Pod::Simple::Search-> new 986 ->find("perlvar") >> so that just the @INC (well, and scriptdir) 987 directories are searched. (This happens because the C<inc> 988 attribute is true by default.) 989 990 Calling Pod::Simple::Search->find(...) is short for 991 Pod::Simple::Search->new->find(...). That is, a throwaway object 992 with default attribute values is used. 993 994 995 =head2 C<< $self->contains_pod( $file ) >> 996 997 Returns true if the supplied filename (not POD module) contains some Pod 998 documentation. 999 1000 1001 =head1 AUTHOR 1002 1003 Sean M. Burke E<lt>sburke@cpan.orgE<gt> 1004 borrowed code from 1005 Marek Rouchal's Pod::Find, which in turn 1006 heavily borrowed code from Nick Ing-Simmons' PodToHtml. 1007 1008 Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided 1009 C<find> and C<contains_pod> to Pod::Find. 1010 1011 =head1 SEE ALSO 1012 1013 L<Pod::Simple>, L<Pod::Perldoc> 1014 1015 =cut 1016 -
(a) /dev/null vs. (b) lib/Pod/Simple/HTMLBatch.pm
a b 1 2 require 5; 3 package Pod::Simple::HTMLBatch; 4 use strict; 5 use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION 6 $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA 7 ); 8 $VERSION = '3.02'; 9 @ISA = (); # Yup, we're NOT a subclass of Pod::Simple::HTML! 10 11 # TODO: nocontents stylesheets. Strike some of the color variations? 12 13 use Pod::Simple::HTML (); 14 BEGIN {*esc = \&Pod::Simple::HTML::esc } 15 use File::Spec (); 16 use UNIVERSAL (); 17 # "Isn't the Universe an amazing place? I wouldn't live anywhere else!" 18 19 use Pod::Simple::Search; 20 $SEARCH_CLASS ||= 'Pod::Simple::Search'; 21 22 BEGIN { 23 if(defined &DEBUG) { } # no-op 24 elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG } 25 else { *DEBUG = sub () {0}; } 26 } 27 28 $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i; 29 # flag to occasionally sleep for $SLEEPY - 1 seconds. 30 31 $HTML_RENDER_CLASS ||= "Pod::Simple::HTML"; 32 33 # 34 # Methods beginning with "_" are particularly internal and possibly ugly. 35 # 36 37 Pod::Simple::_accessorize( __PACKAGE__, 38 'verbose', # how verbose to be during batch conversion 39 'html_render_class', # what class to use to render 40 'contents_file', # If set, should be the name of a file (in current directory) 41 # to write the list of all modules to 42 'index', # will set $htmlpage->index(...) to this (true or false) 43 'progress', # progress object 44 'contents_page_start', 'contents_page_end', 45 46 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad', 47 'no_contents_links', # set to true to suppress automatic adding of << links. 48 '_contents', 49 ); 50 51 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 52 # Just so we can run from the command line more easily 53 sub go { 54 @ARGV == 2 or die sprintf( 55 "Usage: perl -M%s -e %s:go indirs outdir\n (or use \"\@INC\" for indirs)\n", 56 __PACKAGE__, __PACKAGE__, 57 ); 58 59 if(defined($ARGV[1]) and length($ARGV[1])) { 60 my $d = $ARGV[1]; 61 -e $d or die "I see no output directory named \"$d\"\nAborting"; 62 -d $d or die "But \"$d\" isn't a directory!\nAborting"; 63 -w $d or die "Directory \"$d\" isn't writeable!\nAborting"; 64 } 65 66 __PACKAGE__->batch_convert(@ARGV); 67 } 68 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 69 70 71 sub new { 72 my $new = bless {}, ref($_[0]) || $_[0]; 73 $new->html_render_class($HTML_RENDER_CLASS); 74 $new->verbose(1 + DEBUG); 75 $new->_contents([]); 76 77 $new->index(1); 78 79 $new-> _css_wad([]); $new->css_flurry(1); 80 $new->_javascript_wad([]); $new->javascript_flurry(1); 81 82 $new->contents_file( 83 'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION) 84 ); 85 86 $new->contents_page_start( join "\n", grep $_, 87 $Pod::Simple::HTML::Doctype_decl, 88 "<html><head>", 89 "<title>Perl Documentation</title>", 90 $Pod::Simple::HTML::Content_decl, 91 "</head>", 92 "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n" 93 ); # override if you need a different title 94 95 96 $new->contents_page_end( sprintf( 97 "\n\n<p class='contentsfooty'>Generated by %s v%s under Perl v%s\n<br >At %s GMT, which is %s local time.</p>\n\n</body></html>\n", 98 esc( 99 ref($new), 100 eval {$new->VERSION} || $VERSION, 101 $], scalar(gmtime), scalar(localtime), 102 ))); 103 104 return $new; 105 } 106 107 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 108 109 sub muse { 110 my $self = shift; 111 if($self->verbose) { 112 print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n"; 113 } 114 return 1; 115 } 116 117 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 118 119 sub batch_convert { 120 my($self, $dirs, $outdir) = @_; 121 $self ||= __PACKAGE__; # tolerate being called as an optionless function 122 $self = $self->new unless ref $self; # tolerate being used as a class method 123 124 if(!defined($dirs) or $dirs eq '' or $dirs eq '@INC' ) { 125 $dirs = ''; 126 } elsif(ref $dirs) { 127 # OK, it's an explicit set of dirs to scan, specified as an arrayref. 128 } else { 129 # OK, it's an explicit set of dirs to scan, specified as a 130 # string like "/thing:/also:/whatever/perl" (":"-delim, as usual) 131 # or, under MSWin, like "c:/thing;d:/also;c:/whatever/perl" (";"-delim!) 132 require Config; 133 my $ps = quotemeta( $Config::Config{'path_sep'} || ":" ); 134 $dirs = [ grep length($_), split qr/$ps/, $dirs ]; 135 } 136 137 $outdir = $self->filespecsys->curdir 138 unless defined $outdir and length $outdir; 139 140 $self->_batch_convert_main($dirs, $outdir); 141 } 142 143 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 144 145 sub _batch_convert_main { 146 my($self, $dirs, $outdir) = @_; 147 # $dirs is either false, or an arrayref. 148 # $outdir is a pathspec. 149 150 $self->{'_batch_start_time'} ||= time(); 151 152 $self->muse( "= ", scalar(localtime) ); 153 $self->muse( "Starting batch conversion to \"$outdir\"" ); 154 155 my $progress = $self->progress; 156 if(!$progress and $self->verbose > 0 and $self->verbose() <= 5) { 157 require Pod::Simple::Progress; 158 $progress = Pod::Simple::Progress->new( 159 ($self->verbose < 2) ? () # Default omission-delay 160 : ($self->verbose == 2) ? 1 # Reduce the omission-delay 161 : 0 # Eliminate the omission-delay 162 ); 163 $self->progress($progress); 164 } 165 166 if($dirs) { 167 $self->muse(scalar(@$dirs), " dirs to scan: @$dirs"); 168 } else { 169 $self->muse("Scanning \@INC. This could take a minute or two."); 170 } 171 my $mod2path = $self->find_all_pods($dirs ? $dirs : ()); 172 $self->muse("Done scanning."); 173 174 my $total = keys %$mod2path; 175 unless($total) { 176 $self->muse("No pod found. Aborting batch conversion.\n"); 177 return $self; 178 } 179 180 $progress and $progress->goal($total); 181 $self->muse("Now converting pod files to HTML.", 182 ($total > 25) ? " This will take a while more." : () 183 ); 184 185 $self->_spray_css( $outdir ); 186 $self->_spray_javascript( $outdir ); 187 188 $self->_do_all_batch_conversions($mod2path, $outdir); 189 190 $progress and $progress->done(sprintf ( 191 "Done converting %d files.", $self->{"__batch_conv_page_count"} 192 )); 193 return $self->_batch_convert_finish($outdir); 194 return $self; 195 } 196 197 198 sub _do_all_batch_conversions { 199 my($self, $mod2path, $outdir) = @_; 200 $self->{"__batch_conv_page_count"} = 0; 201 202 foreach my $module (sort {lc($a) cmp lc($b)} keys %$mod2path) { 203 $self->_do_one_batch_conversion($module, $mod2path, $outdir); 204 sleep($SLEEPY - 1) if $SLEEPY; 205 } 206 207 return; 208 } 209 210 sub _batch_convert_finish { 211 my($self, $outdir) = @_; 212 $self->write_contents_file($outdir); 213 $self->muse("Done with batch conversion. $$self{'__batch_conv_page_count'} files done."); 214 $self->muse( "= ", scalar(localtime) ); 215 $self->progress and $self->progress->done("All done!"); 216 return; 217 } 218 219 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 220 221 sub _do_one_batch_conversion { 222 my($self, $module, $mod2path, $outdir, $outfile) = @_; 223 224 my $retval; 225 my $total = scalar keys %$mod2path; 226 my $infile = $mod2path->{$module}; 227 my @namelets = grep m/\S/, split "::", $module; 228 # this can stick around in the contents LoL 229 my $depth = scalar @namelets; 230 die "Contentless thingie?! $module $infile" unless @namelets; #sanity 231 232 $outfile ||= do { 233 my @n = @namelets; 234 $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION; 235 $self->filespecsys->catfile( $outdir, @n ); 236 }; 237 238 my $progress = $self->progress; 239 240 my $page = $self->html_render_class->new; 241 if(DEBUG > 5) { 242 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ", 243 ref($page), " render ($depth) $module => $outfile"); 244 } elsif(DEBUG > 2) { 245 $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile") 246 } 247 248 # Give each class a chance to init the converter: 249 250 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth) 251 if $page->can('batch_mode_page_object_init'); 252 $self->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth) 253 if $self->can('batch_mode_page_object_init'); 254 255 # Now get busy... 256 $self->makepath($outdir => \@namelets); 257 258 $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module"); 259 260 if( $retval = $page->parse_from_file($infile, $outfile) ) { 261 ++ $self->{"__batch_conv_page_count"} ; 262 $self->note_for_contents_file( \@namelets, $infile, $outfile ); 263 } else { 264 $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false."); 265 } 266 267 $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth) 268 if $page->can('batch_mode_page_object_kill'); 269 # The following isn't a typo. Note that it switches $self and $page. 270 $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth) 271 if $self->can('batch_mode_page_object_kill'); 272 273 DEBUG > 4 and printf "%s %sb < $infile %s %sb\n", 274 $outfile, -s $outfile, $infile, -s $infile 275 ; 276 277 undef($page); 278 return $retval; 279 } 280 281 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 282 sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' } 283 284 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 285 286 sub note_for_contents_file { 287 my($self, $namelets, $infile, $outfile) = @_; 288 289 # I think the infile and outfile parts are never used. -- SMB 290 # But it's handy to have them around for debugging. 291 292 if( $self->contents_file ) { 293 my $c = $self->_contents(); 294 push @$c, 295 [ join("::", @$namelets), $infile, $outfile, $namelets ] 296 # 0 1 2 3 297 ; 298 DEBUG > 3 and print "Noting @$c[-1]\n"; 299 } 300 return; 301 } 302 303 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- 304 305 sub write_contents_file { 306 my($self, $outdir) = @_; 307 my $outfile = $self->_contents_filespec($outdir) || return; 308 309 $self->muse("Preparing list of modules for ToC"); 310 311 my($toplevel, # maps toplevelbit => [all submodules] 312 $toplevel_form_freq, # ends up being 'foo' => 'Foo' 313 ) = $self->_prep_contents_breakdown; 314 315 my $Contents = eval { $self->_wopen($outfile) }; 316 if( $Contents ) { 317 $self->muse( "Writing contents file $outfile" ); 318 } else { 319 warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all"; 320 return; 321 } 322 323 $self->_write_contents_start( $Contents, $outfile, ); 324 $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq ); 325 $self->_write_contents_end( $Contents, $outfile, ); 326 return $outfile; 327 } 328 329 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 330 331 sub _write_contents_start { 332 my($self, $Contents, $outfile) = @_; 333 my $starter = $self->contents_page_start || ''; 334 335 { 336 my $css_wad = $self->_css_wad_to_markup(1); 337 if( $css_wad ) { 338 $starter =~ s{(</head>)}{\n$css_wad\n$1}i; # otherwise nevermind 339 } 340 341 my $javascript_wad = $self->_javascript_wad_to_markup(1); 342 if( $javascript_wad ) { 343 $starter =~ s{(</head>)}{\n$javascript_wad\n$1}i; # otherwise nevermind 344 } 345 } 346 347 unless(print $Contents $starter, "<dl class='superindex'>\n" ) { 348 warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; 349 close($Contents); 350 return 0; 351 } 352 return 1; 353 } 354 355 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 356 357 sub _write_contents_middle { 358 my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_; 359 360 foreach my $t (sort keys %$toplevel2submodules) { 361 my @downlines = sort {$a->[-1] cmp $b->[-1]} 362 @{ $toplevel2submodules->{$t} }; 363 364 printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n], 365 esc( $t, $toplevel_form_freq->{$t} ) 366 ; 367 368 my($path, $name); 369 foreach my $e (@downlines) { 370 $name = $e->[0]; 371 $path = join( "/", '.', esc( @{$e->[3]} ) ) 372 . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION); 373 print $Contents qq{ <a href="$path">}, esc($name), "</a> \n"; 374 } 375 print $Contents "</dd>\n\n"; 376 } 377 return 1; 378 } 379 380 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 381 382 sub _write_contents_end { 383 my($self, $Contents, $outfile) = @_; 384 unless( 385 print $Contents "</dl>\n", 386 $self->contents_page_end || '', 387 ) { 388 warn "Couldn't write to $outfile: $!"; 389 } 390 close($Contents) or warn "Couldn't close $outfile: $!"; 391 return 1; 392 } 393 394 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 395 396 sub _prep_contents_breakdown { 397 my($self) = @_; 398 my $contents = $self->_contents; 399 my %toplevel; # maps lctoplevelbit => [all submodules] 400 my %toplevel_form_freq; # ends up being 'foo' => 'Foo' 401 # (mapping anycase forms to most freq form) 402 403 foreach my $entry (@$contents) { 404 my $toplevel = 405 $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs' 406 # group all the perlwhatever docs together 407 : $entry->[3][0] # normal case 408 ; 409 ++$toplevel_form_freq{ lc $toplevel }{ $toplevel }; 410 push @{ $toplevel{ lc $toplevel } }, $entry; 411 push @$entry, lc($entry->[0]); # add a sort-order key to the end 412 } 413 414 foreach my $toplevel (sort keys %toplevel) { 415 my $fgroup = $toplevel_form_freq{$toplevel}; 416 $toplevel_form_freq{$toplevel} = 417 ( 418 sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b } 419 keys %$fgroup 420 # This hash is extremely unlikely to have more than 4 members, so this 421 # sort isn't so very wasteful 422 )[0]; 423 } 424 425 return(\%toplevel, \%toplevel_form_freq) if wantarray; 426 return \%toplevel; 427 } 428 429 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 430 431 sub _contents_filespec { 432 my($self, $outdir) = @_; 433 my $outfile = $self->contents_file; 434 return unless $outfile; 435 return $self->filespecsys->catfile( $outdir, $outfile ); 436 } 437 438 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- 439 440 sub makepath { 441 my($self, $outdir, $namelets) = @_; 442 return unless @$namelets > 1; 443 for my $i (0 .. ($#$namelets - 1)) { 444 my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] ); 445 if(-e $dir) { 446 die "$dir exists but not as a directory!?" unless -d $dir; 447 next; 448 } 449 DEBUG > 3 and print " Making $dir\n"; 450 mkdir $dir, 0777 451 or die "Can't mkdir $dir: $!\nAborting" 452 ; 453 } 454 return; 455 } 456 457 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- 458 459 sub batch_mode_page_object_init { 460 my $self = shift; 461 my($page, $module, $infile, $outfile, $depth) = @_; 462 463 # TODO: any further options to percolate onto this new object here? 464 465 $page->default_title($module); 466 $page->index( $self->index ); 467 468 $page->html_css( $self-> _css_wad_to_markup($depth) ); 469 $page->html_javascript( $self->_javascript_wad_to_markup($depth) ); 470 471 $self->add_header_backlink($page, $module, $infile, $outfile, $depth); 472 $self->add_footer_backlink($page, $module, $infile, $outfile, $depth); 473 474 475 return $self; 476 } 477 478 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 479 480 sub add_header_backlink { 481 my $self = shift; 482 return if $self->no_contents_links; 483 my($page, $module, $infile, $outfile, $depth) = @_; 484 $page->html_header_after_title( join '', 485 $page->html_header_after_title || '', 486 487 qq[<p class="backlinktop"><b><a name="___top" href="], 488 $self->url_up_to_contents($depth), 489 qq[" accesskey="1" title="All Documents"><<</a></b></p>\n], 490 ) 491 if $self->contents_file 492 ; 493 return; 494 } 495 496 sub add_footer_backlink { 497 my $self = shift; 498 return if $self->no_contents_links; 499 my($page, $module, $infile, $outfile, $depth) = @_; 500 $page->html_footer( join '', 501 qq[<p class="backlinkbottom"><b><a name="___bottom" href="], 502 $self->url_up_to_contents($depth), 503 qq[" title="All Documents"><<</a></b></p>\n], 504 505 $page->html_footer || '', 506 ) 507 if $self->contents_file 508 ; 509 return; 510 } 511 512 sub url_up_to_contents { 513 my($self, $depth) = @_; 514 --$depth; 515 return join '/', ('..') x $depth, esc($self->contents_file); 516 } 517 518 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- 519 520 sub find_all_pods { 521 my($self, $dirs) = @_; 522 # You can override find_all_pods in a subclass if you want to 523 # do extra filtering or whatnot. But for the moment, we just 524 # pass to modnames2paths: 525 return $self->modnames2paths($dirs); 526 } 527 528 #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- 529 530 sub modnames2paths { # return a hashref mapping modulenames => paths 531 my($self, $dirs) = @_; 532 533 my $m2p; 534 { 535 my $search = $SEARCH_CLASS->new; 536 DEBUG and print "Searching via $search\n"; 537 $search->verbose(1) if DEBUG > 10; 538 $search->progress( $self->progress->copy->goal(0) ) if $self->progress; 539 $search->shadows(0); # don't bother noting shadowed files 540 $search->inc( $dirs ? 0 : 1 ); 541 $search->survey( $dirs ? @$dirs : () ); 542 $m2p = $search->name2path; 543 die "What, no name2path?!" unless $m2p; 544 } 545 546 $self->muse("That's odd... no modules found!") unless keys %$m2p; 547 if( DEBUG > 4 ) { 548 print "Modules found (name => path):\n"; 549 foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) { 550 print " $m $$m2p{$m}\n"; 551 } 552 print "(total ", scalar(keys %$m2p), ")\n\n"; 553 } elsif( DEBUG ) { 554 print "Found ", scalar(keys %$m2p), " modules.\n"; 555 } 556 $self->muse( "Found ", scalar(keys %$m2p), " modules." ); 557 558 # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref 559 return $m2p; 560 } 561 562 #=========================================================================== 563 564 sub _wopen { 565 # this is abstracted out so that the daemon class can override it 566 my($self, $outpath) = @_; 567 require Symbol; 568 my $out_fh = Symbol::gensym(); 569 DEBUG > 5 and print "Write-opening to $outpath\n"; 570 return $out_fh if open($out_fh, ">", "$outpath"); 571 require Carp; 572 Carp::croak("Can't write-open $outpath: $!"); 573 } 574 575 #========================================================================== 576 577 sub add_css { 578 my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_; 579 return unless $url; 580 unless($name) { 581 # cook up a reasonable name based on the URL 582 $name = $url; 583 if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) { 584 $name = $1; 585 $name =~ s/\.css//i; 586 } 587 } 588 $media ||= 'all'; 589 $content_type ||= 'text/css'; 590 591 my $bunch = [$url, $name, $content_type, $media, $_code]; 592 if($is_default) { unshift @{ $self->_css_wad }, $bunch } 593 else { push @{ $self->_css_wad }, $bunch } 594 return; 595 } 596 597 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 598 599 sub _spray_css { 600 my($self, $outdir) = @_; 601 602 return unless $self->css_flurry(); 603 $self->_gen_css_wad(); 604 605 my $lol = $self->_css_wad; 606 foreach my $chunk (@$lol) { 607 my $url = $chunk->[0]; 608 my $outfile; 609 if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) { 610 $outfile = $self->filespecsys->catfile( $outdir, "$1" ); 611 DEBUG > 5 and print "Noting $$chunk[0] as a file I'll create.\n"; 612 } else { 613 DEBUG > 5 and print "OK, noting $$chunk[0] as an external CSS.\n"; 614 # Requires no further attention. 615 next; 616 } 617 618 #$self->muse( "Writing autogenerated CSS file $outfile" ); 619 my $Cssout = $self->_wopen($outfile); 620 print $Cssout ${$chunk->[-1]} 621 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; 622 close($Cssout); 623 DEBUG > 5 and print "Wrote $outfile\n"; 624 } 625 626 return; 627 } 628 629 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 630 631 sub _css_wad_to_markup { 632 my($self, $depth) = @_; 633 634 my @css = @{ $self->_css_wad || return '' }; 635 return '' unless @css; 636 637 my $rel = 'stylesheet'; 638 my $out = ''; 639 640 --$depth; 641 my $uplink = $depth ? ('../' x $depth) : ''; 642 643 foreach my $chunk (@css) { 644 next unless $chunk and @$chunk; 645 646 my( $url1, $url2, $title, $type, $media) = ( 647 $self->_maybe_uplink( $chunk->[0], $uplink ), 648 esc(grep !ref($_), @$chunk) 649 ); 650 651 $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n}; 652 653 $rel = 'alternate stylesheet'; # alternates = all non-first iterations 654 } 655 return $out; 656 } 657 658 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 659 sub _maybe_uplink { 660 # if the given URL looks relative, return the given uplink string -- 661 # otherwise return emptystring 662 my($self, $url, $uplink) = @_; 663 ($url =~ m{^\./} or $url !~ m{[/\:]} ) 664 ? $uplink 665 : '' 666 # qualify it, if/as needed 667 } 668 669 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 670 sub _gen_css_wad { 671 my $self = $_[0]; 672 my $css_template = $self->_css_template; 673 foreach my $variation ( 674 675 # Commented out for sake of concision: 676 # 677 # 011n=black_with_red_on_white 678 # 001n=black_with_yellow_on_white 679 # 101n=black_with_green_on_white 680 # 110=white_with_yellow_on_black 681 # 010=white_with_green_on_black 682 # 011=white_with_blue_on_black 683 # 100=white_with_red_on_black 684 685 qw[ 686 110n=black_with_blue_on_white 687 010n=black_with_magenta_on_white 688 100n=black_with_cyan_on_white 689 690 101=white_with_purple_on_black 691 001=white_with_navy_blue_on_black 692 693 010a=grey_with_green_on_black 694 010b=white_with_green_on_grey 695 101an=black_with_green_on_grey 696 101bn=grey_with_green_on_white 697 ]) { 698 699 my $outname = $variation; 700 my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3) 701 if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s; 702 @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op! 703 704 my $this_css = 705 "/* This file is autogenerated. Do not edit. $variation */\n\n" 706 . $css_template; 707 708 # Only look at three-digitty colors, for now at least. 709 if( $flipmode =~ m/n/ ) { 710 $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg; 711 $this_css =~ s/\bthin\b/medium/g; 712 } 713 $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b> 714 < join '', '#', ($1,$2,$3)[@swap] >eg if @swap; 715 716 if( $flipmode =~ m/a/) 717 { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey 718 elsif($flipmode =~ m/b/) 719 { $this_css =~ s/#000\b/#666/gi } # white -> light grey 720 721 my $name = $outname; 722 $name =~ tr/-_/ /; 723 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css); 724 } 725 726 # Now a few indexless variations: 727 foreach my $variation (qw[ 728 black_with_blue_on_white white_with_purple_on_black 729 white_with_green_on_grey grey_with_green_on_white 730 ]) { 731 my $outname = "indexless_$variation"; 732 my $this_css = join "\n", 733 "/* This file is autogenerated. Do not edit. $outname */\n", 734 "\@import url(\"./_$variation.css\");", 735 ".indexgroup { display: none; }", 736 "\n", 737 ; 738 my $name = $outname; 739 $name =~ tr/-_/ /; 740 $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css); 741 } 742 743 return; 744 } 745 746 sub _color_negate { 747 my $x = lc $_[0]; 748 $x =~ tr[0123456789abcdef] 749 [fedcba9876543210]; 750 return $x; 751 } 752 753 #=========================================================================== 754 755 sub add_javascript { 756 my($self, $url, $content_type, $_code) = @_; 757 return unless $url; 758 push @{ $self->_javascript_wad }, [ 759 $url, $content_type || 'text/javascript', $_code 760 ]; 761 return; 762 } 763 764 sub _spray_javascript { 765 my($self, $outdir) = @_; 766 return unless $self->javascript_flurry(); 767 $self->_gen_javascript_wad(); 768 769 my $lol = $self->_javascript_wad; 770 foreach my $script (@$lol) { 771 my $url = $script->[0]; 772 my $outfile; 773 774 if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) { 775 $outfile = $self->filespecsys->catfile( $outdir, "$1" ); 776 DEBUG > 5 and print "Noting $$script[0] as a file I'll create.\n"; 777 } else { 778 DEBUG > 5 and print "OK, noting $$script[0] as an external JavaScript.\n"; 779 next; 780 } 781 782 #$self->muse( "Writing JavaScript file $outfile" ); 783 my $Jsout = $self->_wopen($outfile); 784 785 print $Jsout ${$script->[-1]} 786 or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; 787 close($Jsout); 788 DEBUG > 5 and print "Wrote $outfile\n"; 789 } 790 791 return; 792 } 793 794 sub _gen_javascript_wad { 795 my $self = $_[0]; 796 my $js_code = $self->_javascript || return; 797 $self->add_javascript( "_podly.js", 0, \$js_code); 798 return; 799 } 800 801 sub _javascript_wad_to_markup { 802 my($self, $depth) = @_; 803 804 my @scripts = @{ $self->_javascript_wad || return '' }; 805 return '' unless @scripts; 806 807 my $out = ''; 808 809 --$depth; 810 my $uplink = $depth ? ('../' x $depth) : ''; 811 812 foreach my $s (@scripts) { 813 next unless $s and @$s; 814 815 my( $url1, $url2, $type, $media) = ( 816 $self->_maybe_uplink( $s->[0], $uplink ), 817 esc(grep !ref($_), @$s) 818 ); 819 820 $out .= qq{<script type="$type" src="$url1$url2"></script>\n}; 821 } 822 return $out; 823 } 824 825 #=========================================================================== 826 827 sub _css_template { return $CSS } 828 sub _javascript { return $JAVASCRIPT } 829 830 $CSS = <<'EOCSS'; 831 /* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */ 832 833 @media all { .hide { display: none; } } 834 835 @media print { 836 .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none } 837 838 * { 839 border-color: black !important; 840 color: black !important; 841 background-color: transparent !important; 842 background-image: none !important; 843 } 844 845 dl.superindex > dd { 846 word-spacing: .6em; 847 } 848 } 849 850 @media aural, braille, embossed { 851 div.indexgroup { display: none; } /* Too noisy, don't you think? */ 852 dl.superindex > dt:before { content: "Group "; } 853 dl.superindex > dt:after { content: " contains:"; } 854 .backlinktop a:before { content: "Back to contents"; } 855 .backlinkbottom a:before { content: "Back to contents"; } 856 } 857 858 @media aural { 859 dl.superindex > dt { pause-before: 600ms; } 860 } 861 862 @media screen, tty, tv, projection { 863 .noscreen { display: none; } 864 865 a:link { color: #7070ff; text-decoration: underline; } 866 a:visited { color: #e030ff; text-decoration: underline; } 867 a:active { color: #800000; text-decoration: underline; } 868 body.contentspage a { text-decoration: none; } 869 a.u { color: #fff !important; text-decoration: none; } 870 871 body.pod { 872 margin: 0 5px; 873 color: #fff; 874 background-color: #000; 875 } 876 877 body.pod h1, body.pod h2, body.pod h3, body.pod h4 { 878 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; 879 font-weight: normal; 880 margin-top: 1.2em; 881 margin-bottom: .1em; 882 border-top: thin solid transparent; 883 /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */ 884 } 885 886 body.pod h1 { border-top-color: #0a0; } 887 body.pod h2 { border-top-color: #080; } 888 body.pod h3 { border-top-color: #040; } 889 body.pod h4 { border-top-color: #010; } 890 891 p.backlinktop + h1 { border-top: none; margin-top: 0em; } 892 p.backlinktop + h2 { border-top: none; margin-top: 0em; } 893 p.backlinktop + h3 { border-top: none; margin-top: 0em; } 894 p.backlinktop + h4 { border-top: none; margin-top: 0em; } 895 896 body.pod dt { 897 font-size: 105%; /* just a wee bit more than normal */ 898 } 899 900 .indexgroup { font-size: 80%; } 901 902 .backlinktop, .backlinkbottom { 903 margin-left: -5px; 904 margin-right: -5px; 905 background-color: #040; 906 border-top: thin solid #050; 907 border-bottom: thin solid #050; 908 } 909 910 .backlinktop a, .backlinkbottom a { 911 text-decoration: none; 912 color: #080; 913 background-color: #000; 914 border: thin solid #0d0; 915 } 916 .backlinkbottom { margin-bottom: 0; padding-bottom: 0; } 917 .backlinktop { margin-top: 0; padding-top: 0; } 918 919 body.contentspage { 920 color: #fff; 921 background-color: #000; 922 } 923 924 body.contentspage h1 { 925 color: #0d0; 926 margin-left: 1em; 927 margin-right: 1em; 928 text-indent: -.9em; 929 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; 930 font-weight: normal; 931 border-top: thin solid #fff; 932 border-bottom: thin solid #fff; 933 text-align: center; 934 } 935 936 dl.superindex > dt { 937 font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; 938 font-weight: normal; 939 font-size: 90%; 940 margin-top: .45em; 941 /* margin-bottom: -.15em; */ 942 } 943 dl.superindex > dd { 944 word-spacing: .6em; /* most important rule here! */ 945 } 946 dl.superindex > a:link { 947 text-decoration: none; 948 color: #fff; 949 } 950 951 .contentsfooty { 952 border-top: thin solid #999; 953 font-size: 90%; 954 } 955 956 } 957 958 /* The End */ 959 960 EOCSS 961 962 #========================================================================== 963 964 $JAVASCRIPT = <<'EOJAVASCRIPT'; 965 966 // From http://www.alistapart.com/articles/alternate/ 967 968 function setActiveStyleSheet(title) { 969 var i, a, main; 970 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { 971 if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) { 972 a.disabled = true; 973 if(a.getAttribute("title") == title) a.disabled = false; 974 } 975 } 976 } 977 978 function getActiveStyleSheet() { 979 var i, a; 980 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { 981 if( a.getAttribute("rel").indexOf("style") != -1 982 && a.getAttribute("title") 983 && !a.disabled 984 ) return a.getAttribute("title"); 985 } 986 return null; 987 } 988 989 function getPreferredStyleSheet() { 990 var i, a; 991 for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { 992 if( a.getAttribute("rel").indexOf("style") != -1 993 && a.getAttribute("rel").indexOf("alt") == -1 994 && a.getAttribute("title") 995 ) return a.getAttribute("title"); 996 } 997 return null; 998 } 999 1000 function createCookie(name,value,days) { 1001 if (days) { 1002 var date = new Date(); 1003 date.setTime(date.getTime()+(days*24*60*60*1000)); 1004 var expires = "; expires="+date.toGMTString(); 1005 } 1006 else expires = ""; 1007 document.cookie = name+"="+value+expires+"; path=/"; 1008 } 1009 1010 function readCookie(name) { 1011 var nameEQ = name + "="; 1012 var ca = document.cookie.split(';'); 1013 for(var i=0 ; i < ca.length ; i++) { 1014 var c = ca[i]; 1015 while (c.charAt(0)==' ') c = c.substring(1,c.length); 1016 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); 1017 } 1018 return null; 1019 } 1020 1021 window.onload = function(e) { 1022 var cookie = readCookie("style"); 1023 var title = cookie ? cookie : getPreferredStyleSheet(); 1024 setActiveStyleSheet(title); 1025 } 1026 1027 window.onunload = function(e) { 1028 var title = getActiveStyleSheet(); 1029 createCookie("style", title, 365); 1030 } 1031 1032 var cookie = readCookie("style"); 1033 var title = cookie ? cookie : getPreferredStyleSheet(); 1034 setActiveStyleSheet(title); 1035 1036 // The End 1037 1038 EOJAVASCRIPT 1039 1040 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1041 1; 1042 __END__ 1043 1044 1045 =head1 NAME 1046 1047 Pod::Simple::HTMLBatch - convert several Pod files to several HTML files 1048 1049 =head1 SYNOPSIS 1050 1051 perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out 1052 1053 1054 =head1 DESCRIPTION 1055 1056 This module is used for running batch-conversions of a lot of HTML 1057 documents 1058 1059 This class is NOT a subclass of Pod::Simple::HTML 1060 (nor of bad old Pod::Html) -- although it uses 1061 Pod::Simple::HTML for doing the conversion of each document. 1062 1063 The normal use of this class is like so: 1064 1065 use Pod::Simple::HTMLBatch; 1066 my $batchconv = Pod::Simple::HTMLBatch->new; 1067 $batchconv->some_option( some_value ); 1068 $batchconv->some_other_option( some_other_value ); 1069 $batchconv->batch_convert( \@search_dirs, $output_dir ); 1070 1071 =head2 FROM THE COMMAND LINE 1072 1073 Note that this class also provides 1074 (but does not export) the function Pod::Simple::HTMLBatch::go. 1075 This is basically just a shortcut for C<< 1076 Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>. 1077 It's meant to be handy for calling from the command line. 1078 1079 However, the shortcut requires that you specify exactly two command-line 1080 arguments, C<indirs> and C<outdir>. 1081 1082 Example: 1083 1084 % mkdir out_html 1085 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html 1086 (to convert the pod from Perl's @INC 1087 files under the directory ../htmlversion) 1088 1089 (Note that the command line there contains a literal atsign-I-N-C. This 1090 is handled as a special case by batch_convert, in order to save you having 1091 to enter the odd-looking "" as the first command-line parameter when you 1092 mean "just use whatever's in @INC".) 1093 1094 Example: 1095 1096 % mkdir ../seekrut 1097 % chmod og-rx ../seekrut 1098 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../htmlversion 1099 (to convert the pod under the current dir into HTML 1100 files under the directory ../htmlversion) 1101 1102 Example: 1103 1104 % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs . 1105 (to convert all pod from happydocs into the current directory) 1106 1107 1108 1109 =head1 MAIN METHODS 1110 1111 =over 1112 1113 =item $batchconv = Pod::Simple::HTMLBatch->new; 1114 1115 This TODO 1116 1117 1118 =item $batchconv->batch_convert( I<indirs>, I<outdir> ); 1119 1120 this TODO 1121 1122 =item $batchconv->batch_convert( undef , ...); 1123 1124 =item $batchconv->batch_convert( q{@INC}, ...); 1125 1126 These two values for I<indirs> specify that the normal Perl @INC 1127 1128 =item $batchconv->batch_convert( \@dirs , ...); 1129 1130 This specifies that the input directories are the items in 1131 the arrayref C<\@dirs>. 1132 1133 =item $batchconv->batch_convert( "somedir" , ...); 1134 1135 This specifies that the director "somedir" is the input. 1136 (This can be an absolute or relative path, it doesn't matter.) 1137 1138 A common value you might want would be just "." for the current 1139 directory: 1140 1141 $batchconv->batch_convert( "." , ...); 1142 1143 1144 =item $batchconv->batch_convert( 'somedir:someother:also' , ...); 1145 1146 This specifies that you want the dirs "somedir", "somother", and "also" 1147 scanned, just as if you'd passed the arrayref 1148 C<[qw( somedir someother also)]>. Note that a ":"-separator is normal 1149 under Unix, but Under MSWin, you'll need C<'somedir;someother;also'> 1150 instead, since the pathsep on MSWin is ";" instead of ":". (And 1151 I<that> is because ":" often comes up in paths, like 1152 C<"c:/perl/lib">.) 1153 1154 (Exactly what separator character should be used, is gotten from 1155 C<$Config::Config{'path_sep'}>, via the L<Config> module.) 1156 1157 =item $batchconv->batch_convert( ... , undef ); 1158 1159 This specifies that you want the HTML output to go into the current 1160 directory. 1161 1162 (Note that a missing or undefined value means a different thing in 1163 the first slot than in the second. That's so that C<batch_convert()> 1164 with no arguments (or undef arguments) means "go from @INC, into 1165 the current directory.) 1166 1167 =item $batchconv->batch_convert( ... , 'somedir' ); 1168 1169 This specifies that you want the HTML output to go into the 1170 directory 'somedir'. 1171 (This can be an absolute or relative path, it doesn't matter.) 1172 1173 =back 1174 1175 1176 Note that you can also call C<batch_convert> as a class method, 1177 like so: 1178 1179 Pod::Simple::HTMLBatch->batch_convert( ... ); 1180 1181 That is just short for this: 1182 1183 Pod::Simple::HTMLBatch-> new-> batch_convert(...); 1184 1185 That is, it runs a conversion with default options, for 1186 whatever inputdirs and output dir you specify. 1187 1188 1189 =head2 ACCESSOR METHODS 1190 1191 The following are all accessor methods -- that is, they don't do anything 1192 on their own, but just alter the contents of the conversion object, 1193 which comprises the options for this particular batch conversion. 1194 1195 We show the "put" form of the accessors below (i.e., the syntax you use 1196 for setting the accessor to a specific value). But you can also 1197 call each method with no parameters to get its current value. For 1198 example, C<< $self->contents_file() >> returns the current value of 1199 the contents_file attribute. 1200 1201 =over 1202 1203 1204 =item $batchconv->verbose( I<nonnegative_integer> ); 1205 1206 This controls how verbose to be during batch conversion, as far as 1207 notes to STDOUT (or whatever is C<select>'d) about how the conversion 1208 is going. If 0, no progress information is printed. 1209 If 1 (the default value), some progress information is printed. 1210 Higher values print more information. 1211 1212 1213 =item $batchconv->index( I<true-or-false> ); 1214 1215 This controls whether or not each HTML page is liable to have a little 1216 table of contents at the top (which we call an "index" for historical 1217 reasons). This is true by default. 1218 1219 1220 =item $batchconv->contents_file( I<filename> ); 1221 1222 If set, should be the name of a file (in the output directory) 1223 to write the HTML index to. The default value is "index.html". 1224 If you set this to a false value, no contents file will be written. 1225 1226 =item $batchconv->contents_page_start( I<HTML_string> ); 1227 1228 This specifies what string should be put at the beginning of 1229 the contents page. 1230 The default is a string more or less like this: 1231 1232 <html> 1233 <head><title>Perl Documentation</title></head> 1234 <body class='contentspage'> 1235 <h1>Perl Documentation</h1> 1236 1237 =item $batchconv->contents_page_end( I<HTML_string> ); 1238 1239 This specifies what string should be put at the end of the contents page. 1240 The default is a string more or less like this: 1241 1242 <p class='contentsfooty'>Generated by 1243 Pod::Simple::HTMLBatch v3.01 under Perl v5.008 1244 <br >At Fri May 14 22:26:42 2004 GMT, 1245 which is Fri May 14 14:26:42 2004 local time.</p> 1246 1247 1248 1249 =item $batchconv->add_css( $url ); 1250 1251 TODO 1252 1253 =item $batchconv->add_javascript( $url ); 1254 1255 TODO 1256 1257 =item $batchconv->css_flurry( I<true-or-false> ); 1258 1259 If true (the default value), we autogenerate some CSS files in the 1260 output directory, and set our HTML files to use those. 1261 TODO: continue 1262 1263 =item $batchconv->javascript_flurry( I<true-or-false> ); 1264 1265 If true (the default value), we autogenerate a JavaScript in the 1266 output directory, and set our HTML files to use it. Currently, 1267 the JavaScript is used only to get the browser to remember what 1268 stylesheet it prefers. 1269 TODO: continue 1270 1271 =item $batchconv->no_contents_links( I<true-or-false> ); 1272 1273 TODO 1274 1275 =item $batchconv->html_render_class( I<classname> ); 1276 1277 This sets what class is used for rendering the files. 1278 The default is "Pod::Simple::Search". If you set it to something else, 1279 it should probably be a subclass of Pod::Simple::Search, and you should 1280 C<require> or C<use> that class so that's it's loaded before 1281 Pod::Simple::HTMLBatch tries loading it. 1282 1283 =back 1284 1285 1286 1287 1288 =head1 NOTES ON CUSTOMIZATION 1289 1290 TODO 1291 1292 call add_css($someurl) to add stylesheet as alternate 1293 call add_css($someurl,1) to add as primary stylesheet 1294 1295 call add_javascript 1296 1297 subclass Pod::Simple::HTML and set $batchconv->html_render_class to 1298 that classname 1299 and maybe override 1300 $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth) 1301 or maybe override 1302 $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth) 1303 1304 1305 1306 =head1 ASK ME! 1307 1308 If you want to do some kind of big pod-to-HTML version with some 1309 particular kind of option that you don't see how to achieve using this 1310 module, email me (C<sburke@cpan.org>) and I'll probably have a good idea 1311 how to do it. For reasons of concision and energetic laziness, some 1312 methods and options in this module (and the dozen modules it depends on) 1313 are undocumented; but one of those undocumented bits might be just what 1314 you're looking for. 1315 1316 1317 =head1 SEE ALSO 1318 1319 L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec> 1320 1321 1322 1323 1324 =head1 COPYRIGHT AND DISCLAIMERS 1325 1326 Copyright (c) 2004 Sean M. Burke. All rights reserved. 1327 1328 This library is free software; you can redistribute it and/or modify it 1329 under the same terms as Perl itself. 1330 1331 This program is distributed in the hope that it will be useful, but 1332 without any warranty; without even the implied warranty of 1333 merchantability or fitness for a particular purpose. 1334 1335 =head1 AUTHOR 1336 1337 Sean M. Burke C<sburke@cpan.org> 1338 1339 =cut 1340 1341 1342 -
(a) /dev/null vs. (b) lib/Pod/Simple/HTMLLegacy.pm
a b 1 2 require 5; 3 package Pod::Simple::HTMLLegacy; 4 use strict; 5 6 use vars qw($VERSION); 7 use Getopt::Long; 8 9 $VERSION = "5.01"; 10 11 #-------------------------------------------------------------------------- 12 # 13 # This class is meant to thinly emulate bad old Pod::Html 14 # 15 # TODO: some basic docs 16 17 sub pod2html { 18 my @args = (@_); 19 20 my( $verbose, $infile, $outfile, $title ); 21 my $index = 1; 22 23 { 24 my($help); 25 26 my($netscape); # dummy 27 local @ARGV = @args; 28 GetOptions( 29 "help" => \$help, 30 "verbose!" => \$verbose, 31 "infile=s" => \$infile, 32 "outfile=s" => \$outfile, 33 "title=s" => \$title, 34 "index!" => \$index, 35 36 "netscape!" => \$netscape, 37 ) or return bad_opts(@args); 38 bad_opts(@args) if @ARGV; # it should be all switches! 39 return help_message() if $help; 40 } 41 42 for($infile, $outfile) { $_ = undef unless defined and length } 43 44 if($verbose) { 45 warn sprintf "%s version %s\n", __PACKAGE__, $VERSION; 46 warn "OK, processed args [@args] ...\n"; 47 warn sprintf 48 " Verbose: %s\n Index: %s\n Infile: %s\n Outfile: %s\n Title: %s\n", 49 map defined($_) ? $_ : "(nil)", 50 $verbose, $index, $infile, $outfile, $title, 51 ; 52 *Pod::Simple::HTML::DEBUG = sub(){1}; 53 } 54 require Pod::Simple::HTML; 55 Pod::Simple::HTML->VERSION(3); 56 57 die "No such input file as $infile\n" 58 if defined $infile and ! -e $infile; 59 60 61 my $pod = Pod::Simple::HTML->new; 62 $pod->force_title($title) if defined $title; 63 $pod->index($index); 64 return $pod->parse_from_file($infile, $outfile); 65 } 66 67 #-------------------------------------------------------------------------- 68 69 sub bad_opts { die _help_message(); } 70 sub help_message { print STDOUT _help_message() } 71 72 #-------------------------------------------------------------------------- 73 74 sub _help_message { 75 76 join '', 77 78 "[", __PACKAGE__, " version ", $VERSION, qq~] 79 Usage: pod2html --help --infile=<name> --outfile=<name> 80 --verbose --index --noindex 81 82 Options: 83 --help - prints this message. 84 --[no]index - generate an index at the top of the resulting html 85 (default behavior). 86 --infile - filename for the pod to convert (input taken from stdin 87 by default). 88 --outfile - filename for the resulting html file (output sent to 89 stdout by default). 90 --title - title that will appear in resulting html file. 91 --[no]verbose - self-explanatory (off by default). 92 93 Note that pod2html is DEPRECATED, and this version implements only 94 some of the options known to older versions. 95 For more information, see 'perldoc pod2html'. 96 ~; 97 98 } 99 100 1; 101 __END__ 102 103 OVER the underpass! UNDER the overpass! Around the FUTURE and BEYOND REPAIR!! 104 -
(a) /dev/null vs. (b) lib/Pod/Simple/XHTML.pm
a b 1 =pod 2 3 =head1 NAME 4 5 Pod::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 19 This class is a formatter that takes Pod and renders it as XHTML 20 validating HTML. 21 22 This is a subclass of L<Pod::Simple::Methody> and inherits all its 23 methods. The implementation is entirely different than 24 L<Pod::Simple::HTML>, but it largely preserves the same interface. 25 26 =cut 27 28 package Pod::Simple::XHTML; 29 use strict; 30 use vars qw( $VERSION @ISA $HAS_HTML_ENTITIES ); 31 $VERSION = '3.04'; 32 use Carp (); 33 use Pod::Simple::Methody (); 34 @ISA = ('Pod::Simple::Methody'); 35 36 BEGIN { 37 $HAS_HTML_ENTITIES = eval "require HTML::Entities; 1"; 38 } 39 40 my %entities = ( 41 q{>} => 'gt', 42 q{<} => 'lt', 43 q{'} => '#39', 44 q{"} => 'quot', 45 q{&} => 'amp', 46 ); 47 48 sub 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 60 Pod::Simple::XHTML offers a number of methods that modify the format of 61 the HTML output. Call these after creating the parser object, but before 62 the 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 70 In turning L<Foo::Bar> into http://whatever/Foo%3a%3aBar, what 71 to put before the "Foo%3a%3aBar". The default value is 72 "http://search.cpan.org/perldoc?". 73 74 =head2 perldoc_url_postfix 75 76 What to put after "Foo%3a%3aBar" in the URL. This option is not set by 77 default. 78 79 =head2 title_prefix, title_postfix 80 81 What to put before and after the title in the head. The values should 82 already be &-escaped. 83 84 =head2 html_css 85 86 $parser->html_css('path/to/style.css'); 87 88 The URL or relative path of a CSS file to include. This option is not 89 set by default. 90 91 =head2 html_javascript 92 93 The URL or relative path of a JavaScript file to pull in. This option is 94 not set by default. 95 96 =head2 html_doctype 97 98 A document type tag for the file. This option is not set by default. 99 100 =head2 html_header_tags 101 102 Additional arbitrary HTML tags for the header of the document. The 103 default value is just a content type header tag: 104 105 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 106 107 Add additional meta tags here, or blocks of inline CSS or JavaScript 108 (wrapped in the appropriate tags). 109 110 =head2 default_title 111 112 Set a default title for the page if no title can be determined from the 113 content. The value of this string should already be &-escaped. 114 115 =head2 force_title 116 117 Force a title for the page (don't try to determine it from the content). 118 The value of this string should already be &-escaped. 119 120 =head2 html_header, html_footer 121 122 Set the HTML output at the beginning and end of each file. The default 123 header includes a title, a doctype tag (if C<html_doctype> is set), a 124 content tag (customized by C<html_header_tags>), a tag for a CSS file 125 (if C<html_css> is set), and a tag for a Javascript file (if 126 C<html_javascript> is set). The default footer simply closes the C<html> 127 and C<body> tags. 128 129 The options listed above customize parts of the default header, but 130 setting C<html_header> or C<html_footer> completely overrides the 131 built-in header or footer. These may be useful if you want to use 132 template tags instead of literal HTML headers and footers or are 133 integrating converted POD pages in a larger website. 134 135 If you want no headers or footers output in the HTML, set these options 136 to the empty string. 137 138 =head2 index 139 140 TODO -- Not implemented. 141 142 Whether to add a table-of-contents at the top of each page (called an 143 index for the sake of tradition). 144 145 146 =cut 147 148 __PACKAGE__->_accessorize( 149 'perldoc_url_prefix', 150 'perldoc_url_postfix', 151 'title_prefix', 'title_postfix', 152 'html_css', 153 'html_javascript', 154 'html_doctype', 155 'html_header_tags', 156 'title', # Used internally for the title extracted from the content 157 'default_title', 158 'force_title', 159 'html_header', 160 'html_footer', 161 'index', 162 'batch_mode', # whether we're in batch mode 163 'batch_mode_current_level', 164 # When in batch mode, how deep the current module is: 1 for "LWP", 165 # 2 for "LWP::Procotol", 3 for "LWP::Protocol::GHTTP", etc 166 ); 167 168 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 169 170 =head1 SUBCLASSING 171 172 If the standard options aren't enough, you may want to subclass 173 Pod::Simple::XHMTL. These are the most likely candidates for methods 174 you'll want to override when subclassing. 175 176 =cut 177 178 sub new { 179 my $self = shift; 180 my $new = $self->SUPER::new(@_); 181 $new->{'output_fh'} ||= *STDOUT{IO}; 182 $new->accept_targets( 'html', 'HTML' ); 183 $new->perldoc_url_prefix('http://search.cpan.org/perldoc?'); 184 $new->html_header_tags('<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">'); 185 $new->nix_X_codes(1); 186 $new->codes_in_verbatim(1); 187 $new->{'scratch'} = ''; 188 return $new; 189 } 190 191 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 192 193 =head2 handle_text 194 195 This method handles the body of text within any element: it's the body 196 of a paragraph, or everything between a "=begin" tag and the 197 corresponding "=end" tag, or the text within an L entity, etc. You would 198 want to override this if you are adding a custom element type that does 199 more than just display formatted text. Perhaps adding a way to generate 200 HTML tables from an extended version of POD. 201 202 So, let's say you want add a custom element called 'foo'. In your 203 subclass's C<new> method, after calling C<SUPER::new> you'd call: 204 205 $new->accept_targets_as_text( 'foo' ); 206 207 Then override the C<start_for> method in the subclass to check for when 208 "$flags->{'target'}" is equal to 'foo' and set a flag that marks that 209 you're in a foo block (maybe "$self->{'in_foo'} = 1"). Then override the 210 C<handle_text> method to check for the flag, and pass $text to your 211 custom subroutine to construct the HTML output for 'foo' elements, 212 something like: 213 214 sub handle_text { 215 my ($self, $text) = @_; 216 if ($self->{'in_foo'}) { 217 $self->{'scratch'} .= build_foo_html($text); 218 } else { 219 $self->{'scratch'} .= $text; 220 } 221 } 222 223 =cut 224 225 sub handle_text { 226 # escape special characters in HTML (<, >, &, etc) 227 $_[0]{'scratch'} .= $_[0]{'in_verbatim'} ? encode_entities( $_[1] ) : $_[1] 228 } 229 230 sub start_Para { $_[0]{'scratch'} = '<p>' } 231 sub start_Verbatim { $_[0]{'scratch'} = '<pre><code>'; $_[0]{'in_verbatim'} = 1} 232 233 sub start_head1 { $_[0]{'scratch'} = '<h1>' } 234 sub start_head2 { $_[0]{'scratch'} = '<h2>' } 235 sub start_head3 { $_[0]{'scratch'} = '<h3>' } 236 sub start_head4 { $_[0]{'scratch'} = '<h4>' } 237 238 sub start_item_bullet { $_[0]{'scratch'} = '<li>' } 239 sub start_item_number { $_[0]{'scratch'} = "<li>$_[1]{'number'}. " } 240 sub start_item_text { $_[0]{'scratch'} = '<li>' } 241 242 sub start_over_bullet { $_[0]{'scratch'} = '<ul>'; $_[0]->emit } 243 sub start_over_text { $_[0]{'scratch'} = '<ul>'; $_[0]->emit } 244 sub start_over_block { $_[0]{'scratch'} = '<ul>'; $_[0]->emit } 245 sub start_over_number { $_[0]{'scratch'} = '<ol>'; $_[0]->emit } 246 247 sub end_over_bullet { $_[0]{'scratch'} .= '</ul>'; $_[0]->emit } 248 sub end_over_text { $_[0]{'scratch'} .= '</ul>'; $_[0]->emit } 249 sub end_over_block { $_[0]{'scratch'} .= '</ul>'; $_[0]->emit } 250 sub end_over_number { $_[0]{'scratch'} .= '</ol>'; $_[0]->emit } 251 252 # . . . . . Now the actual formatters: 253 254 sub end_Para { $_[0]{'scratch'} .= '</p>'; $_[0]->emit } 255 sub end_Verbatim { 256 $_[0]{'scratch'} .= '</code></pre>'; 257 $_[0]{'in_verbatim'} = 0; 258 $_[0]->emit; 259 } 260 261 sub end_head1 { $_[0]{'scratch'} .= '</h1>'; $_[0]->emit } 262 sub end_head2 { $_[0]{'scratch'} .= '</h2>'; $_[0]->emit } 263 sub end_head3 { $_[0]{'scratch'} .= '</h3>'; $_[0]->emit } 264 sub end_head4 { $_[0]{'scratch'} .= '</h4>'; $_[0]->emit } 265 266 sub end_item_bullet { $_[0]{'scratch'} .= '</li>'; $_[0]->emit } 267 sub end_item_number { $_[0]{'scratch'} .= '</li>'; $_[0]->emit } 268 sub end_item_text { $_[0]->emit } 269 270 # This handles =begin and =for blocks of all kinds. 271 sub start_for { 272 my ($self, $flags) = @_; 273 $self->{'scratch'} .= '<div'; 274 $self->{'scratch'} .= ' class="'.$flags->{'target'}.'"' if ($flags->{'target'}); 275 $self->{'scratch'} .= '>'; 276 $self->emit; 277 278 } 279 sub end_for { 280 my ($self) = @_; 281 $self->{'scratch'} .= '</div>'; 282 $self->emit; 283 } 284 285 sub start_Document { 286 my ($self) = @_; 287 if (defined $self->html_header) { 288 $self->{'scratch'} .= $self->html_header; 289 $self->emit unless $self->html_header eq ""; 290 } else { 291 my ($doctype, $title, $metatags); 292 $doctype = $self->html_doctype || ''; 293 $title = $self->force_title || $self->title || $self->default_title || ''; 294 $metatags = $self->html_header_tags || ''; 295 if ($self->html_css) { 296 $metatags .= "\n<link rel='stylesheet' href='" . 297 $self->html_css . "' type='text/css'>"; 298 } 299 if ($self->html_javascript) { 300 $metatags .= "\n<script type='text/javascript' src='" . 301 $self->html_javascript . "'></script>"; 302 } 303 $self->{'scratch'} .= <<"HTML"; 304 $doctype 305 <html> 306 <head> 307 <title>$title</title> 308 $metatags 309 </head> 310 <body> 311 HTML 312 $self->emit; 313 } 314 } 315 316 sub end_Document { 317 my ($self) = @_; 318 if (defined $self->html_footer) { 319 $self->{'scratch'} .= $self->html_footer; 320 $self->emit unless $self->html_footer eq ""; 321 } else { 322 $self->{'scratch'} .= "</body>\n</html>"; 323 $self->emit; 324 } 325 } 326 327 # Handling code tags 328 sub start_B { $_[0]{'scratch'} .= '<b>' } 329 sub end_B { $_[0]{'scratch'} .= '</b>' } 330 331 sub start_C { $_[0]{'scratch'} .= '<code>' } 332 sub end_C { $_[0]{'scratch'} .= '</code>' } 333 334 sub start_E { $_[0]{'scratch'} .= '&' } 335 sub end_E { $_[0]{'scratch'} .= ';' } 336 337 sub start_F { $_[0]{'scratch'} .= '<i>' } 338 sub end_F { $_[0]{'scratch'} .= '</i>' } 339 340 sub start_I { $_[0]{'scratch'} .= '<i>' } 341 sub end_I { $_[0]{'scratch'} .= '</i>' } 342 343 sub start_L { 344 my ($self, $flags) = @_; 345 my $url; 346 if ($flags->{'type'} eq 'url') { 347 $url = $flags->{'to'}; 348 } elsif ($flags->{'type'} eq 'pod') { 349 $url .= $self->perldoc_url_prefix || ''; 350 $url .= $flags->{'to'} || ''; 351 $url .= '/' . $flags->{'section'} if ($flags->{'section'}); 352 $url .= $self->perldoc_url_postfix || ''; 353 # require Data::Dumper; 354 # print STDERR Data::Dumper->Dump([$flags]); 355 } 356 357 $self->{'scratch'} .= '<a href="'. $url . '">'; 358 } 359 sub end_L { $_[0]{'scratch'} .= '</a>' } 360 361 sub start_S { $_[0]{'scratch'} .= '<nobr>' } 362 sub end_S { $_[0]{'scratch'} .= '</nobr>' } 363 364 sub emit { 365 my($self) = @_; 366 my $out = $self->{'scratch'} . "\n"; 367 print {$self->{'output_fh'}} $out, "\n"; 368 $self->{'scratch'} = ''; 369 return; 370 } 371 372 # Bypass built-in E<> handling to preserve entity encoding 373 sub _treat_Es {} 374 375 1; 376 377 __END__ 378 379 =head1 SEE ALSO 380 381 L<Pod::Simple>, L<Pod::Simple::Methody> 382 383 =head1 COPYRIGHT 384 385 Copyright (c) 2003-2005 Allison Randal. 386 387 This library is free software; you can redistribute it and/or modify 388 it under the same terms as Perl itself. The full text of the license 389 can be found in the LICENSE file included with this module. 390 391 This library is distributed in the hope that it will be useful, but 392 without any warranty; without even the implied warranty of 393 merchantability or fitness for a particular purpose. 394 395 =head1 AUTHOR 396 397 Allison Randal <allison@perl.org> 398 399 =cut 400